From 672f9e09bf841cfca167b672bbf9c318c4bfca08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jana=20D=C3=B6nszelmann?= Date: Wed, 10 Sep 2025 20:00:50 -0700 Subject: [PATCH] gamestate Co-authored-by: Julia Ryan --- .gitignore | 1 + Cargo.lock | 7 ++ Cargo.toml | 6 ++ src/gamestate.rs | 211 +++++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 5 ++ 5 files changed, 230 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 src/gamestate.rs create mode 100644 src/main.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..f9e4843 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "quoridor" +version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..843d6de --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "quoridor" +version = "0.1.0" +edition = "2024" + +[dependencies] diff --git a/src/gamestate.rs b/src/gamestate.rs new file mode 100644 index 0000000..b56fe93 --- /dev/null +++ b/src/gamestate.rs @@ -0,0 +1,211 @@ +use std::fmt::Display; + +#[derive(Clone, Copy, PartialEq)] +struct PlayerState { + xy: u8, + walls_left: u8, +} + +impl PlayerState { + pub const P1_START: Self = Self::new(4, 0, 9); + pub const P2_START: Self = Self::new(4, 8, 9); + + pub const fn new(x: u8, y: u8, walls_left: u8) -> Self { + let mut res = Self { xy: 0, walls_left }; + + res.set_x(x); + res.set_y(y); + + res + } +} + +impl PlayerState { + pub const fn x(&self) -> u8 { + self.xy & 0b00001111 + } + + pub const fn y(&self) -> u8 { + (self.xy & 0b11110000) >> 4 + } + + pub const fn set_x(&mut self, x: u8) { + // zero out x + self.xy &= 0b11110000; + // write the x part + self.xy |= x & 0b00001111; + } + + pub const fn set_y(&mut self, y: u8) { + // zero out y + self.xy &= 0b00001111; + // write the y part + self.xy |= (y & 0b00001111) << 4; + } +} + +#[derive(Copy, Clone)] +struct WallState { + verticals: [u8; 9], + horizontals: [u8; 9], +} + +impl WallState { + #[inline] + pub fn block_cleaned_hori(&mut self, byte_idx: u8, bit: u8) { + self.horizontals[byte_idx as usize] |= 1 << bit; + } + #[inline] + pub fn block_cleaned_verti(&mut self, byte_idx: u8, bit: u8) { + self.verticals[byte_idx as usize] |= 1 << bit; + } + + #[inline] + pub fn can_walk_between_cleaned_hori(&self, byte_idx: u8, bit: u8) -> bool { + (self.horizontals[byte_idx as usize] >> bit) & 1 != 0 + } + #[inline] + pub fn can_walk_between_cleaned_verti(&self, byte_idx: u8, bit: u8) -> bool { + (self.verticals[byte_idx as usize] >> bit) & 1 != 0 + } + + pub fn block(&mut self, from_x: u8, from_y: u8, to_x: u8, to_y: u8) { + match (from_x.wrapping_sub(to_x), from_y.wrapping_sub(to_y)) { + (1, 0) => self.block_cleaned_verti(to_y, to_x), + (0xff, 0) => self.block_cleaned_verti(from_y, from_x), + + (0, 1) => self.block_cleaned_hori(to_x, to_y), + (0, 0xff) => self.block_cleaned_hori(from_x, from_y), + _ => unreachable!(), + } + } + + pub fn can_walk_between(&self, from_x: u8, from_y: u8, to_x: u8, to_y: u8) -> bool { + !match (from_x.wrapping_sub(to_x), from_y.wrapping_sub(to_y)) { + (1, 0) => self.can_walk_between_cleaned_verti(to_y, to_x), + (0xff, 0) => self.can_walk_between_cleaned_verti(from_y, from_x), + + (0, 1) => self.can_walk_between_cleaned_hori(to_x, to_y), + (0, 0xff) => self.can_walk_between_cleaned_hori(from_x, from_y), + _ => unreachable!(), + } + } +} + +impl Default for WallState { + fn default() -> Self { + Self { + verticals: Default::default(), + horizontals: Default::default(), + } + } +} + +struct GameState { + p1: PlayerState, + p2: PlayerState, + walls: WallState, +} + +impl Default for GameState { + fn default() -> Self { + Self { + p1: PlayerState::P1_START, + p2: PlayerState::P2_START, + walls: Default::default(), + } + } +} + +/* +what we're going for: + -- -- -- +| | |P2| + -- -- -- +|P1| | | + -- -- -- +*/ +impl Display for GameState { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + writeln!( + f, + "P1: {}, P2: {}\n", + self.p1.walls_left, self.p2.walls_left + )?; + for y in 0..9 { + for x in 0..9 { + if y > 0 { + let wall = if !self.walls.can_walk_between(x, y - 1, x, y) { + ' ' + } else { + '-' + }; + write!(f, "{wall}{wall}")?; + } + if x != 8 { + write!(f, " ")?; + } + } + writeln!(f, "")?; + for x in 0..9 { + if x > 0 { + let wall = if !self.walls.can_walk_between(x - 1, y, x, y) { + ' ' + } else { + '|' + }; + write!(f, "{wall}")?; + } + let player = if self.p1.x() == x && self.p1.y() == y { + "P1" + } else if self.p2.x() == x && self.p2.y() == y { + "P2" + } else { + " " + }; + write!(f, "{player}")?; + } + writeln!(f, "")?; + } + writeln!(f, "")?; + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use crate::gamestate::{GameState, PlayerState, WallState}; + + #[test] + fn test_blocking() { + let mut w = WallState::default(); + + assert!(w.can_walk_between(0, 0, 1, 0)); + w.block(0, 0, 1, 0); + assert!(!w.can_walk_between(0, 0, 1, 0)); + + w.block(8, 7, 8, 8); + w.block(0, 8, 1, 8); + + assert!(w.can_walk_between(0, 0, 0, 1)); + w.block(0, 0, 0, 1); + assert!(!w.can_walk_between(0, 0, 0, 1)); + + assert!(w.can_walk_between(0, 7, 0, 8)); + w.block(0, 7, 0, 8); + assert!(!w.can_walk_between(0, 7, 0, 8)); + + assert!(w.can_walk_between(7, 0, 8, 0)); + w.block(7, 0, 8, 0); + assert!(!w.can_walk_between(7, 0, 8, 0)); + + println!( + "{}", + GameState { + p1: PlayerState::P1_START, + p2: PlayerState::P2_START, + walls: w, + } + ); + } +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..a71187c --- /dev/null +++ b/src/main.rs @@ -0,0 +1,5 @@ +mod gamestate; + +fn main() { + println!("Hello, world!"); +}