use std::time::Duration;
use either::Either;
use rand::RngExt;
use crate::core_game_engine::{
BOARD_WIDTH, Button, DelayParameters, ExtDuration, Game, GameAccess, GameBuilder, GameLimits,
GameModifier, GameRng, InGameTime, Input, Line, MiscPceRots, MiscTetGens, NotificationFeed,
PLAYABLE_BOARD_HEIGHT, Phase, Piece, Stat, Tetromino, TileType,
};
#[derive(
PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Debug, serde::Serialize, serde::Deserialize,
)]
pub struct Ascent {
height_loaded: usize,
cached_display_values: [(String, String); 1],
}
impl Ascent {
pub const MOD_ID: &str = stringify!(Ascent);
pub fn build(builder: &GameBuilder) -> Game {
let modifier = Box::new(Self {
height_loaded: 0,
cached_display_values: [("Height ascended".to_owned(), 0.to_string())],
});
builder
.clone()
.rotation_system(crate::core_game_engine::MiscPceRots::Ocular)
.lock_delay_curve(Some(Either::Left(DelayParameters::constant(
ExtDuration::Infinite,
))))
.game_limits(GameLimits::single(
Stat::TimeElapsed(Duration::from_secs(2 * 60)),
true,
))
.build_modded(vec![modifier])
}
}
impl GameModifier<MiscTetGens, MiscPceRots, TileType> for Ascent {
fn id(&self) -> String {
Self::MOD_ID.to_owned()
}
fn cfg(&self) -> String {
"".to_owned()
}
fn values(&self) -> &[(String, String)] {
&self.cached_display_values
}
fn try_clone(
&self,
) -> Result<Box<dyn GameModifier<MiscTetGens, MiscPceRots, TileType>>, String> {
Ok(Box::new(self.clone()))
}
fn on_game_built(&mut self, game: GameAccess) {
let ascent_lines = Self::prng_ascent_lines(&mut self.height_loaded, &mut game.state.rng);
game.state
.board
.resize(Self::PREGENERATED_HEIGHT, Default::default());
for ((line, _is_frozen), ascent_line) in game
.state
.board
.iter_mut()
.take(Self::PREGENERATED_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: crate::core_game_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.tetromino_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 Some(piece) = game.phase.piece_mut() else {
return;
};
let piece_tiles_coords = piece.coords();
for (y, (line, _is_frozen)) in game.state.board.iter_mut().enumerate() {
for (x, tile) in line.iter_mut().take(Self::PLAYABLE_WIDTH).enumerate() {
let Some(tile_type) = tile else {
continue;
};
if matches!(tile_type, TileType::Tet(_)) {
let new_tile_type = 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;
TileType::Generic
} else {
match tile_type {
TileType::Tet(tet) => TileType::Tet(match tet {
Tetromino::O => Tetromino::S,
Tetromino::I => Tetromino::J,
Tetromino::S => Tetromino::I,
Tetromino::Z => Tetromino::L,
Tetromino::T => Tetromino::Z,
Tetromino::L => Tetromino::O,
Tetromino::J => Tetromino::T,
}),
TileType::Generic => TileType::Generic,
}
};
*tile = Some(new_tile_type);
}
}
}
let has_hit_camera_top =
PLAYABLE_BOARD_HEIGHT - Self::CAMERA_MARGIN_TOP <= (piece.position.1 as usize);
if has_hit_camera_top {
let mut ascent_lines =
Self::prng_ascent_lines(&mut self.height_loaded, &mut game.state.rng);
game.state.board.push((ascent_lines.next().unwrap(), false));
piece.position.1 -= 1;
}
self.cached_display_values[0].1 = (piece.position.1 as usize + self.height_loaded
- Self::PREGENERATED_HEIGHT)
.to_string();
}
fn on_receive_player_input(
&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.tetromino_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 = BOARD_WIDTH - (1 - BOARD_WIDTH % 2);
const PREGENERATED_HEIGHT: usize = PLAYABLE_BOARD_HEIGHT + 4;
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 {
*tile = Some(TileType::Generic);
}
}
let gem_idx = rng.random_range(0..Self::PLAYABLE_WIDTH);
if line[gem_idx].is_some() {
line[gem_idx] = Some(Tetromino::VARIANTS[rng.random_range(1..=7)].into());
}
}
if Self::PLAYABLE_WIDTH != line.len() {
let tile_ty = if (*height_loaded / 10).is_multiple_of(2)
^ (height_loaded.is_multiple_of(10) || *height_loaded % 10 == 9)
{
TileType::Generic
} else {
TileType::Tet(Tetromino::I)
};
line[Self::PLAYABLE_WIDTH] = Some(tile_ty);
}
*height_loaded += 1;
line
})
}
}