use crate::wire_representation::{Game, Position};
use rand::Rng;
use serde::{Deserialize, Serialize, Serializer};
use std::borrow::Borrow;
use std::collections::HashMap;
use std::fmt::{self, Debug};
use std::hash::Hash;
use std::time::Duration;
pub type SnakeIDMap = HashMap<String, SnakeId>;
#[derive(Debug, Clone, Copy)]
pub struct Vector {
pub x: i64,
pub y: i64,
}
pub const N_MOVES: usize = 4;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize)]
pub enum Move {
#[allow(missing_docs)]
Left,
#[allow(missing_docs)]
Down,
#[allow(missing_docs)]
Up,
#[allow(missing_docs)]
Right,
}
impl std::fmt::Display for Move {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Move::Left => write!(f, "left"),
Move::Right => write!(f, "right"),
Move::Up => write!(f, "up"),
Move::Down => write!(f, "down"),
}
}
}
impl Move {
pub fn to_vector(self) -> Vector {
match self {
Move::Left => Vector { x: -1, y: 0 },
Move::Right => Vector { x: 1, y: 0 },
Move::Up => Vector { x: 0, y: 1 },
Move::Down => Vector { x: 0, y: -1 },
}
}
pub fn from_vector(vector: Vector) -> Self {
match vector {
Vector { x: -1, y: 0 } => Self::Left,
Vector { x: 1, y: 0 } => Self::Right,
Vector { x: 0, y: 1 } => Self::Up,
Vector { x: 0, y: -1 } => Self::Down,
_ => panic!(),
}
}
pub const fn all() -> [Self; N_MOVES] {
[Move::Up, Move::Down, Move::Left, Move::Right]
}
pub fn all_iter() -> MoveIter {
MoveIter(0)
}
pub fn as_index(&self) -> usize {
match self {
Move::Up => 0,
Move::Down => 1,
Move::Left => 2,
Move::Right => 3,
}
}
pub fn from_index(index: usize) -> Move {
match index {
0 => Move::Up,
1 => Move::Down,
2 => Move::Left,
3 => Move::Right,
_ => panic!("invalid index"),
}
}
#[allow(dead_code)]
pub fn is_not_opposite(&self, other: &Move) -> bool {
!matches!(
(self, other),
(Move::Up, Move::Down)
| (Move::Down, Move::Up)
| (Move::Left, Move::Right)
| (Move::Right, Move::Left)
)
}
}
#[derive(Copy, Clone, Debug)]
pub struct MoveIter(usize);
impl Iterator for MoveIter {
type Item = Move;
fn next(&mut self) -> Option<Self::Item> {
if self.0 < N_MOVES {
let m = Move::from_index(self.0);
self.0 += 1;
Some(m)
} else {
None
}
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Deserialize)]
#[repr(transparent)]
pub struct SnakeId(pub u8);
impl SnakeId {
pub fn as_usize(&self) -> usize {
self.0 as usize
}
}
impl Serialize for SnakeId {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.serialize_u8(self.0)
}
}
pub fn build_snake_id_map(g: &Game) -> SnakeIDMap {
let mut hm = HashMap::new();
hm.insert(g.you.id.clone(), SnakeId(0));
let mut i = 1;
for snake in g.board.snakes.iter() {
if snake.id != g.you.id {
hm.insert(snake.id.clone(), SnakeId(i));
i += 1;
}
}
hm
}
pub trait SnakeIDGettableGame {
#[allow(missing_docs)]
type SnakeIDType: PartialEq + Debug + Serialize + Eq + Hash + Clone + Send;
#[allow(missing_docs)]
fn get_snake_ids(&self) -> Vec<Self::SnakeIDType>;
}
pub trait SimulatorInstruments: std::fmt::Debug {
#[allow(missing_docs)]
fn observe_simulation(&self, duration: Duration);
}
pub trait YouDeterminableGame: std::fmt::Debug + SnakeIDGettableGame {
fn is_you(&self, snake_id: &Self::SnakeIDType) -> bool;
fn you_id(&self) -> &Self::SnakeIDType;
}
pub trait VictorDeterminableGame: std::fmt::Debug + SnakeIDGettableGame {
#[allow(missing_docs)]
fn is_over(&self) -> bool;
fn get_winner(&self) -> Option<Self::SnakeIDType>;
fn alive_snake_count(&self) -> usize;
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
#[repr(transparent)]
pub struct Action<const N_SNAKES: usize> {
moves: [Option<Move>; N_SNAKES],
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub struct OtherAction<const N_SNAKES: usize> {
moves: [Option<Move>; N_SNAKES],
}
impl<const N_SNAKES: usize> Action<N_SNAKES> {
pub fn new(moves: [Option<Move>; N_SNAKES]) -> Self {
Self { moves }
}
pub fn collect_from<'a, T: Iterator<Item = &'a (SnakeId, Move)>>(ids_and_moves: T) -> Self {
let mut moves = [None; N_SNAKES];
for (id, mv) in ids_and_moves {
moves[id.as_usize()] = Some(*mv);
}
Self { moves }
}
pub fn own_move(&self) -> Move {
self.moves[0].unwrap()
}
pub fn other_moves(&self) -> OtherAction<N_SNAKES> {
let mut new_moves = self.moves;
new_moves[0] = None;
OtherAction { moves: new_moves }
}
pub fn into_inner(self) -> [Option<Move>; N_SNAKES] {
self.moves
}
}
pub trait SimulableGame<T: SimulatorInstruments, const N_SNAKES: usize>:
std::fmt::Debug + Sized + SnakeIDGettableGame
{
#[allow(clippy::type_complexity)]
fn simulate(
&self,
instruments: &T,
snake_ids: Vec<Self::SnakeIDType>,
) -> Box<dyn Iterator<Item = (Action<N_SNAKES>, Self)> + '_> {
let moves_to_simulate = Move::all();
let build = snake_ids
.into_iter()
.map(|s| (s, moves_to_simulate.as_slice()));
self.simulate_with_moves(instruments, build)
}
#[allow(clippy::type_complexity)]
fn simulate_with_moves<S>(
&self,
instruments: &T,
snake_ids_and_moves: impl IntoIterator<Item = (Self::SnakeIDType, S)>,
) -> Box<dyn Iterator<Item = (Action<N_SNAKES>, Self)> + '_>
where
S: Borrow<[Move]>;
}
pub trait HazardQueryableGame: PositionGettableGame {
fn is_hazard(&self, pos: &Self::NativePositionType) -> bool;
fn get_hazard_damage(&self) -> u8;
}
pub trait FoodQueryableGame: PositionGettableGame {
fn is_food(&self, pos: &Self::NativePositionType) -> bool;
}
pub trait NeckQueryableGame: PositionGettableGame + SnakeIDGettableGame {
fn is_neck(&self, sid: &Self::SnakeIDType, pos: &Self::NativePositionType) -> bool;
}
pub trait HazardSettableGame: PositionGettableGame {
fn set_hazard(&mut self, pos: Self::NativePositionType);
fn clear_hazard(&mut self, pos: Self::NativePositionType);
}
pub trait PositionGettableGame {
type NativePositionType: Eq + Hash + Clone + Ord + PartialOrd + Debug;
fn position_is_snake_body(&self, pos: Self::NativePositionType) -> bool;
fn position_from_native(&self, native: Self::NativePositionType) -> Position;
fn native_from_position(&self, pos: Position) -> Self::NativePositionType;
fn off_board(&self, pos: Position) -> bool;
}
pub trait HeadGettableGame: PositionGettableGame + SnakeIDGettableGame {
fn get_head_as_position(
&self,
snake_id: &Self::SnakeIDType,
) -> crate::wire_representation::Position;
fn get_head_as_native_position(&self, snake_id: &Self::SnakeIDType)
-> Self::NativePositionType;
}
pub trait FoodGettableGame: PositionGettableGame + SnakeIDGettableGame {
fn get_all_food_as_positions(&self) -> Vec<crate::wire_representation::Position>;
fn get_all_food_as_native_positions(&self) -> Vec<Self::NativePositionType>;
}
pub trait LengthGettableGame: SnakeIDGettableGame {
type LengthType: Ord + PartialOrd;
fn get_length(&self, snake_id: &Self::SnakeIDType) -> Self::LengthType;
fn get_length_i64(&self, snake_id: &Self::SnakeIDType) -> i64;
}
pub trait HealthGettableGame: SnakeIDGettableGame {
type HealthType: PartialEq;
const ZERO: Self::HealthType;
fn get_health(&self, snake_id: &Self::SnakeIDType) -> Self::HealthType;
fn get_health_i64(&self, snake_id: &Self::SnakeIDType) -> i64;
fn is_alive(&self, snake_id: &Self::SnakeIDType) -> bool {
self.get_health(snake_id) != Self::ZERO
}
}
pub trait RandomReasonableMovesGame: SnakeIDGettableGame {
#[allow(missing_docs)]
fn random_reasonable_move_for_each_snake<'a>(
&'a self,
rng: &'a mut impl Rng,
) -> Box<dyn Iterator<Item = (Self::SnakeIDType, Move)> + 'a>;
}
pub trait ReasonableMovesGame: SnakeIDGettableGame {
#[allow(missing_docs)]
fn reasonable_moves_for_each_snake(
&self,
) -> Box<dyn Iterator<Item = (Self::SnakeIDType, Vec<Move>)> + '_>;
}
pub trait NeighborDeterminableGame: PositionGettableGame {
fn neighbors<'a>(
&'a self,
pos: &Self::NativePositionType,
) -> Box<dyn Iterator<Item = Self::NativePositionType> + 'a>;
fn possible_moves<'a>(
&'a self,
pos: &Self::NativePositionType,
) -> Box<dyn Iterator<Item = (Move, Self::NativePositionType)> + 'a>;
}
pub trait ShoutGettableGame: SnakeIDGettableGame {
fn get_shout(&self, snake_id: &Self::SnakeIDType) -> Option<String>;
}
pub trait SizeDeterminableGame {
#[allow(missing_docs)]
fn get_width(&self) -> u32;
#[allow(missing_docs)]
fn get_height(&self) -> u32;
}
pub trait TurnDeterminableGame {
#[allow(missing_docs)]
fn turn(&self) -> u64;
}
pub trait SnakeBodyGettableGame: PositionGettableGame + SnakeIDGettableGame {
fn get_snake_body_vec(&self, snake_id: &Self::SnakeIDType) -> Vec<Self::NativePositionType>;
fn get_snake_body_iter(
&self,
snake_id: &Self::SnakeIDType,
) -> Box<dyn Iterator<Item = Self::NativePositionType> + '_>;
}
pub trait MaxSnakes<const MAX_SNAKES: usize> {}
pub trait EmptyCellGettableGame: PositionGettableGame {
fn get_empty_cells(&self) -> Box<dyn Iterator<Item = Self::NativePositionType> + '_>;
}
pub trait StandardFoodPlaceableGame {
fn place_food(&mut self, rng: &mut impl Rng);
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_move_all_order_matches_iter() {
assert_eq!(Move::all().to_vec(), Move::all_iter().collect::<Vec<_>>());
}
}