forked from eros/curltastic
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:
parent
27e524a418
commit
1f673b4eba
@ -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),
|
|
||||||
}
|
fn slot_for(&mut self, team: &Team) -> &mut Option<Player> {
|
||||||
Some(t)
|
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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user