use std::ops::{Index, IndexMut};
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
pub struct Cell {
row: usize,
col: usize,
}
pub fn cell(row: usize, col: usize) -> Cell {
Cell { row, col }
}
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
pub enum Direction {
Up,
Right,
Down,
Left,
}
impl Direction {
pub fn opposite(&self) -> Self {
match *self {
Self::Up => Self::Down,
Self::Down => Self::Up,
Self::Right => Self::Left,
Self::Left => Self::Right,
}
}
}
impl Cell {
fn wrap_add(a: usize, add: bool, grid_side: usize) -> usize {
if a == grid_side - 1 && add {
0
} else if a == 0 && !add {
grid_side - 1
} else if add {
a + 1
} else {
a - 1
}
}
fn shift(self, direction: Direction, grid_side: usize) -> Cell {
let Cell { row, col } = self;
match direction {
Direction::Up => Cell {
row: Self::wrap_add(row, false, grid_side),
col,
},
Direction::Right => Cell {
row,
col: Self::wrap_add(col, true, grid_side),
},
Direction::Down => Cell {
row: Self::wrap_add(row, true, grid_side),
col,
},
Direction::Left => Cell {
row,
col: Self::wrap_add(col, false, grid_side),
},
}
}
}
pub struct CircularSnake {
snake_positions: Vec<Cell>,
head: usize,
grid_side: usize,
}
impl CircularSnake {
pub fn new(grid_side: usize) -> Self {
let mid = grid_side / 2;
Self {
snake_positions: vec![cell(mid, mid), cell(mid, mid - 1)],
head: 0,
grid_side,
}
}
pub fn head(&self) -> Cell {
self.snake_positions[self.head]
}
pub fn len(&self) -> usize {
self.snake_positions.len()
}
pub fn slither_forward(
&mut self,
snake_heading: Direction,
grid: &mut SnakeGrid,
) -> UpdateResult {
let old_head = self.snake_positions[self.head];
let new_head = old_head.shift(snake_heading, self.grid_side);
match grid[new_head] {
CellState::Snake | CellState::SnakeHead => UpdateResult::GameOver,
CellState::Food => {
self.snake_positions.insert(self.head + 1, new_head);
self.head += 1;
grid[old_head] = CellState::Snake;
grid[new_head] = CellState::SnakeHead;
UpdateResult::FoodEaten
}
CellState::Free => {
let tail = (self.head + 1) % self.snake_positions.len();
let old_tail = self.snake_positions[tail];
self.snake_positions[tail] = new_head;
self.head = tail;
grid[old_head] = CellState::Snake;
grid[old_tail] = CellState::Free;
grid[new_head] = CellState::SnakeHead;
UpdateResult::NothingInParticular
}
}
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum UpdateResult {
FoodEaten,
GameOver,
NothingInParticular,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum CellState {
SnakeHead,
Snake,
Food,
Free,
}
pub struct SnakeGrid {
grid: Vec<CellState>,
grid_side: usize,
}
impl Index<Cell> for SnakeGrid {
type Output = CellState;
fn index(&self, cell: Cell) -> &Self::Output {
&self.grid[self.index_of(cell)]
}
}
impl IndexMut<Cell> for SnakeGrid {
fn index_mut(&mut self, cell: Cell) -> &mut Self::Output {
let i = self.index_of(cell);
&mut self.grid[i]
}
}
impl SnakeGrid {
pub fn new(grid_side: usize, snake: &CircularSnake) -> Self {
assert!(grid_side >= 4, "Grid is too small");
let mut grid = Self {
grid_side,
grid: vec![CellState::Free; grid_side * grid_side],
};
for cell in &snake.snake_positions {
grid[*cell] = CellState::Snake;
}
grid[snake.head()] = CellState::SnakeHead;
grid
}
pub fn find_random_free_cell(&self) -> Option<Cell> {
use rand::Rng;
let mut rng = rand::thread_rng();
for _ in 0..60 {
let row = rng.gen_range(0..self.grid_side);
let col = rng.gen_range(0..self.grid_side);
if self[cell(row, col)] == CellState::Free {
return Some(cell(row, col));
}
}
None
}
pub fn grid_side(&self) -> usize {
self.grid_side
}
fn index_of(&self, cell: Cell) -> usize {
cell.row * self.grid_side + cell.col
}
}
pub mod output {
use super::*;
use std::io::{Error, ErrorKind, Result, Write};
use termcolor::{Buffer, BufferWriter, Color, ColorChoice, ColorSpec, WriteColor};
pub fn paint_on_raw_console(grid: &SnakeGrid) {
let str = format_for_raw_console(grid).unwrap();
let stdout = std::io::stdout();
let mut stdout = stdout.lock();
write!(stdout, "\x1B[2J\n\r{}", str).unwrap();
stdout.flush().unwrap();
}
fn print_fence(buf: &mut Buffer, side: usize) -> Result<()> {
write!(buf, "+")?;
for _ in 0..side {
write!(buf, "~~")?;
}
write!(buf, "+\n\r")
}
fn write_colored(buf: &mut Buffer, string: &str, color: &ColorSpec) -> Result<()> {
buf.set_color(color)?;
write!(buf, "{}", string)?;
buf.set_color(&ColorSpec::new())
}
fn format_for_raw_console(grid: &SnakeGrid) -> Result<String> {
let bufwtr = BufferWriter::stdout(ColorChoice::Always);
let mut buf = bufwtr.buffer();
let snake_color = {
let mut it = ColorSpec::new();
it.set_fg(Some(Color::Green));
it
};
let food_color = {
let mut it = ColorSpec::new();
it.set_fg(Some(Color::Yellow));
it
};
print_fence(&mut buf, grid.grid_side())?;
for row in 0..grid.grid_side() {
write!(&mut buf, "|").unwrap();
for col in 0..grid.grid_side() {
match grid[cell(row, col)] {
CellState::SnakeHead => write_colored(&mut buf, "@ ", &snake_color)?,
CellState::Snake => write_colored(&mut buf, "o ", &snake_color)?,
CellState::Food => write_colored(&mut buf, "x ", &food_color)?,
CellState::Free => write!(&mut buf, " ")?,
}
}
write!(&mut buf, "|\n\r")?;
}
print_fence(&mut buf, grid.grid_side())?;
String::from_utf8(buf.into_inner()).map_err(|e| Error::new(ErrorKind::Other, e))
}
}