configmaxxing

This commit is contained in:
Theis Pieter Hollebeek 2025-03-03 21:59:04 +01:00
parent 414ac1ed4c
commit 22702ae8a4
4 changed files with 382 additions and 42 deletions

55
Cargo.lock generated
View File

@ -610,6 +610,9 @@ dependencies = [
"rand",
"rodio",
"sdl2",
"serde",
"toml",
"xdg",
]
[[package]]
@ -669,6 +672,35 @@ dependencies = [
"version-compare",
]
[[package]]
name = "serde"
version = "1.0.218"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8dfc9d19bdbf6d17e22319da49161d5d0108e4188e8b680aef6299eed22df60"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.218"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f09503e191f4e797cb8aac08e9a4a4695c5edf6a2e70e376d961ddd5c969f82b"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_spanned"
version = "0.6.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1"
dependencies = [
"serde",
]
[[package]]
name = "shlex"
version = "1.3.0"
@ -770,11 +802,26 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]]
name = "toml"
version = "0.8.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd87a5cdd6ffab733b2f74bc4fd7ee5fff6634124999ac278c35fc78c6120148"
dependencies = [
"serde",
"serde_spanned",
"toml_datetime",
"toml_edit",
]
[[package]]
name = "toml_datetime"
version = "0.6.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41"
dependencies = [
"serde",
]
[[package]]
name = "toml_edit"
@ -783,6 +830,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17b4795ff5edd201c7cd6dca065ae59972ce77d1b80fa0a84d94950ece7d1474"
dependencies = [
"indexmap",
"serde",
"serde_spanned",
"toml_datetime",
"winnow",
]
@ -1094,6 +1143,12 @@ dependencies = [
"bitflags 2.9.0",
]
[[package]]
name = "xdg"
version = "2.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "213b7324336b53d2414b2db8537e56544d981803139155afa84f76eeebb7a546"
[[package]]
name = "zerocopy"
version = "0.7.35"

View File

@ -6,6 +6,9 @@ edition = "2021"
[dependencies]
rand = "0.9.0"
rodio = "0.20.1"
serde = { version = "1.0.218", features = ["serde_derive"] }
toml = "0.8.20"
xdg = "2.5.2"
[dependencies.sdl2]
version = "0.37.0"

View File

@ -6,6 +6,8 @@ use sdl2::pixels::Color;
use sdl2::rect::Rect;
use sdl2::render::{Texture, TextureCreator, WindowCanvas};
use sdl2::ttf::Sdl2TtfContext;
use serde::{Deserialize, Serialize};
use std::fs;
use std::time::Duration;
use super::audio::{self};
@ -111,11 +113,228 @@ impl UiCtx<String> for SdlUiCtx {
}
}
#[derive(Debug, PartialEq, Serialize, Deserialize)]
#[serde(tag = "key")]
enum Key {
Zero,
One,
Two,
Three,
Four,
Five,
Six,
Seven,
Eight,
Nine,
A,
B,
C,
D,
E,
F,
G,
H,
I,
J,
K,
L,
M,
N,
O,
P,
Q,
R,
S,
T,
U,
V,
W,
X,
Y,
Z,
Up,
Down,
Left,
Right,
Enter,
Backspace,
Space,
}
impl Key {
fn from_sdl_keycode(keycode: Keycode) -> Option<Key> {
let v = match keycode {
Keycode::Num0 | Keycode::Kp0 => Key::Zero,
Keycode::Num1 | Keycode::Kp1 => Key::One,
Keycode::Num2 | Keycode::Kp2 => Key::Two,
Keycode::Num3 | Keycode::Kp3 => Key::Three,
Keycode::Num4 | Keycode::Kp4 => Key::Four,
Keycode::Num5 | Keycode::Kp5 => Key::Five,
Keycode::Num6 | Keycode::Kp6 => Key::Six,
Keycode::Num7 | Keycode::Kp7 => Key::Seven,
Keycode::Num8 | Keycode::Kp8 => Key::Eight,
Keycode::Num9 | Keycode::Kp9 => Key::Nine,
Keycode::A => Key::A,
Keycode::B => Key::B,
Keycode::C => Key::C,
Keycode::D => Key::D,
Keycode::E => Key::E,
Keycode::F => Key::F,
Keycode::G => Key::G,
Keycode::H => Key::H,
Keycode::I => Key::I,
Keycode::J => Key::J,
Keycode::K => Key::K,
Keycode::L => Key::L,
Keycode::M => Key::M,
Keycode::N => Key::N,
Keycode::O => Key::O,
Keycode::P => Key::P,
Keycode::Q => Key::Q,
Keycode::R => Key::R,
Keycode::S => Key::S,
Keycode::T => Key::T,
Keycode::U => Key::U,
Keycode::V => Key::V,
Keycode::W => Key::W,
Keycode::X => Key::X,
Keycode::Y => Key::Y,
Keycode::Z => Key::Z,
Keycode::Up => Key::Up,
Keycode::Down => Key::Down,
Keycode::Left => Key::Left,
Keycode::Right => Key::Right,
Keycode::Return => Key::Enter,
Keycode::Backspace => Key::Backspace,
Keycode::Space => Key::Space,
_ => return None,
};
Some(v)
}
}
impl std::fmt::Display for Key {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let val = match self {
Key::Zero => "0",
Key::One => "1",
Key::Two => "2",
Key::Three => "3",
Key::Four => "4",
Key::Five => "5",
Key::Six => "6",
Key::Seven => "7",
Key::Eight => "8",
Key::Nine => "9",
Key::A => "A",
Key::B => "B",
Key::C => "C",
Key::D => "D",
Key::E => "E",
Key::F => "F",
Key::G => "G",
Key::H => "H",
Key::I => "I",
Key::J => "J",
Key::K => "K",
Key::L => "L",
Key::M => "M",
Key::N => "N",
Key::O => "O",
Key::P => "P",
Key::Q => "Q",
Key::R => "R",
Key::S => "S",
Key::T => "T",
Key::U => "U",
Key::V => "V",
Key::W => "W",
Key::X => "X",
Key::Y => "Y",
Key::Z => "Z",
Key::Up => "Up",
Key::Down => "Down",
Key::Left => "Left",
Key::Right => "Right",
Key::Enter => "Enter",
Key::Backspace => "Backspace",
Key::Space => "Space",
};
write!(f, "{val}")
}
}
#[derive(Debug, Serialize, Deserialize)]
struct Config {
reimtris1_feature_parity: bool,
restart: Vec<Key>,
left: Vec<Key>,
right: Vec<Key>,
rotate_cw: Vec<Key>,
rotate_ccw: Vec<Key>,
soft_drop: Vec<Key>,
hard_drop: Vec<Key>,
swap: Vec<Key>,
pause: Vec<Key>,
toggle_mute: Vec<Key>,
}
impl Default for Config {
fn default() -> Self {
Self {
reimtris1_feature_parity: false,
restart: vec![Key::Enter, Key::Space],
left: vec![Key::Left],
right: vec![Key::Right],
rotate_cw: vec![Key::X],
rotate_ccw: vec![Key::Z],
soft_drop: vec![Key::Down],
hard_drop: vec![Key::Space],
swap: vec![Key::C],
pause: vec![Key::P],
toggle_mute: vec![Key::M],
}
}
}
fn config_from_file<P: AsRef<std::path::Path>>(path: P) -> Result<Config, String> {
let Some(config) = fs::read_to_string(path.as_ref()).ok() else {
let config = Config::default();
{
println!("could not get config! attempting to create default...");
let config = toml::to_string(&config).map_err(|err| err.to_string())?;
fs::write(path, config).map_err(|err| err.to_string())?;
}
return Ok(config);
};
let Some(config) = toml::from_str(&config).ok() else {
println!("womp womp, config contains an invalid config, attempting to reset to default...");
let config = Config::default();
{
let config = toml::to_string(&config).map_err(|err| err.to_string())?;
fs::write(path, config).map_err(|err| err.to_string())?;
}
return Ok(config);
};
Ok(config)
}
pub fn start_game() -> Result<(), String> {
let mut game = Game::new();
let mut actions = ActionsHeld::new();
let mut paused = false;
let config = {
let base = xdg::BaseDirectories::new().map_err(|err| err.to_string())?;
let path = base
.place_config_file("reimtris2/config.toml")
.map_err(|err| err.to_string())?;
let config = config_from_file(path)?;
config
};
const FONT: &'static str = "resources/josenfin_sans_regular.ttf";
let audio_thread = audio::audio_thread();
let sdl_context = sdl2::init()?;
@ -125,6 +344,7 @@ pub fn start_game() -> Result<(), String> {
let window = video_subsystem
.window("reimtris2", 1000, 800)
.resizable()
.maximized()
.position_centered()
.build()
.unwrap();
@ -144,50 +364,77 @@ pub fn start_game() -> Result<(), String> {
keycode: Some(Keycode::Escape),
..
} => break 'running Ok(()),
Event::MouseMotion { .. } => {
if config.reimtris1_feature_parity {
break 'running Ok(());
}
}
Event::KeyDown {
keycode: Some(keycode),
..
} => {
let keycode = match keycode {
Keycode::Return if !paused && game.game_over => {
game = Game::new();
continue;
}
Keycode::M => {
audio_thread.send(audio::Command::ToggleMuted).unwrap();
continue;
}
Keycode::P => {
paused = !paused;
continue;
}
Keycode::Left | Keycode::A => Action::Left,
Keycode::Right | Keycode::D => Action::Right,
Keycode::Down | Keycode::S => Action::SoftDrop,
Keycode::Space => Action::HardDrop,
Keycode::Z => Action::RotateCcw,
Keycode::X => Action::RotateCw,
Keycode::C => Action::Swap,
_ => continue,
let Some(key) = Key::from_sdl_keycode(keycode) else {
continue;
};
actions.insert(keycode, game.ticks);
if config.pause.contains(&key) {
paused = !paused;
};
if config.restart.contains(&key) && !paused && game.game_over {
game = Game::new();
}
if config.toggle_mute.contains(&key) {
audio_thread.send(audio::Command::ToggleMuted).unwrap();
}
if config.left.contains(&key) {
actions.insert(Action::Left, game.ticks);
}
if config.right.contains(&key) {
actions.insert(Action::Right, game.ticks);
}
if config.soft_drop.contains(&key) {
actions.insert(Action::SoftDrop, game.ticks);
}
if config.hard_drop.contains(&key) {
actions.insert(Action::HardDrop, game.ticks);
}
if config.rotate_cw.contains(&key) {
actions.insert(Action::RotateCw, game.ticks);
}
if config.rotate_ccw.contains(&key) {
actions.insert(Action::RotateCcw, game.ticks);
}
if config.swap.contains(&key) {
actions.insert(Action::Swap, game.ticks);
}
}
Event::KeyUp {
keycode: Some(keycode),
..
} => {
let keycode = match keycode {
Keycode::Left | Keycode::A => Action::Left,
Keycode::Right | Keycode::D => Action::Right,
Keycode::Down | Keycode::S => Action::SoftDrop,
Keycode::Space => Action::HardDrop,
Keycode::Z => Action::RotateCcw,
Keycode::X => Action::RotateCw,
Keycode::C => Action::Swap,
_ => continue,
let Some(key) = Key::from_sdl_keycode(keycode) else {
continue;
};
actions.remove(&keycode);
if config.left.contains(&key) {
actions.remove(&Action::Left);
}
if config.right.contains(&key) {
actions.remove(&Action::Right);
}
if config.soft_drop.contains(&key) {
actions.remove(&Action::SoftDrop);
}
if config.hard_drop.contains(&key) {
actions.remove(&Action::HardDrop);
}
if config.rotate_cw.contains(&key) {
actions.remove(&Action::RotateCw);
}
if config.rotate_ccw.contains(&key) {
actions.remove(&Action::RotateCcw);
}
if config.swap.contains(&key) {
actions.remove(&Action::Swap);
}
}
_ => {}
}
@ -195,17 +442,28 @@ pub fn start_game() -> Result<(), String> {
ctx.draw_board(&game.board, &game.current_tetromino)?;
ctx.draw_bag(&game.held_tetromino, &game.next_tetrominos)?;
ctx.draw_score(FONT, &game.score)?;
if paused {
ctx.draw_important_text(
"resources/josenfin_sans_regular.ttf",
"game paused o_o... press [p] to unpause !!",
)?;
let keys = config
.pause
.iter()
.map(|v| v.to_string().to_lowercase())
.collect::<Vec<_>>()
.join(" | ");
let paused = format!("game paused o_o... press [{keys}] to unpause !!");
ctx.draw_important_text(FONT, paused)?;
} else if game.game_over {
ctx.draw_important_text(
"resources/josenfin_sans_regular.ttf",
"game over T_T... press [enter] 2 restart :D",
)?;
let keys = config
.restart
.iter()
.map(|v| v.to_string().to_lowercase())
.collect::<Vec<_>>()
.join(" | ");
let game_over = format!("game over T_T... press [{keys}] 2 restart :D");
ctx.draw_important_text(FONT, game_over)?;
} else {
let effects = game.step(&actions);
effects.into_iter().for_each(|effect| {

View File

@ -1,6 +1,6 @@
use crate::{
board::Board,
game::CurrentTetromino,
game::{CurrentTetromino, Score},
tetromino::{Direction, Tetromino},
};
@ -180,6 +180,30 @@ pub trait GameUiCtx<Err>: UiCtx<Err> {
Ok(())
}
fn draw_score<P: AsRef<std::path::Path>>(&mut self, font: P, score: &Score) -> Result<(), Err> {
let (win_width, win_height) = self.window_size()?;
let board_width = self.tile_size() * Board::WIDTH as i32;
let board_height = self.tile_size() * Board::HEIGHT as i32;
let x = center(board_width, win_width) + board_width + self.tile_size();
let y = center(board_height, win_height) + self.tile_size();
let level = format!("level: {}", score.level);
let lines = format!("lines: {}", score.lines);
let points = format!("points: {}", score.points);
let level_size = self.text_size(font.as_ref(), &level)?;
let lines_size = self.text_size(font.as_ref(), &lines)?;
let points_size = self.text_size(font.as_ref(), &points)?;
self.fill_text(font.as_ref(), level, x, y, level_size.0, level_size.1)?;
let y = y + level_size.1 + self.tile_size();
self.fill_text(font.as_ref(), lines, x, y, lines_size.0, lines_size.1)?;
let y = y + lines_size.1 + self.tile_size();
self.fill_text(font.as_ref(), points, x, y, points_size.0, points_size.1)?;
Ok(())
}
fn draw_board(&mut self, board: &Board, current: &CurrentTetromino) -> Result<(), Err> {
let (win_width, win_height) = self.window_size()?;
self.outline_rect(