use std::num::{NonZeroU32, NonZeroUsize};
use falling_tetromino_engine::{ExtDuration, Game, GameBuilder, Modifier};
pub mod ascent;
pub mod cheese;
pub mod combo_board;
pub mod puzzle;
pub fn reconstruct_build_modded<'a>(
builder: &'a GameBuilder,
mod_descriptors: impl IntoIterator<Item = &'a str>,
) -> Result<(Game, Vec<String>), String> {
let mut compounding_mods: Vec<Modifier> = Vec::new();
#[allow(clippy::type_complexity)]
let mut building_mod: Option<(&str, Box<dyn Fn(&'a GameBuilder) -> Game>)> = None;
let mut store_building_mod = |mod_id, build| {
if let Some((other_id, _)) = building_mod {
return Err(format!("incompatible mods: {other_id:?} + {mod_id:?}"));
}
building_mod.replace((mod_id, build));
Ok(())
};
let mut unrecognized_mod_descriptors = Vec::new();
fn get_mod_args<'de, T: serde::Deserialize<'de>>(
lines: &mut std::str::Lines<'de>,
mod_id: &str,
) -> Result<T, String> {
let Some(mod_args_str) = lines.next() else {
return Err(format!("mod args missing for {mod_id:?}"));
};
let args = match serde_json::from_str(mod_args_str) {
Ok(args) => args,
Err(e) => {
return Err(format!(
"mod args parse error for {mod_id}: {mod_args_str} ({e}"
))
}
};
Ok(args)
}
for mod_descriptor in mod_descriptors {
let mut lines = mod_descriptor.lines();
let mod_id = lines.next().unwrap_or("");
if mod_id == puzzle::MOD_ID {
let build = Box::new(puzzle::build);
store_building_mod(mod_id, build)?;
} else if mod_id == ascent::MOD_ID {
let build = Box::new(ascent::build);
store_building_mod(mod_id, build)?;
} else if mod_id == cheese::MOD_ID {
let (linelimit, cheese_tiles_per_line, fall_delay) =
get_mod_args::<(Option<NonZeroU32>, NonZeroUsize, ExtDuration)>(
&mut lines, mod_id,
)?;
let build = Box::new(move |builder| {
cheese::build(builder, linelimit, cheese_tiles_per_line, fall_delay)
});
store_building_mod(mod_id, build)?;
} else if mod_id == combo_board::MOD_ID {
let linelimit = get_mod_args::<u16>(&mut lines, mod_id)?;
let modifier = combo_board::modifier(linelimit);
compounding_mods.push(modifier);
} else if mod_id == print_recency_tet_gen_stats::MOD_ID {
let modifier = print_recency_tet_gen_stats::modifier();
compounding_mods.push(modifier);
} else if mod_id == print_msgs::MOD_ID {
let messages = get_mod_args::<Vec<String>>(&mut lines, mod_id)?;
let modifier = print_msgs::modifier(messages);
compounding_mods.push(modifier);
} else if mod_id == custom_start_board::MOD_ID {
let encoded_board = get_mod_args::<String>(&mut lines, mod_id)?;
let modifier = custom_start_board::modifier(&encoded_board);
compounding_mods.push(modifier);
} else {
unrecognized_mod_descriptors.push(mod_id.to_owned());
}
}
let mut game = if let Some((_, build)) = building_mod {
build(builder)
} else {
builder.build()
};
game.modifiers.extend(compounding_mods);
Ok((game, unrecognized_mod_descriptors))
}
pub mod custom_start_board {
use falling_tetromino_engine::Modifier;
pub const MOD_ID: &str = "custom_start_board";
pub fn modifier(encoded_board: &str) -> Modifier {
let init_board = crate::application::NewGameSettings::decode_board(encoded_board);
let mut init = false;
Modifier {
descriptor: format!(
"{MOD_ID}\n{}",
serde_json::to_string(&encoded_board).unwrap()
),
mod_function: Box::new(move |_point, _config, _init_vals, state, _phase, _msgs| {
if !init {
state.board = init_board;
init = true;
}
}),
}
}
}
pub mod print_msgs {
use falling_tetromino_engine::{Feedback, Modifier};
pub const MOD_ID: &str = "print_msgs";
pub fn modifier(messages: Vec<String>) -> Modifier {
Modifier {
descriptor: format!("{MOD_ID}\n{}", serde_json::to_string(&messages).unwrap()),
mod_function: Box::new({
let mut init = false;
move |_point, _config, _init_vals, state, _phase, msgs| {
if init {
return;
} else {
init = true;
}
for msg in messages.iter() {
msgs.push((state.time, Feedback::Text(msg.to_owned())));
}
}
}),
}
}
}
#[allow(dead_code)]
pub mod print_recency_tet_gen_stats {
use falling_tetromino_engine::{
Feedback, Modifier, Tetromino, TetrominoGenerator, UpdatePoint,
};
pub const MOD_ID: &str = "print_recency_tet_gen_stats";
pub fn modifier() -> Modifier {
Modifier {
descriptor: MOD_ID.to_owned(),
mod_function: Box::new(|point, _config, _init_vals, state, _phase, msgs| {
if !matches!(point, UpdatePoint::PieceSpawned) {
return;
}
let TetrominoGenerator::Recency {
last_generated,
snap: _,
} = state.piece_generator
else {
return;
};
let mut pieces_played_strs = Tetromino::VARIANTS;
pieces_played_strs.sort_by_key(|&tet| last_generated[tet as usize]);
let [o, i, s, z, t, l, j] = state.pieces_locked;
let str_piece_tallies = format!("{o}o {i}i {s}s {z}z {t}t {l}l {j}j");
let str_piece_likelihood = pieces_played_strs
.map(|tet| {
format!(
"{tet:?}{}{}{}",
last_generated[tet as usize],
"█".repeat(
(last_generated[tet as usize] * last_generated[tet as usize])
as usize
/ 8
),
[" ", "▏", "▎", "▍", "▌", "▋", "▊", "▉"][(last_generated[tet as usize]
* last_generated[tet as usize])
as usize
% 8]
)
.to_ascii_lowercase()
})
.join("");
msgs.push((state.time, Feedback::Text("".to_owned())));
msgs.push((state.time, Feedback::Text(str_piece_likelihood)));
msgs.push((state.time, Feedback::Text(str_piece_tallies)));
}),
}
}
}