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 {
red: Option<Player>,
yellow: Option<Player>,
phase: GamePhase,
end: u8,
scores: [i32; 2],
@ -27,7 +25,7 @@ pub struct Game {
}
pub struct ThrowOutcome {
pub trajectory: Vec<(f32, f32, f32)>,
pub trajectory: Vec<StoneTrajectory>,
pub end_scored: Option<ServerMessage>,
pub state_message: ServerMessage,
pub game_over: Option<ServerMessage>,
@ -36,8 +34,6 @@ pub struct ThrowOutcome {
impl Game {
pub fn new() -> Self {
Self {
red: None,
yellow: None,
phase: GamePhase::Waiting,
end: 1,
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) {
if !self.can_start() {
return;
}
self.hammer = if rand::random() { Team::Red } else { Team::Yellow };
self.turn_team = self.hammer.other();
self.phase = GamePhase::Playing;
@ -114,17 +60,16 @@ impl Game {
self.active_stones.clear();
}
pub fn current_player_id(&self) -> Option<&str> {
let p = match self.turn_team {
Team::Red => self.red.as_ref()?,
Team::Yellow => self.yellow.as_ref()?,
};
Some(&p.id)
}
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> {
let current_id = self.current_player_id().ok_or("No current player")?;
if current_id != player_id {
pub fn handle_throw(
&mut self,
team: Team,
broom_x: f32,
broom_y: f32,
weight: u8,
curl: i8,
friction: f32,
) -> Result<Vec<StoneTrajectory>, String> {
if self.turn_team != team {
return Err("Not your turn".to_string());
}
if self.phase != GamePhase::Playing {
@ -138,7 +83,7 @@ impl Game {
}
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.phase = GamePhase::Simulating;
@ -147,19 +92,19 @@ impl Game {
Team::Yellow => self.stones_yellow = self.stones_yellow.saturating_sub(1),
}
Ok(path)
Ok(trajectory)
}
Outdated
Review

the game should be playable with a single player who has the capability to switch teams

the game should be playable with a single player who has the capability to switch teams
pub fn process_throw(
&mut self,
player_id: &str,
team: Team,
broom_x: f32,
broom_y: f32,
weight: u8,
curl: i8,
friction: f32,
) -> 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();
let end_scored = self.take_last_end_scored();
@ -223,7 +168,7 @@ impl Game {
Team::Red => 0,
Team::Yellow => 1,
};
self.scores[team_idx] += points as i32;
self.scores[team_idx] += points;
self.hammer = team.other();
} else {
points = 0;
@ -315,29 +260,36 @@ mod tests {
use super::*;
#[test]
fn prefers_red_when_requested_and_free() {
let mut game = Game::new();
assert_eq!(game.add_player("p1".into(), Some(Team::Red)), Some(Team::Red));
fn starts_in_waiting_phase() {
let game = Game::new();
assert!(matches!(game.phase, GamePhase::Waiting));
}
#[test]
fn prefers_yellow_when_requested_and_free() {
fn starts_when_called() {
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]
fn falls_back_when_preferred_taken() {
fn rejects_throw_for_wrong_team() {
let mut game = Game::new();
assert_eq!(game.add_player("p1".into(), Some(Team::Red)), Some(Team::Red));
assert_eq!(game.add_player("p2".into(), Some(Team::Red)), Some(Team::Yellow));
game.start();
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]
fn legacy_order_without_preference() {
fn accepts_throw_for_turn_team() {
let mut game = Game::new();
assert_eq!(game.add_player("p1".into(), None), Some(Team::Red));
assert_eq!(game.add_player("p2".into(), None), Some(Team::Yellow));
assert_eq!(game.add_player("p3".into(), None), None);
game.start();
let turn = game.turn_team;
let result = game.handle_throw(turn, 0.5, 38.7, 7, 1, 1.0);
assert!(result.is_ok());
}
}