use std::{num::NonZeroU8, time::Duration};
use rand::RngExt;
use falling_tetromino_engine::{
Button, DelayParameters, ExtDuration, Game, GameAccess, GameBuilder, GameLimits, GameModifier,
GameRng, InGameTime, Input, Line, NotificationFeed, Phase, Piece, Stat, Tetromino,
};
use crate::tui_settings::Palette;
#[derive(
PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Debug, serde::Serialize, serde::Deserialize,
)]
pub struct Ascent {
height_loaded: usize,
}
impl Ascent {
pub const MOD_ID: &str = stringify!(Ascent);
pub fn build(builder: &GameBuilder) -> Game {
let modifier = Box::new(Self { height_loaded: 0 });
builder
.clone()
.lock_delay_params(DelayParameters::constant(ExtDuration::Infinite))
.game_limits(GameLimits::single(
Stat::TimeElapsed(Duration::from_secs(2 * 60)),
true,
))
.build_modded(vec![modifier])
}
}
impl GameModifier for Ascent {
fn id(&self) -> String {
Self::MOD_ID.to_owned()
}
fn cfg(&self) -> String {
"".to_owned()
}
fn try_clone(&self) -> Result<Box<dyn GameModifier>, String> {
Ok(Box::new(*self))
}
fn on_game_built(&mut self, game: GameAccess) {
let ascent_lines = Self::prng_ascent_lines(&mut self.height_loaded, &mut game.state.rng);
for (line, ascent_line) in game
.state
.board
.iter_mut()
.take(Game::HEIGHT)
.zip(ascent_lines)
{
*line = ascent_line;
}
let asc_tet_01 = Tetromino::L;
let asc_tet_02 = Tetromino::J;
*game.phase = Phase::PieceInPlay {
piece: Piece {
tetromino: asc_tet_01,
orientation: falling_tetromino_engine::Orientation::N,
position: (0, 0),
},
autoshift_scheduled: None,
fall_or_lock_time: Duration::MAX,
lowest_y: 0,
lock_cap_time: Duration::MAX,
};
game.state.piece_held = Some((asc_tet_02, true));
game.config.generate_piece_preview = 0;
}
fn on_player_action_post(
&mut self,
game: GameAccess,
_feed: &mut NotificationFeed,
input: Input,
) {
if !matches!(
input,
Input::Activate(Button::RotateLeft | Button::Rotate180 | Button::RotateRight)
) {
return;
}
let piece = game.phase.piece_mut().unwrap();
let piece_tiles_coords = piece.tiles().map(|(coord, _)| coord);
for (y, line) in game.state.board.iter_mut().enumerate() {
for (x, tile) in line.iter_mut().take(Self::PLAYABLE_WIDTH).enumerate() {
let Some(tiletypeid) = tile else {
continue;
};
let i = tiletypeid.get();
if i <= 7 {
let tile = if piece_tiles_coords.iter().any(|&(x_p, y_p)| {
(x_p as usize).abs_diff(x) + (y_p as usize).abs_diff(y) <= 1
}) {
game.state.points += 1;
Palette::GRAY
} else {
NonZeroU8::try_from(match i {
4 => 6,
6 => 1,
1 => 3,
3 => 2,
2 => 7,
7 => 5,
5 => 4,
_ => unreachable!(),
})
.unwrap()
};
*tiletypeid = tile;
}
}
}
let has_hit_camera_top =
Game::LOCK_OUT_HEIGHT - Self::CAMERA_MARGIN_TOP <= (piece.position.1 as usize);
if !has_hit_camera_top {
return;
}
let mut ascent_lines =
Self::prng_ascent_lines(&mut self.height_loaded, &mut game.state.rng);
game.state.board.rotate_left(1);
game.state.board[Game::HEIGHT - 1] = ascent_lines.next().unwrap();
piece.position.1 -= 1;
game.state.lineclears += 1;
}
fn on_player_input_received(
&mut self,
game: GameAccess,
_feed: &mut NotificationFeed,
_time: &mut InGameTime,
player_input: &mut Option<Input>,
) {
match player_input {
Some(Input::Activate(Button::HoldPiece)) => {
player_input.take();
let (Some(piece), Some((held_tetromino, _))) =
(game.phase.piece_mut(), game.state.piece_held.as_mut())
else {
return;
};
(piece.tetromino, *held_tetromino) = (*held_tetromino, piece.tetromino);
}
Some(Input::Activate(Button::DropSoft | Button::DropHard)) => {
player_input.take();
}
_ => {}
}
}
}
impl Ascent {
const PLAYABLE_WIDTH: usize = Game::WIDTH - (1 - Game::WIDTH % 2);
const CAMERA_MARGIN_TOP: usize = 5;
fn prng_ascent_lines<'a>(
height_loaded: &'a mut usize,
rng: &'a mut GameRng,
) -> impl Iterator<Item = Line> + 'a {
std::iter::repeat(Line::default()).map(|mut line| {
if !height_loaded.is_multiple_of(2) {
for (j, tile) in line.iter_mut().enumerate() {
if j % 2 == 1 {
let white_tile = Some(Palette::WHITE);
*tile = white_tile;
}
}
let gem_idx = rng.random_range(0..Self::PLAYABLE_WIDTH);
if line[gem_idx].is_some() {
line[gem_idx] = Some(NonZeroU8::try_from(rng.random_range(1..=7)).unwrap());
}
}
if Self::PLAYABLE_WIDTH != line.len() {
let tile_id = if (*height_loaded / 10).is_multiple_of(2)
^ (height_loaded.is_multiple_of(10) || *height_loaded % 10 == 9)
{
Palette::WHITE
} else {
NonZeroU8::try_from(2).unwrap()
};
line[Self::PLAYABLE_WIDTH] = Some(tile_id);
}
*height_loaded += 1;
line
})
}
}