jasonlooked #1

Merged
eros merged 21 commits from jasonlooked into main 2026-07-11 10:13:09 -07:00
Showing only changes of commit 0e08e21c9b - Show all commits

View File

@ -12,8 +12,6 @@ pub enum GamePhase {
} }
pub struct Game { pub struct Game {
red: Option<Player>,
yellow: Option<Player>,
phase: GamePhase, phase: GamePhase,
end: u8, end: u8,
scores: [i32; 2], scores: [i32; 2],
@ -27,7 +25,7 @@ pub struct Game {
} }
pub struct ThrowOutcome { pub struct ThrowOutcome {
pub trajectory: Vec<(f32, f32, f32)>, pub trajectory: Vec<StoneTrajectory>,
pub end_scored: Option<ServerMessage>, pub end_scored: Option<ServerMessage>,
pub state_message: ServerMessage, pub state_message: ServerMessage,
pub game_over: Option<ServerMessage>, pub game_over: Option<ServerMessage>,
@ -36,8 +34,6 @@ pub struct ThrowOutcome {
impl Game { impl Game {
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
red: None,
yellow: None,
phase: GamePhase::Waiting, phase: GamePhase::Waiting,
end: 1, end: 1,
scores: [0, 0], scores: [0, 0],
@ -51,57 +47,7 @@ impl Game {
} }
} }
Review

on the frontend, use a skeumorphic HUD to display how many stones are left. for instance, at the start of the game, show 2 rows of 8 stones. a stone should be removed from the hud when it is thrown.

on the frontend, use a skeumorphic HUD to display how many stones are left. for instance, at the start of the game, show 2 rows of 8 stones. a stone should be removed from the hud when it is thrown.
pub fn add_player(&mut self, id: String, preferred: Option<Team>) -> Option<Team> {
let team = preferred
.filter(|t| self.slot_for(t).is_none())
.or_else(|| self.first_open_team())?;
let player = Player { id, team, connected: true };
*self.slot_for(&team) = Some(player);
Some(team)
}
fn slot_for(&mut self, team: &Team) -> &mut Option<Player> {
match team {
Team::Red => &mut self.red,
Team::Yellow => &mut self.yellow,
}
}
fn first_open_team(&self) -> Option<Team> {
if self.red.is_none() {
Some(Team::Red)
} else if self.yellow.is_none() {
Some(Team::Yellow)
} else {
None
}
}
pub fn remove_player(&mut self, id: &str) -> Option<Team> {
if let Some(ref p) = self.red {
if p.id == id {
self.red = None;
return Some(Team::Red);
}
}
if let Some(ref p) = self.yellow {
if p.id == id {
self.yellow = None;
return Some(Team::Yellow);
}
}
None
}
pub fn can_start(&self) -> bool {
self.red.is_some() && self.yellow.is_some()
}
pub fn start(&mut self) { pub fn start(&mut self) {
if !self.can_start() {
return;
}
self.hammer = if rand::random() { Team::Red } else { Team::Yellow }; self.hammer = if rand::random() { Team::Red } else { Team::Yellow };
self.turn_team = self.hammer.other(); self.turn_team = self.hammer.other();
self.phase = GamePhase::Playing; self.phase = GamePhase::Playing;
@ -114,17 +60,16 @@ impl Game {
self.active_stones.clear(); self.active_stones.clear();
} }
pub fn current_player_id(&self) -> Option<&str> { pub fn handle_throw(
let p = match self.turn_team { &mut self,
Team::Red => self.red.as_ref()?, team: Team,
Team::Yellow => self.yellow.as_ref()?, broom_x: f32,
}; broom_y: f32,
Some(&p.id) weight: u8,
} curl: i8,
friction: f32,
pub fn handle_throw(&mut self, player_id: &str, broom_x: f32, broom_y: f32, weight: u8, curl: i8, friction: f32) -> Result<Vec<(f32, f32, f32)>, String> { ) -> Result<Vec<StoneTrajectory>, String> {
let current_id = self.current_player_id().ok_or("No current player")?; if self.turn_team != team {
if current_id != player_id {
return Err("Not your turn".to_string()); return Err("Not your turn".to_string());
} }
if self.phase != GamePhase::Playing { if self.phase != GamePhase::Playing {
@ -138,7 +83,7 @@ impl Game {
} }
self.active_stones.clear(); self.active_stones.clear();
let path = self.physics.throw(self.turn_team, broom_x, broom_y, weight, curl, friction)?; let trajectory = self.physics.throw(self.turn_team, broom_x, broom_y, weight, curl, friction)?;
self.active_stones = self.physics.current_stones(); self.active_stones = self.physics.current_stones();
self.phase = GamePhase::Simulating; self.phase = GamePhase::Simulating;
@ -147,19 +92,19 @@ impl Game {
Team::Yellow => self.stones_yellow = self.stones_yellow.saturating_sub(1), Team::Yellow => self.stones_yellow = self.stones_yellow.saturating_sub(1),
} }
Ok(path) Ok(trajectory)
} }
pub fn process_throw( pub fn process_throw(
&mut self, &mut self,
player_id: &str, team: Team,
broom_x: f32, broom_x: f32,
broom_y: f32, broom_y: f32,
weight: u8, weight: u8,
curl: i8, curl: i8,
friction: f32, friction: f32,
) -> Result<ThrowOutcome, String> { ) -> Result<ThrowOutcome, String> {
let trajectory = self.handle_throw(player_id, broom_x, broom_y, weight, curl, friction)?; let trajectory = self.handle_throw(team, broom_x, broom_y, weight, curl, friction)?;
self.finish_simulation(); self.finish_simulation();
let end_scored = self.take_last_end_scored(); let end_scored = self.take_last_end_scored();
@ -223,7 +168,7 @@ impl Game {
Team::Red => 0, Team::Red => 0,
Team::Yellow => 1, Team::Yellow => 1,
}; };
self.scores[team_idx] += points as i32; self.scores[team_idx] += points;
self.hammer = team.other(); self.hammer = team.other();
} else { } else {
points = 0; points = 0;
@ -315,29 +260,36 @@ mod tests {
use super::*; use super::*;
#[test] #[test]
fn prefers_red_when_requested_and_free() { fn starts_in_waiting_phase() {
let mut game = Game::new(); let game = Game::new();
assert_eq!(game.add_player("p1".into(), Some(Team::Red)), Some(Team::Red)); assert!(matches!(game.phase, GamePhase::Waiting));
} }
#[test] #[test]
fn prefers_yellow_when_requested_and_free() { fn starts_when_called() {
let mut game = Game::new(); let mut game = Game::new();
assert_eq!(game.add_player("p1".into(), Some(Team::Yellow)), Some(Team::Yellow)); game.start();
assert!(matches!(game.phase, GamePhase::Playing));
assert_eq!(game.end, 1);
assert_eq!(game.scores, [0, 0]);
} }
#[test] #[test]
fn falls_back_when_preferred_taken() { fn rejects_throw_for_wrong_team() {
let mut game = Game::new(); let mut game = Game::new();
assert_eq!(game.add_player("p1".into(), Some(Team::Red)), Some(Team::Red)); game.start();
assert_eq!(game.add_player("p2".into(), Some(Team::Red)), Some(Team::Yellow)); let turn = game.turn_team;
let wrong = turn.other();
let result = game.handle_throw(wrong, 0.5, 38.7, 7, 1, 1.0);
assert!(result.is_err());
} }
#[test] #[test]
fn legacy_order_without_preference() { fn accepts_throw_for_turn_team() {
let mut game = Game::new(); let mut game = Game::new();
assert_eq!(game.add_player("p1".into(), None), Some(Team::Red)); game.start();
assert_eq!(game.add_player("p2".into(), None), Some(Team::Yellow)); let turn = game.turn_team;
assert_eq!(game.add_player("p3".into(), None), None); let result = game.handle_throw(turn, 0.5, 38.7, 7, 1, 1.0);
assert!(result.is_ok());
} }
} }