Skip to content

Commit

Permalink
Initial Hold- and Rotation System, refactor gravity level stuff (agai…
Browse files Browse the repository at this point in the history
…n), remove line from endscreen information, refactor cmdline flag for custom layout, refactor add_modifier to be safe, change descent mode generation
  • Loading branch information
Strophox committed Nov 18, 2024
1 parent f890770 commit cb21689
Show file tree
Hide file tree
Showing 9 changed files with 94 additions and 95 deletions.
103 changes: 57 additions & 46 deletions tetrs_engine/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -604,7 +604,7 @@ impl GameMode {
initial_gravity: 1,
increase_gravity: true,
limits: Limits {
gravity: Some((true, 16)),
gravity: Some((true, 15)),
..Default::default()
},
}
Expand Down Expand Up @@ -850,14 +850,11 @@ impl Game {

/// Adds a 'game mod' that will get executed regularly before and after each [`InternalEvent`].
///
/// # Safety
///
// FIXME: Document Safety.
/// This indirectly allows raw, mutable access to the game's [`GameMode`]
/// and [`GameState`] fields.
/// This should not cause undefined behaviour per se, but may lead to spurious `panic!`s or
/// other unexpected gameplay behaviour due to violating internal invarints.
pub unsafe fn add_modifier(&mut self, game_mod: FnGameMod) {
pub fn add_modifier(&mut self, game_mod: FnGameMod) {
self.modifiers.push(game_mod)
}

Expand Down Expand Up @@ -1156,6 +1153,29 @@ impl Game {
}
}

/// Try holding a tetromino in the game state and report success.
fn attempt_hold(&mut self, tetromino: Tetromino, event_time: GameTime) -> bool {
match self.state.hold_piece {
None | Some((_, true)) => {
if let Some((held_piece, _)) = self.state.hold_piece {
self.state.next_pieces.push_front(held_piece);
} else {
self.state.next_pieces.extend(
self.config
.tetromino_generator
.with_rng(&mut self.rng)
.take(1),
);
}
self.state.hold_piece = Some((tetromino, false));
self.state.events.clear();
self.state.events.insert(InternalEvent::Spawn, event_time);
true
}
_ => false,
}
}

/// Given an event, update the internal game state, possibly adding new future events.
///
/// This function is likely the most important part of a game update as it handles the logic of
Expand Down Expand Up @@ -1190,51 +1210,42 @@ impl Game {
.saturating_sub(self.state.next_pieces.len()),
),
);
let next_piece = Self::position_tetromino(tetromino);
feedback_events.push((event_time, Feedback::PieceSpawned(next_piece)));
// Newly spawned piece conflicts with board - Game over.
if !next_piece.fits(&self.state.board) {
self.state.end = Some(Err(GameOver::BlockOut));
return feedback_events;
}
let mut turns = 0;
if self.state.buttons_pressed[Button::RotateRight] {
turns += 1;
}
if self.state.buttons_pressed[Button::RotateAround] {
turns += 2;
}
if self.state.buttons_pressed[Button::RotateLeft] {
turns -= 1;
}
if turns != 0 {
self.state
.events
.insert(InternalEvent::Rotate(turns), event_time);
// Initial Hold System.
if self.state.buttons_pressed[Button::Hold] && self.attempt_hold(tetromino, event_time) {
None
} else {
let raw_piece = Self::position_tetromino(tetromino);
let mut turns = 0;
if self.state.buttons_pressed[Button::RotateRight] {
turns += 1;
}
if self.state.buttons_pressed[Button::RotateAround] {
turns += 2;
}
if self.state.buttons_pressed[Button::RotateLeft] {
turns -= 1;
}
// Initial Rotation system.
let next_piece = self.config
.rotation_system
.rotate(&raw_piece, &self.state.board, turns)
.unwrap_or(raw_piece);
feedback_events.push((event_time, Feedback::PieceSpawned(next_piece)));
// Newly spawned piece conflicts with board - Game over.
if !next_piece.fits(&self.state.board) {
self.state.end = Some(Err(GameOver::BlockOut));
return feedback_events;
}
self.state.events.insert(InternalEvent::Fall, event_time);
Some(next_piece)
}
self.state.events.insert(InternalEvent::Fall, event_time);
Some(next_piece)
}
InternalEvent::HoldPiece => {
let prev_piece = prev_piece.expect("hold piece event but no active piece");
match self.state.hold_piece {
None | Some((_, true)) => {
if let Some((held_piece, _)) = self.state.hold_piece {
self.state.next_pieces.push_front(held_piece);
} else {
self.state.next_pieces.extend(
self.config
.tetromino_generator
.with_rng(&mut self.rng)
.take(1),
);
}
self.state.hold_piece = Some((prev_piece.shape, false));
self.state.events.clear();
self.state.events.insert(InternalEvent::Spawn, event_time);
None
}
_ => Some(prev_piece),
if self.attempt_hold(prev_piece.shape, event_time) {
None
} else {
Some(prev_piece)
}
}
InternalEvent::Rotate(turns) => {
Expand Down
2 changes: 1 addition & 1 deletion tetrs_engine/src/piece_generation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ use crate::Tetromino;

/// Handles the information of which pieces to spawn during a game.
///
/// To actually generate [`Tetromino`]s, the [`TetrominoGenerator::with_rng`] method needs to be used to yield a
/// To actually generate [`Tetromino`]s, the [`TetrominoSource::with_rng`] method needs to be used to yield a
/// [`TetrominoIterator`] that implements [`Iterator`].
#[derive(Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
Expand Down
2 changes: 1 addition & 1 deletion tetrs_tui/src/game_mods/cheese_mode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,6 @@ pub fn new_game(cheese_limit: Option<NonZeroUsize>, gap_size: usize, gravity: u3
..Default::default()
},
});
unsafe { game.add_modifier(cheese_mode) };
game.add_modifier(cheese_mode);
game
}
2 changes: 1 addition & 1 deletion tetrs_tui/src/game_mods/combo_mode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ pub fn new_game(gravity: u32, initial_layout: u16) -> Game {
increase_gravity: false,
limits: Limits::default(),
});
unsafe { game.add_modifier(combo_mode) };
game.add_modifier(combo_mode);
game
}

Expand Down
7 changes: 3 additions & 4 deletions tetrs_tui/src/game_mods/descent_mode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,9 @@ pub fn random_descent_lines() -> impl Iterator<Item = Line> {
let mut line = [None; Game::WIDTH];
match i % 4 {
0 | 2 => {}
1 | 3 => {
r => {
for (j, cell) in line.iter_mut().enumerate() {
if j % 2 == 1 || rng.gen_bool(0.5) {
if j % 2 == 1 || (r == 1 && rng.gen_bool(0.5)) {
*cell = grey_tile;
}
}
Expand All @@ -46,7 +46,6 @@ pub fn random_descent_lines() -> impl Iterator<Item = Line> {
line[gem_idx] = Some(NonZeroU8::try_from(rng.gen_range(1..=7)).unwrap());
}
}
_ => unreachable!(),
};
if playing_width < line.len() {
line[playing_width] = color_tiles[(i / 10) % 7];
Expand Down Expand Up @@ -176,6 +175,6 @@ pub fn new_game() -> Game {
},
});
game.config_mut().preview_count = 0;
unsafe { game.add_modifier(descent_mode) };
game.add_modifier(descent_mode);
game
}
7 changes: 2 additions & 5 deletions tetrs_tui/src/game_mods/puzzle_mode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -142,13 +142,10 @@ pub fn new_game() -> Game {
name: "Puzzle".to_string(),
initial_gravity: 2,
increase_gravity: false,
limits: Limits {
gravity: Some((true, u32::try_from(puzzles_len).unwrap())),
..Default::default()
},
limits: Limits::default(),
});
game.config_mut().preview_count = 0;
unsafe { game.add_modifier(puzzle_mode) };
game.add_modifier(puzzle_mode);
game
}

Expand Down
11 changes: 6 additions & 5 deletions tetrs_tui/src/game_mods/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,23 @@ use tetrs_engine::{
};

#[allow(dead_code)]
pub fn custom_starting_board(mut layout_bits: u128) -> FnGameMod {
pub fn custom_start_board(board_str: &String) -> FnGameMod {
let grey_tile = Some(std::num::NonZeroU8::try_from(254).unwrap());
let mut init = false;
let board_str = board_str.clone();
Box::new(move |_, _, state, _, _| {
if !init {
let mut chars = board_str.chars().rev();
'init: for row in state.board.iter_mut() {
for cell in row.iter_mut().rev() {
if layout_bits == 0 {
let Some(char) = chars.next() else {
break 'init;
}
*cell = if layout_bits & 1 != 0 {
};
*cell = if char != ' ' {
grey_tile
} else {
None
};
layout_bits >>= 1;
}
}
init = true;
Expand Down
24 changes: 13 additions & 11 deletions tetrs_tui/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,19 @@ use clap::Parser;
#[derive(Parser, Debug)]
#[command(version, about, long_about = None)]
struct Args {
/// A custom starting layout for Combo mode, encoded in binary, by 4-wide rows.
/// Example: "▀▄▄▀" => 0b_1001_0110 = 150
/// => `./tetrs_tui -c 150`.
#[arg(short, long)]
combo_layout: Option<u16>,
/// A custom starting board for **Custom** mode, encoded in binary, by 10-wide rows.
/// Example: "█▀ ▄██▀ ▀█" => 0b_1100111011_1001110001 = 982815
/// => `./tetrs_tui --custom_start=982815`.
/// Custom starting board when playing Custom mode (10-wide rows), encoded as string.
/// Spaces indicate empty cells, anything else is a filled cell.
/// The string just represents the row information, starting with the topmost row.
/// Example: '█▀ ▄██▀ ▀█' => "XX XXX XXO OOO O"
/// => `./tetrs_tui --custom_start="XX XXX XXO OOO O"`.
#[arg(long)]
custom_start: Option<u128>,
/// Whether to enable the combo bot in combo mode.
custom_start: Option<String>,
/// Custom starting layout when playing Combo mode (4-wide rows), encoded as binary.
/// Example: '▀▄▄▀' => 0b_1001_0110 = 150
/// => `./tetrs_tui -combo_start=150` or `./tetrs_tui -c 150`.
#[arg(short, long)]
combo_start: Option<u16>,
/// Whether to enable the combo bot in Combo mode: `./tetrs_tui -e`
#[arg(short, long)]
enable_combo_bot: bool,
}
Expand All @@ -30,7 +32,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
let stdout = io::BufWriter::new(io::stdout());
let mut app = terminal_app::TerminalApp::new(
stdout,
args.combo_layout,
args.combo_start,
args.custom_start,
args.enable_combo_bot,
);
Expand Down
31 changes: 10 additions & 21 deletions tetrs_tui/src/terminal_app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ pub struct TerminalApp<T: Write> {
game_config: GameConfig,
game_mode_store: GameModeStore,
past_games: Vec<FinishedGameStats>,
custom_starting_board: Option<u128>,
custom_start_board: Option<String>,
combo_bot_enabled: bool,
}

Expand Down Expand Up @@ -209,7 +209,7 @@ impl<T: Write> TerminalApp<T> {
pub fn new(
mut terminal: T,
initial_combo_layout: Option<u16>,
experimental_custom_layout: Option<u128>,
custom_start_board: Option<String>,
combo_bot_enabled: bool,
) -> Self {
// Console prologue: Initialization.
Expand Down Expand Up @@ -250,7 +250,7 @@ impl<T: Write> TerminalApp<T> {
descent_mode: false,
},
past_games: vec![],
custom_starting_board: experimental_custom_layout,
custom_start_board,
combo_bot_enabled,
};
if let Err(_e) = app.load_local() {
Expand Down Expand Up @@ -799,12 +799,10 @@ impl<T: Write> TerminalApp<T> {
increase_gravity,
limits,
});
if let Some(layout_bits) = self.custom_starting_board {
unsafe {
custom_game.add_modifier(game_mods::utils::custom_starting_board(
layout_bits,
));
}
if let Some(custom_start_board_str) = &self.custom_start_board {
custom_game.add_modifier(game_mods::utils::custom_start_board(
custom_start_board_str
));
}
custom_game
};
Expand Down Expand Up @@ -1142,7 +1140,7 @@ impl<T: Write> TerminalApp<T> {
let FinishedGameStats {
timestamp: _,
actions,
score_bonuses,
score_bonuses: _,
gamemode,
last_state,
} = finished_game_stats;
Expand Down Expand Up @@ -1244,16 +1242,7 @@ impl<T: Write> TerminalApp<T> {
)))?
.queue(MoveTo(x_main, y_main + y_selection + 10))?
.queue(Print(format!("{:^w_main$}", actions_str)))?
.queue(MoveTo(x_main, y_main + y_selection + 11))?
.queue(Print(format!(
"{:^w_main$}",
format!(
"Average score bonus: {:.1}",
score_bonuses.iter().copied().map(u64::from).sum::<u64>() as f64
/ (score_bonuses.len() as f64/*I give up*/)
)
)))?
.queue(MoveTo(x_main, y_main + y_selection + 13))?
.queue(MoveTo(x_main, y_main + y_selection + 12))?
.queue(Print(format!("{:^w_main$}", "──────────────────────────")))?;
let names = selection
.iter()
Expand All @@ -1263,7 +1252,7 @@ impl<T: Write> TerminalApp<T> {
self.term
.queue(MoveTo(
x_main,
y_main + y_selection + 14 + u16::try_from(i).unwrap(),
y_main + y_selection + 13 + u16::try_from(i).unwrap(),
))?
.queue(Print(format!(
"{:^w_main$}",
Expand Down

0 comments on commit cb21689

Please sign in to comment.