refactor(backend): encapsulate throw lifecycle and clean dead code in game.rs

Add Game::process_throw to centralize throw→simulation→scoring→state flow.

Remove dead room_tx field, simplify add_player, and rename end_ends_or_continue.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
eros 2026-06-24 10:46:12 -07:00
parent 27e524a418
commit 1f673b4eba

View File

@ -24,7 +24,13 @@ pub struct Game {
stones_red: u8, stones_red: u8,
stones_yellow: u8, stones_yellow: u8,
last_end_scored: Option<(u8, i32, Option<Team>)>, last_end_scored: Option<(u8, i32, Option<Team>)>,
pub room_tx: Option<tokio::sync::broadcast::Sender<ServerMessage>>, }
pub struct ThrowOutcome {
pub trajectory: Vec<(f32, f32, f32)>,
pub end_scored: Option<ServerMessage>,
pub state_message: ServerMessage,
pub game_over: Option<ServerMessage>,
} }
impl Game { impl Game {
@ -42,43 +48,31 @@ impl Game {
stones_red: STONES_PER_TEAM, stones_red: STONES_PER_TEAM,
stones_yellow: STONES_PER_TEAM, stones_yellow: STONES_PER_TEAM,
last_end_scored: None, last_end_scored: None,
room_tx: None,
} }
} }
pub fn add_player(&mut self, id: String, preferred: Option<Team>) -> Option<Team> { pub fn add_player(&mut self, id: String, preferred: Option<Team>) -> Option<Team> {
let team = match preferred { let team = preferred
Some(Team::Red) if self.red.is_none() => Some(Team::Red), .filter(|t| self.slot_for(t).is_none())
Some(Team::Yellow) if self.yellow.is_none() => Some(Team::Yellow), .or_else(|| self.first_open_team())?;
Some(_) => {
// Preferred is taken; fall back to the other free team.
if self.red.is_none() {
Some(Team::Red)
} else if self.yellow.is_none() {
Some(Team::Yellow)
} else {
None
}
}
None => {
// Legacy first-free logic.
if self.red.is_none() {
Some(Team::Red)
} else if self.yellow.is_none() {
Some(Team::Yellow)
} else {
None
}
}
};
if let Some(t) = team { let player = Player { id, team, connected: true };
let player = Player { id, team: t, connected: true }; *self.slot_for(&team) = Some(player);
match t { Some(team)
Team::Red => self.red = Some(player),
Team::Yellow => self.yellow = Some(player),
} }
Some(t)
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 { } else {
None None
} }
@ -171,6 +165,34 @@ impl Game {
Ok(path) Ok(path)
} }
pub fn process_throw(
&mut self,
player_id: &str,
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)?;
self.finish_simulation();
let end_scored = self.take_last_end_scored();
let state_message = self.game_state_message();
let game_over = if self.phase == GamePhase::GameComplete {
Some(self.game_over_message())
} else {
None
};
Ok(ThrowOutcome {
trajectory,
end_scored,
state_message,
game_over,
})
}
pub fn finish_simulation(&mut self) { pub fn finish_simulation(&mut self) {
if self.phase != GamePhase::Simulating { if self.phase != GamePhase::Simulating {
return; return;
@ -229,10 +251,10 @@ impl Game {
self.last_end_scored = Some((self.end, points, scoring_team)); self.last_end_scored = Some((self.end, points, scoring_team));
self.phase = GamePhase::EndComplete; self.phase = GamePhase::EndComplete;
self.end_ends_or_continue(points, scoring_team); self.advance_end_or_finish();
} }
fn end_ends_or_continue(&mut self, _points: i32, _scoring_team: Option<Team>) { fn advance_end_or_finish(&mut self) {
let tied = self.scores[0] == self.scores[1]; let tied = self.scores[0] == self.scores[1];
let after_regulation = self.end >= ENDS; let after_regulation = self.end >= ENDS;
@ -241,10 +263,6 @@ impl Game {
return; return;
} }
if after_regulation && tied {
// Extra end
}
self.end += 1; self.end += 1;
self.stones_red = STONES_PER_TEAM; self.stones_red = STONES_PER_TEAM;
self.stones_yellow = STONES_PER_TEAM; self.stones_yellow = STONES_PER_TEAM;
@ -311,13 +329,11 @@ pub struct Room {
impl Room { impl Room {
pub fn new(id: &str) -> Self { pub fn new(id: &str) -> Self {
let (tx, _) = tokio::sync::broadcast::channel(256); let (tx, _) = tokio::sync::broadcast::channel(256);
let mut room = Self { Self {
id: id.to_string(), id: id.to_string(),
game: Game::new(), game: Game::new(),
tx, tx,
}; }
room.game.room_tx = Some(room.tx.clone());
room
} }
} }