use crate::command::Command; use crate::consts::*; use std::collections::BTreeSet; use std::rc::Rc; #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)] pub enum GameStatus { Continue, PlayerOneWon, PlayerTwoWon, Draw, // Until I add score I guess } #[derive(Debug, Clone, Hash, PartialEq, Eq)] pub struct GameState { pub status: GameStatus, pub players: [Player; 2], pub obstacles: Rc>, pub powerup_oils: Rc>, pub powerup_boosts: Rc>, pub finish_lines: Rc>, } #[derive(Debug, Clone, Hash, PartialEq, Eq)] pub struct Player { pub position: Position, pub next_position: Position, pub speed: u16, pub boost_remaining: u8, pub oils: u16, pub boosts: u16, pub finished: bool, } #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)] pub struct Position { pub y: u8, pub x: u16, } impl GameState { pub fn update(&mut self, commands: [Command; 2]) { if self.status != GameStatus::Continue { return; } self.do_command(0, &commands[0]); self.do_command(1, &commands[1]); self.update_player_collisions(); self.update_player_travel(); self.status = if self.players[0].finished && self.players[1].finished { if self.players[0].speed > self.players[1].speed { GameStatus::PlayerOneWon } else if self.players[0].speed < self.players[1].speed { GameStatus::PlayerTwoWon } else { GameStatus::Draw } } else if self.players[0].finished { GameStatus::PlayerOneWon } else if self.players[1].finished { GameStatus::PlayerTwoWon } else { GameStatus::Continue }; } pub fn reset_players_to_start(&mut self) { self.players[0].position = Position { x: 1, y: 1 }; self.players[1].position = Position { x: 1, y: 4 }; for player in &mut self.players { player.speed = 5; player.boost_remaining = 0; player.oils = 0; player.boosts = 0; player.finished = false; } } fn do_command(&mut self, player_index: usize, command: &Command) { use Command::*; self.players[player_index].tick_boost(); match command { Nothing => {} Accelerate => self.players[player_index].accelerate(), Decelerate => self.players[player_index].decelerate(), TurnLeft => self.players[player_index].turn_left(), TurnRight => self.players[player_index].turn_right(), UseBoost => self.players[player_index].boost(), UseOil => { debug_assert!(self.players[player_index].oils > 0); self.players[player_index].oils = self.players[player_index].oils.saturating_sub(1); let player_position = self.players[player_index].position; let mut obstacles = (*self.obstacles).clone(); obstacles.insert(Position { x: player_position.x.saturating_sub(1), y: player_position.y, }); self.obstacles = Rc::new(obstacles); } } let turning = match command { TurnLeft | TurnRight => true, _ => false, }; self.players[player_index].set_next_position_x(turning); } fn update_player_collisions(&mut self) { let same_lanes_before = self.players[0].position.y == self.players[1].position.y; let same_lanes_after = self.players[0].next_position.y == self.players[1].next_position.y; let first_passing_second = self.players[0].position.x < self.players[1].position.x && self.players[0].next_position.x >= self.players[1].next_position.x; let second_passing_first = self.players[1].position.x < self.players[0].position.x && self.players[1].next_position.x >= self.players[0].next_position.x; let same_x_after = self.players[0].next_position.x == self.players[1].next_position.x; if same_lanes_before && same_lanes_after && first_passing_second { self.players[0].next_position.x = self.players[1].next_position.x.saturating_sub(1); } else if same_lanes_before && same_lanes_after && second_passing_first { self.players[1].next_position.x = self.players[0].next_position.x.saturating_sub(1); } else if same_lanes_after && same_x_after { self.players[0].bonked_while_changing_lanes(); self.players[1].bonked_while_changing_lanes(); } } fn update_player_travel(&mut self) { for player in &mut self.players { player.move_along( &self.obstacles, &self.powerup_oils, &self.powerup_boosts, &self.finish_lines, ); } } pub fn valid_moves(&self, player_index: usize) -> Vec { let player = &self.players[player_index]; let mut result = Vec::with_capacity(7); result.push(Command::Nothing); if player.speed < SPEED_4 { result.push(Command::Accelerate); } if player.speed > SPEED_0 { result.push(Command::Decelerate); } if player.position.y > MIN_Y { result.push(Command::TurnLeft); } if player.position.y < MAX_Y - 1 { result.push(Command::TurnRight); } if player.boosts > 0 { result.push(Command::UseBoost); } if false && player.oils > 0 { result.push(Command::UseOil); } result } } impl Player { fn accelerate(&mut self) { self.speed = match self.speed { i if i < SPEED_1 => SPEED_1, i if i < SPEED_2 => SPEED_2, i if i < SPEED_3 => SPEED_3, _ => SPEED_4, }; } fn decelerate(&mut self) { self.speed = match self.speed { i if i <= SPEED_1 => SPEED_0, i if i <= SPEED_2 => SPEED_1, i if i <= SPEED_3 => SPEED_2, i if i <= SPEED_4 => SPEED_3, _ => SPEED_4, }; self.boost_remaining = 0; } fn decelerate_from_obstacle(&mut self) { self.speed = match self.speed { i if i <= SPEED_2 => SPEED_1, i if i <= SPEED_3 => SPEED_2, i if i <= SPEED_4 => SPEED_3, _ => SPEED_4, }; self.boost_remaining = 0; } fn turn_left(&mut self) { self.next_position.y = self.position.y.saturating_sub(1).max(MIN_Y); } fn turn_right(&mut self) { self.next_position.y = self.position.y.saturating_add(1).min(MAX_Y); } fn boost(&mut self) { debug_assert!(self.boosts > 0); self.speed = SPEED_BOOST; self.boost_remaining = BOOST_DURATION; self.boosts = self.boosts.saturating_sub(1); } fn tick_boost(&mut self) { self.boost_remaining = self.boost_remaining.saturating_sub(1); if self.boost_remaining == 0 && self.speed == SPEED_BOOST { self.speed = SPEED_4; } } fn set_next_position_x(&mut self, turning: bool) { if turning { self.next_position.x = self.position.x.saturating_add(self.speed.saturating_sub(1)); } else { self.next_position.x = self.position.x.saturating_add(self.speed); } } fn bonked_while_changing_lanes(&mut self) { self.next_position.y = self.position.y; self.next_position.x = self.position.x.saturating_add(self.speed.saturating_sub(1)); } fn move_along( &mut self, obstacles: &BTreeSet, powerup_oils: &BTreeSet, powerup_boosts: &BTreeSet, finish_lines: &BTreeSet, ) { let start_x = self.position.x.saturating_add(1); for x in start_x..=self.next_position.x { let position = Position { x, y: self.next_position.y, }; if obstacles.contains(&position) { self.decelerate_from_obstacle(); } if powerup_oils.contains(&position) { self.oils = self.oils.saturating_add(1); } if powerup_boosts.contains(&position) { self.boosts = self.boosts.saturating_add(1); } if finish_lines.contains(&position) { self.finished = true; } } self.position = self.next_position; } }