summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorJustin Wernick <justin@worthe-it.co.za>2022-04-19 21:36:41 +0200
committerJustin Wernick <justin@worthe-it.co.za>2022-04-19 21:36:41 +0200
commitf8a0e0f7f2f9cd5fb69899b5d7037bc969df4339 (patch)
treefccd2b409974c350485b096858f8f5bf8dad915f /src
parent2f23ab85adb7ffc5866cc948bad0c35a7d41a05b (diff)
Refile for merging repos
Diffstat (limited to 'src')
-rw-r--r--src/command.rs27
-rw-r--r--src/consts.rs14
-rw-r--r--src/global_json.rs165
-rw-r--r--src/json.rs185
-rw-r--r--src/lib.rs147
-rw-r--r--src/main.rs24
-rw-r--r--src/state.rs355
7 files changed, 0 insertions, 917 deletions
diff --git a/src/command.rs b/src/command.rs
deleted file mode 100644
index 1858202..0000000
--- a/src/command.rs
+++ /dev/null
@@ -1,27 +0,0 @@
-use std::fmt;
-
-#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
-pub enum Command {
- Nothing,
- Accelerate,
- Decelerate,
- TurnLeft,
- TurnRight,
- UseBoost,
- UseOil,
-}
-
-impl fmt::Display for Command {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- use Command::*;
- match self {
- Nothing => write!(f, "NOTHING"),
- Accelerate => write!(f, "ACCELERATE"),
- Decelerate => write!(f, "DECELERATE"),
- TurnLeft => write!(f, "TURN_LEFT"),
- TurnRight => write!(f, "TURN_RIGHT"),
- UseBoost => write!(f, "USE_BOOST"),
- UseOil => write!(f, "USE_OIL"),
- }
- }
-}
diff --git a/src/consts.rs b/src/consts.rs
deleted file mode 100644
index 8010eba..0000000
--- a/src/consts.rs
+++ /dev/null
@@ -1,14 +0,0 @@
-pub const SPEED_0: u16 = 0;
-pub const SPEED_1: u16 = 3;
-pub const SPEED_2: u16 = 6;
-pub const SPEED_3: u16 = 8;
-pub const SPEED_4: u16 = 9;
-pub const SPEED_BOOST: u16 = 15;
-
-pub const BOOST_DURATION: u8 = 5;
-
-pub const MIN_Y: u8 = 1;
-pub const HEIGHT: u8 = 4;
-pub const MAX_Y: u8 = MIN_Y + HEIGHT;
-
-pub const WIDTH: u16 = 1500;
diff --git a/src/global_json.rs b/src/global_json.rs
deleted file mode 100644
index 7ac109a..0000000
--- a/src/global_json.rs
+++ /dev/null
@@ -1,165 +0,0 @@
-use std::convert::TryInto;
-use std::fs::File;
-use std::io::prelude::*;
-use std::rc::Rc;
-
-use anyhow::Result;
-use serde::{Deserialize, Serialize};
-use serde_json;
-use serde_repr::{Deserialize_repr, Serialize_repr};
-
-use crate::state::*;
-
-pub fn read_initial_state_from_global_json_file(filename: &str) -> Result<GameState> {
- let mut state = read_state_from_global_json_file(filename)?;
- state.reset_players_to_start();
- Ok(state)
-}
-
-pub fn read_state_from_global_json_file(filename: &str) -> Result<GameState> {
- let mut file = File::open(filename)?;
- let mut content = String::new();
- file.read_to_string(&mut content)?;
- let json_state: JsonState = serde_json::from_str(content.as_ref())?;
- json_state.to_game_state()
-}
-
-#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
-#[serde(rename_all = "camelCase")]
-pub struct JsonState {
- // pub current_round: usize,
- // pub max_rounds: usize,
- pub players: [JsonPlayer; 2],
- pub blocks: Vec<JsonBlock>,
-}
-
-#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
-#[serde(rename_all = "camelCase")]
-pub struct JsonPlayer {
- // id: usize,
- position: JsonPosition,
- speed: u16,
- // state: JsonPlayerState,
- powerups: Vec<JsonPowerup>,
- // boosting: bool,
- boost_counter: u8,
-}
-
-#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
-#[serde(rename_all = "camelCase")]
-pub struct JsonBlock {
- position: JsonPosition,
- surface_object: JsonSurfaceObject,
- // occupied_by_player_id: usize,
-}
-
-#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
-#[serde(rename_all = "camelCase")]
-pub struct JsonPosition {
- lane: u8,
- block_number: u16,
-}
-
-// #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
-// #[serde(rename_all = "SCREAMING_SNAKE_CASE")]
-// pub enum JsonPlayerState {
-// Ready,
-// Nothing,
-// TurningLeft,
-// TurningRight,
-// Accelerating,
-// Decelarating,
-// PickedUpPowerup,
-// UsedBoost,
-// UsedOil,
-// HitMud,
-// HitOil,
-// Finishing,
-// }
-
-#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
-#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
-pub enum JsonPowerup {
- Boost,
- Oil,
-}
-
-#[derive(Serialize_repr, Deserialize_repr, Clone, Debug, PartialEq, Eq)]
-#[serde(rename_all = "camelCase")]
-#[repr(u8)]
-pub enum JsonSurfaceObject {
- Empty = 0,
- Mud = 1,
- OilSpill = 2,
- OilItem = 3,
- FinishLine = 4,
- Boost = 5,
-}
-
-impl JsonState {
- fn to_game_state(&self) -> Result<GameState> {
- Ok(GameState {
- status: GameStatus::Continue,
- players: [self.players[0].to_player()?, self.players[1].to_player()?],
- muds: Rc::new(
- self.blocks
- .iter()
- .filter(|cell| cell.surface_object == JsonSurfaceObject::Mud)
- .map(|cell| cell.position.to_position())
- .collect(),
- ),
- oil_spills: Rc::new(
- self.blocks
- .iter()
- .filter(|cell| cell.surface_object == JsonSurfaceObject::OilSpill)
- .map(|cell| cell.position.to_position())
- .collect(),
- ),
- powerup_oils: Rc::new(
- self.blocks
- .iter()
- .filter(|cell| cell.surface_object == JsonSurfaceObject::OilItem)
- .map(|cell| cell.position.to_position())
- .collect(),
- ),
- powerup_boosts: Rc::new(
- self.blocks
- .iter()
- .filter(|cell| cell.surface_object == JsonSurfaceObject::Boost)
- .map(|cell| cell.position.to_position())
- .collect(),
- ),
- })
- }
-}
-
-impl JsonPlayer {
- fn to_player(&self) -> Result<Player> {
- Ok(Player {
- position: self.position.to_position(),
- speed: self.speed,
- boost_remaining: self.boost_counter,
- oils: self
- .powerups
- .iter()
- .filter(|powerup| **powerup == JsonPowerup::Oil)
- .count()
- .try_into()?,
- boosts: self
- .powerups
- .iter()
- .filter(|powerup| **powerup == JsonPowerup::Boost)
- .count()
- .try_into()?,
- })
- }
-}
-
-impl JsonPosition {
- fn to_position(&self) -> Position {
- Position {
- x: self.block_number,
- y: self.lane,
- }
- }
-}
diff --git a/src/json.rs b/src/json.rs
deleted file mode 100644
index 82fc9fc..0000000
--- a/src/json.rs
+++ /dev/null
@@ -1,185 +0,0 @@
-use std::convert::TryInto;
-use std::fs::File;
-use std::io::prelude::*;
-use std::rc::Rc;
-
-use anyhow::Result;
-use serde::{Deserialize, Serialize};
-use serde_json;
-use serde_repr::{Deserialize_repr, Serialize_repr};
-
-use crate::state::*;
-
-pub fn read_state_from_json_file(filename: &str) -> Result<GameState> {
- let mut file = File::open(filename)?;
- let mut content = String::new();
- file.read_to_string(&mut content)?;
- let json_state: JsonState = serde_json::from_str(content.as_ref())?;
- json_state.to_game_state()
-}
-
-#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
-#[serde(rename_all = "camelCase")]
-pub struct JsonState {
- // pub current_round: usize,
- // pub max_rounds: usize,
- pub player: JsonPlayer,
- pub opponent: JsonOpponent,
- pub world_map: Vec<Vec<JsonWorldMapCell>>,
-}
-
-#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
-#[serde(rename_all = "camelCase")]
-pub struct JsonPlayer {
- // id: usize,
- position: JsonPosition,
- speed: u16,
- // state: JsonPlayerState,
- powerups: Vec<JsonPowerup>,
- // boosting: bool,
- boost_counter: u8,
-}
-
-#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
-#[serde(rename_all = "camelCase")]
-pub struct JsonOpponent {
- // id: usize,
- position: JsonPosition,
- speed: u16,
-}
-
-#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
-#[serde(rename_all = "camelCase")]
-pub struct JsonWorldMapCell {
- position: JsonPosition,
- surface_object: JsonSurfaceObject,
- // occupied_by_player_id: usize,
-}
-
-#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
-#[serde(rename_all = "camelCase")]
-pub struct JsonPosition {
- y: u8,
- x: u16,
-}
-
-// #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
-// #[serde(rename_all = "SCREAMING_SNAKE_CASE")]
-// pub enum JsonPlayerState {
-// Ready,
-// Nothing,
-// TurningLeft,
-// TurningRight,
-// Accelerating,
-// Decelarating,
-// PickedUpPowerup,
-// UsedBoost,
-// UsedOil,
-// HitMud,
-// HitOil,
-// Finishing,
-// }
-
-#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
-#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
-pub enum JsonPowerup {
- Boost,
- Oil,
-}
-
-#[derive(Serialize_repr, Deserialize_repr, Clone, Debug, PartialEq, Eq)]
-#[serde(rename_all = "camelCase")]
-#[repr(u8)]
-pub enum JsonSurfaceObject {
- Empty = 0,
- Mud = 1,
- OilSpill = 2,
- OilItem = 3,
- FinishLine = 4,
- Boost = 5,
-}
-
-impl JsonState {
- fn to_game_state(&self) -> Result<GameState> {
- Ok(GameState {
- status: GameStatus::Continue,
- players: [self.player.to_player()?, self.opponent.to_player()],
- muds: Rc::new(
- self.world_map
- .iter()
- .flatten()
- .filter(|cell| cell.surface_object == JsonSurfaceObject::Mud)
- .map(|cell| cell.position.to_position())
- .collect(),
- ),
- oil_spills: Rc::new(
- self.world_map
- .iter()
- .flatten()
- .filter(|cell| cell.surface_object == JsonSurfaceObject::OilSpill)
- .map(|cell| cell.position.to_position())
- .collect(),
- ),
- powerup_oils: Rc::new(
- self.world_map
- .iter()
- .flatten()
- .filter(|cell| cell.surface_object == JsonSurfaceObject::OilItem)
- .map(|cell| cell.position.to_position())
- .collect(),
- ),
- powerup_boosts: Rc::new(
- self.world_map
- .iter()
- .flatten()
- .filter(|cell| cell.surface_object == JsonSurfaceObject::Boost)
- .map(|cell| cell.position.to_position())
- .collect(),
- ),
- })
- }
-}
-
-impl JsonPlayer {
- fn to_player(&self) -> Result<Player> {
- Ok(Player {
- position: self.position.to_position(),
- speed: self.speed,
- boost_remaining: self.boost_counter,
- oils: self
- .powerups
- .iter()
- .filter(|powerup| **powerup == JsonPowerup::Oil)
- .count()
- .try_into()?,
- boosts: self
- .powerups
- .iter()
- .filter(|powerup| **powerup == JsonPowerup::Boost)
- .count()
- .try_into()?,
- })
- }
-}
-
-impl JsonOpponent {
- // TODO: Track opponent powerups from round to round?
- fn to_player(&self) -> Player {
- Player {
- position: self.position.to_position(),
- speed: self.speed,
- boost_remaining: 0,
- oils: 0,
- boosts: 0,
- }
- }
-}
-
-impl JsonPosition {
- fn to_position(&self) -> Position {
- Position {
- x: self.x,
- y: self.y,
- }
- }
-}
diff --git a/src/lib.rs b/src/lib.rs
deleted file mode 100644
index c36a817..0000000
--- a/src/lib.rs
+++ /dev/null
@@ -1,147 +0,0 @@
-pub mod command;
-pub mod consts;
-pub mod global_json;
-pub mod json;
-pub mod state;
-
-use command::*;
-use consts::*;
-use pathfinding::prelude::*;
-use state::*;
-use std::cmp::Ordering;
-
-pub fn choose_command(round: usize, state: &GameState) -> Command {
- if round <= 100 {
- choose_command_with_looking_forward_heuristic(state)
- } else {
- choose_command_with_astar(state)
- }
-}
-
-#[derive(Debug, Clone)]
-struct HeuristicLookaheadState {
- moves: Vec<Command>,
- events: GameStateUpdateEvents,
- current_state: GameState,
-}
-
-fn choose_command_with_looking_forward_heuristic(state: &GameState) -> Command {
- let depth = 3;
- let mut states = vec![HeuristicLookaheadState {
- moves: Vec::new(),
- events: GameStateUpdateEvents::default(),
- current_state: state.clone(),
- }];
-
- for _ in 0..depth {
- states = states
- .into_iter()
- .flat_map(move |state| {
- state
- .current_state
- .good_moves(0)
- .into_iter()
- .map(move |player_move| {
- let mut state = state.clone();
- state.moves.push(player_move);
- state
- .current_state
- .update([player_move, Command::Accelerate], &mut state.events);
- state
- })
- })
- .collect();
- }
-
- states
- .into_iter()
- .max_by(|a, b| compare_events(&a.events, &b.events))
- .unwrap()
- .moves
- .into_iter()
- .next()
- .unwrap()
-}
-
-fn compare_states(a: &GameState, b: &GameState) -> Ordering {
- if a.status == GameStatus::PlayerOneWon && b.status == GameStatus::PlayerOneWon {
- a.players[0].speed.cmp(&b.players[0].speed)
- } else if a.status == GameStatus::PlayerOneWon {
- Ordering::Greater
- } else if b.status == GameStatus::PlayerOneWon {
- Ordering::Less
- } else {
- let weighted_position_a = a.players[0].position.x + a.players[0].boosts * 2;
- let weighted_position_b = b.players[0].position.x + b.players[0].boosts * 2;
-
- weighted_position_a
- .cmp(&weighted_position_b)
- .then(a.players[0].speed.cmp(&b.players[0].speed))
- .then(a.players[0].position.x.cmp(&b.players[0].position.x))
- .then(a.players[0].boosts.cmp(&b.players[0].boosts))
- }
-}
-
-fn compare_events(a: &GameStateUpdateEvents, b: &GameStateUpdateEvents) -> Ordering {
- let a = &a.players[0];
- let b = &b.players[0];
- a.boosts_collected
- .cmp(&b.boosts_collected)
- .then(a.boosts_maintained.cmp(&b.boosts_maintained))
- .then(a.distance_travelled.cmp(&b.distance_travelled))
- .then(a.mud_hit.cmp(&b.mud_hit).reverse())
- .then(a.boosts_used.cmp(&b.boosts_used).reverse())
-}
-
-fn choose_command_with_astar(state: &GameState) -> Command {
- // TODO: Find all shortest paths, choose the one that has the
- // highest end speed, or otherwise wins
- shortest_path_first_command(state).unwrap_or(Command::Accelerate)
-}
-
-fn shortest_path_first_command(initial_state: &GameState) -> Option<Command> {
- let shortest_path_states = astar(
- initial_state,
- |state| {
- state
- .good_moves(0)
- .into_iter()
- .filter(|player_move| *player_move != Command::UseOil)
- .map(|player_move| {
- let mut state = state.clone();
- state.update(
- [player_move, Command::Decelerate],
- &mut GameStateUpdateEvents::default(),
- );
- (state, 1)
- })
- .collect::<Vec<_>>()
- },
- |state| (WIDTH - state.players[0].position.x) / SPEED_BOOST,
- |state| state.status != GameStatus::Continue,
- )
- .unwrap();
-
- shortest_path_states
- .0
- .iter()
- .zip(shortest_path_states.0.iter().skip(1))
- .map(|(state, next)| {
- let player_move = state
- .good_moves(0)
- .into_iter()
- .filter(|player_move| *player_move != Command::UseOil)
- .find(|player_move| {
- let mut state = state.clone();
- state.update(
- [*player_move, Command::Decelerate],
- &mut GameStateUpdateEvents::default(),
- );
- state == *next
- })
- .unwrap();
-
- player_move
- })
- .next()
-}
diff --git a/src/main.rs b/src/main.rs
deleted file mode 100644
index c5f7857..0000000
--- a/src/main.rs
+++ /dev/null
@@ -1,24 +0,0 @@
-use std::io::prelude::*;
-use std::io::stdin;
-
-use vroomba::command::Command;
-use vroomba::*;
-
-fn main() {
- for line in stdin().lock().lines() {
- let round_number = line
- .expect("Failed to read line from stdin: {}")
- .parse::<usize>()
- .expect("Round number was not an unsigned integer: {}");
- let command =
- match json::read_state_from_json_file(&format!("./rounds/{}/state.json", round_number))
- {
- Ok(state) => choose_command(round_number, &state),
- Err(e) => {
- eprintln!("WARN: State file could not be parsed: {}", e);
- Command::Nothing
- }
- };
- println!("C;{};{}", round_number, command);
- }
-}
diff --git a/src/state.rs b/src/state.rs
deleted file mode 100644
index acbce80..0000000
--- a/src/state.rs
+++ /dev/null
@@ -1,355 +0,0 @@
-use crate::command::Command;
-use crate::consts::*;
-use std::collections::BTreeSet;
-use std::ops::Bound::{Excluded, Included};
-use std::rc::Rc;
-
-#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
-pub enum GameStatus {
- Continue,
- PlayerOneWon,
- PlayerTwoWon,
- Draw, // Until I add score I guess
-}
-
-// TODO: Maintain sorted vecs for these BTrees? Do the range counts
-// with binary searches to find indices only.
-#[derive(Debug, Clone, Hash, PartialEq, Eq)]
-pub struct GameState {
- pub status: GameStatus,
- pub players: [Player; 2],
- pub muds: Rc<BTreeSet<Position>>,
- pub oil_spills: Rc<BTreeSet<Position>>,
- pub powerup_oils: Rc<BTreeSet<Position>>,
- pub powerup_boosts: Rc<BTreeSet<Position>>,
-}
-
-#[derive(Debug, Clone, Hash, PartialEq, Eq)]
-pub struct Player {
- pub position: Position,
- pub speed: u16,
- pub boost_remaining: u8,
- pub oils: u16,
- pub boosts: u16,
-}
-
-#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
-pub struct Position {
- pub y: u8,
- pub x: u16,
-}
-
-#[derive(Debug, Clone, Default)]
-pub struct GameStateUpdateEvents {
- pub players: [GameStatePlayerUpdateEvents; 2],
-}
-#[derive(Debug, Clone, Default)]
-pub struct GameStatePlayerUpdateEvents {
- pub boosts_used: u16,
- pub boosts_collected: u16,
- pub boosts_maintained: u8,
- pub mud_hit: u16,
- pub oil_collected: u16,
- pub distance_travelled: u16,
-}
-
-impl GameState {
- pub fn update(&mut self, commands: [Command; 2], events: &mut GameStateUpdateEvents) {
- if self.status != GameStatus::Continue {
- return;
- }
-
- let next_positions = [
- self.do_command(0, &commands[0], &mut events.players[0]),
- self.do_command(1, &commands[1], &mut events.players[1]),
- ];
- let next_positions = self.update_player_collisions(next_positions, events);
- self.update_player_travel(next_positions, events);
-
- 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;
- }
- }
-
- fn do_command(
- &mut self,
- player_index: usize,
- command: &Command,
- events: &mut GameStatePlayerUpdateEvents,
- ) -> Position {
- use Command::*;
- self.players[player_index].tick_boost();
- let mut next_y = self.players[player_index].position.y;
-
- match command {
- Nothing => {}
- Accelerate => self.players[player_index].accelerate(),
- Decelerate => self.players[player_index].decelerate(),
- TurnLeft => next_y = next_y.saturating_sub(1).max(MIN_Y),
- TurnRight => next_y = next_y.saturating_add(1).min(MAX_Y),
- UseBoost => {
- events.boosts_used += 1;
- 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 oil_spills = (*self.oil_spills).clone();
- oil_spills.insert(Position {
- x: player_position.x.saturating_sub(1),
- y: player_position.y,
- });
- self.oil_spills = Rc::new(oil_spills);
- }
- }
-
- let turning = match command {
- TurnLeft | TurnRight => true,
- _ => false,
- };
-
- let next_x = self.players[player_index].next_position_x(turning);
- Position {
- x: next_x,
- y: next_y,
- }
- }
-
- fn update_player_collisions(
- &mut self,
- next_positions: [Position; 2],
- _events: &mut GameStateUpdateEvents,
- ) -> [Position; 2] {
- let same_lanes_before = self.players[0].position.y == self.players[1].position.y;
- let same_lanes_after = next_positions[0].y == next_positions[1].y;
- let first_passing_second = self.players[0].position.x < self.players[1].position.x
- && next_positions[0].x >= next_positions[1].x;
- let second_passing_first = self.players[1].position.x < self.players[0].position.x
- && next_positions[1].x >= next_positions[0].x;
- let same_x_after = next_positions[0].x == next_positions[1].x;
-
- if same_lanes_before && same_lanes_after && first_passing_second {
- [
- Position {
- y: next_positions[0].y,
- x: next_positions[1].x.saturating_sub(1),
- },
- next_positions[1],
- ]
- } else if same_lanes_before && same_lanes_after && second_passing_first {
- [
- next_positions[0],
- Position {
- y: next_positions[1].y,
- x: next_positions[0].x.saturating_sub(1),
- },
- ]
- } else if same_lanes_after && same_x_after {
- [
- Position {
- y: self.players[0].position.y,
- x: self.players[0].next_position_x(true),
- },
- Position {
- y: self.players[1].position.y,
- x: self.players[1].next_position_x(true),
- },
- ]
- } else {
- next_positions
- }
- }
-
- fn update_player_travel(
- &mut self,
- next_positions: [Position; 2],
- events: &mut GameStateUpdateEvents,
- ) {
- for (i, (player, next_position)) in self
- .players
- .iter_mut()
- .zip(next_positions.iter())
- .enumerate()
- {
- player.move_along(
- *next_position,
- &self.muds,
- &self.oil_spills,
- &self.powerup_oils,
- &self.powerup_boosts,
- &mut events.players[i],
- );
- }
- }
-
- pub fn valid_moves(&self, player_index: usize) -> Vec<Command> {
- let player = &self.players[player_index];
- let mut result = Vec::with_capacity(7);
- result.push(Command::Nothing);
- 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 player.oils > 0 {
- result.push(Command::UseOil);
- }
- result
- }
-
- pub fn good_moves(&self, player_index: usize) -> Vec<Command> {
- let player = &self.players[player_index];
- let mut result = Vec::with_capacity(5);
- result.push(Command::Accelerate);
- 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 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_BOOST => SPEED_BOOST,
- _ => 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 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 next_position_x(&mut self, turning: bool) -> u16 {
- if turning {
- self.position.x.saturating_add(self.speed.saturating_sub(1))
- } else {
- self.position.x.saturating_add(self.speed)
- }
- }
-
- fn move_along(
- &mut self,
- next_position: Position,
- muds: &BTreeSet<Position>,
- oil_spills: &BTreeSet<Position>,
- powerup_oils: &BTreeSet<Position>,
- powerup_boosts: &BTreeSet<Position>,
- events: &mut GameStatePlayerUpdateEvents,
- ) {
- let range = (
- Included(Position {
- y: next_position.y,
- x: self.position.x.saturating_add(1),
- }),
- Excluded(Position {
- y: next_position.y,
- x: next_position.x.saturating_add(1),
- }),
- );
-
- let mud_hit = muds
- .range(range)
- .count()
- .saturating_add(oil_spills.range(range).count());
- for _ in 0..mud_hit {
- self.decelerate_from_obstacle();
- }
-
- let oil_collected = powerup_oils.range(range).count() as u16;
- self.oils = self.oils.saturating_add(oil_collected);
- let boosts_collected = powerup_boosts.range(range).count() as u16;
- self.boosts = self.boosts.saturating_add(boosts_collected);
-
- events.mud_hit = events.mud_hit.saturating_add(mud_hit as u16);
- events.boosts_collected = events.boosts_collected.saturating_add(boosts_collected);
- events.oil_collected = events.oil_collected.saturating_add(oil_collected);
- events.distance_travelled = events
- .distance_travelled
- .saturating_add(next_position.x.saturating_sub(self.position.x));
- if self.speed == SPEED_BOOST {
- events.boosts_maintained = events.boosts_maintained.saturating_add(1);
- }
-
- self.position = next_position;
- }
-
- fn finished(&self) -> bool {
- self.position.x >= WIDTH
- }
-}