commit 3613f35cc40eab8053b0058512742e228e769440 Author: Vegard Matthey Date: Mon Apr 1 20:50:56 2024 +0200 initial diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..96ef6c0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +Cargo.lock diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..f24512e --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "term_chess" +version = "0.1.0" +edition = "2021" + +[dependencies] diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..37e9d5d --- /dev/null +++ b/src/main.rs @@ -0,0 +1,1016 @@ +const SIZE: usize = 8; +const WHITE: Color = Color { + r: 255, + g: 255, + b: 255, +}; +const RED: Color = Color { r: 255, g: 0, b: 0 }; +const BLUE: Color = Color { r: 0, g: 0, b: 255 }; +const BLACK: Color = Color { r: 0, g: 0, b: 0 }; + +#[derive(Debug, PartialEq)] +enum MoveError { + InCheck, + MoveNotFound, +} + +#[derive(Debug, PartialEq)] +enum SpecialMoveError { + ExpectedRook, + MoveNotAvailable, + UnexpectedDistance, + SquareAttacked, +} + +#[derive(Debug)] +enum InputParseError { + MissingCoordinate, + NumberParseError, +} + +struct Game { + board: Board, + turn: Side, + moves: Vec<(Position, Position)>, +} + +#[derive(Debug, Clone)] +struct Piece { + kind: PieceKind, + pos: Position, + side: Side, + move_count: u32, +} + +#[derive(PartialEq)] +enum SpecialMove { + Castle((bool, bool)), + EnPassant(Position), +} + +#[derive(PartialEq, Debug, Clone)] +enum Side { + Black, + White, +} + +#[derive(PartialEq, Debug, Clone)] +enum PieceKind { + Pawn, + Bishop, + Knight, + Rook, + Queen, + King, +} + +#[derive(PartialEq, Debug, Clone)] +struct Position { + x: usize, + y: usize, +} + +#[derive(Clone)] +struct Board([[Option; SIZE]; SIZE]); + +fn main() { + let mut game = Game::new(); + let mut msg = String::new(); + game.board.render("White to play."); + + loop { + let start_pos = get_input("Select piece: "); + let res_pos = Position::new(start_pos.x, start_pos.y); + if let Some(piece) = &game.board.0[start_pos.y][start_pos.x] { + let mut piece = piece.clone(); + if piece.side == game.turn { + let pos = get_input("Select square: "); + let move_result = piece.move_piece(&pos, &game.board, game.moves.last()); + if let Ok(special_move) = move_result { + if handle_special_moves(special_move, &mut game, &mut piece, &start_pos, &pos) + .is_ok() + { + game.turn = match game.turn { + Side::White => Side::Black, + Side::Black => Side::White, + }; + game.moves.push((start_pos, pos)); + piece.move_count += 1; + } else { + piece.pos = start_pos; + } + } else { + piece.pos = start_pos; + } + } + if piece.pos != res_pos { + let (x, y) = (piece.pos.x, piece.pos.y); + game.board.0[res_pos.y][res_pos.x] = None; + game.board.0[y][x] = Some(piece); + if game.board.count_moves(&game.turn, game.moves.last()) == 0 { + if let Some(loser) = game.board.check_for_check(game.moves.last()) { + msg = match loser { + Side::White => "Black wins with checkmate!".to_string(), + Side::Black => "White wins with checkmate!".to_string(), + }; + } else { + msg = "Stalemate!".to_string(); + } + game.board.render(&msg); + break; + } + } + } else { + msg.push_str("Coordinates do not correspond to piece. "); + } + match game.turn { + Side::White => msg.push_str("White to play."), + Side::Black => msg.push_str("Black to play."), + } + game.board.render(&msg); + msg.clear(); + } +} + +fn handle_special_moves( + special_move: Option, + game: &mut Game, + piece: &mut Piece, + start_pos: &Position, + pos: &Position, +) -> Result<(), SpecialMoveError> { + if let Some(special_move) = special_move { + match special_move { + SpecialMove::EnPassant(en_passant) => { + assert!(game.board.0[en_passant.y][en_passant.x].is_some()); + game.board.0[en_passant.y][en_passant.x] = None; + } + SpecialMove::Castle((left, right)) => { + assert!(piece.kind == PieceKind::King); + assert!(start_pos.y == pos.y); + match start_pos.x.abs_diff(pos.x) { + 2 => { + if right { + assert_eq!(start_pos.x, 4); + for x in start_pos.x..SIZE { + if game.board.is_attacked( + &Position::new(x, piece.pos.y), + &piece.side, + game.moves.last(), + ) { + return Err(SpecialMoveError::SquareAttacked); + } + } + assert!(game.board.0[piece.pos.y][7].is_some()); + if let Some(mut rook) = game.board.0[piece.pos.y][7].take() { + rook.move_piece( + &Position::new(pos.x - 1, pos.y), + &game.board, + game.moves.last(), + ) + .expect("f rook"); + game.board.0[pos.y][pos.x - 1] = Some(rook); + } else { + return Err(SpecialMoveError::ExpectedRook); + } + } else { + return Err(SpecialMoveError::MoveNotAvailable); + } + } + 3 => { + if left { + assert!(game.board.0[piece.pos.y][0].is_some()); + assert_eq!(start_pos.x, 4); + for x in 0..start_pos.x + 1 { + if game.board.is_attacked( + &Position::new(x, piece.pos.y), + &piece.side, + game.moves.last(), + ) { + return Err(SpecialMoveError::SquareAttacked); + } + } + if let Some(mut rook) = game.board.0[piece.pos.y][0].take() { + rook.move_piece( + &Position::new(pos.x + 1, pos.y), + &game.board, + game.moves.last(), + ) + .expect("f moving rook"); + game.board.0[pos.y][pos.x + 1] = Some(rook); + } + } else { + return Err(SpecialMoveError::MoveNotAvailable); + } + } + _ => return Err(SpecialMoveError::UnexpectedDistance), + } + } + } + } + Ok(()) +} + +impl Game { + fn new() -> Self { + Self { + board: Board::new(), + turn: Side::White, + moves: Vec::new(), + } + } +} + +fn get_input(query: &str) -> Position { + use std::{io, io::Write}; + let mut s = String::new(); + print!("{}", query); + io::stdout().flush().expect("failed to flush stdout"); + io::stdin().read_line(&mut s).expect("failed to read stdin"); + match parse_input(&s) { + Ok(v) => v, + Err(e) => { + eprintln!("string: {s} failed to parse: {:?}", e); + std::process::exit(1); + } + } +} + +fn parse_input(input: &str) -> Result { + let mut args = input.trim().split(','); + let (x, y) = ( + args.next() + .ok_or(InputParseError::MissingCoordinate)? + .parse() + .map_err(|_| InputParseError::NumberParseError)?, + args.next() + .ok_or(InputParseError::MissingCoordinate)? + .parse() + .map_err(|_| InputParseError::NumberParseError)?, + ); + Ok(Position { x, y }) +} + +impl Board { + fn check_for_check(&self, prev_move: Option<&(Position, Position)>) -> Option { + for piece in self.0.iter().flatten().flatten() { + let (squares, _) = piece.pos(self, prev_move); + for pos in squares { + if let Some(cap) = &self.0[pos.y][pos.x] { + if cap.side != piece.side && cap.kind == PieceKind::King { + return Some(cap.side.clone()); + } + } + } + } + None + } + + fn count_moves(&mut self, side: &Side, prev_move: Option<&(Position, Position)>) -> u32 { + let mut pieces = Vec::new(); + let mut move_count = 0; + for piece in self.0.iter().flatten().flatten() { + if piece.side == *side { + pieces.push(piece.clone()); + } + } + for piece in pieces.iter_mut() { + for pos in piece.pos(self, prev_move).0 { + if piece.move_piece(&pos, self, prev_move).is_ok() { + move_count += 1; + } + } + } + move_count + } + + fn is_attacked( + &self, + pos: &Position, + side: &Side, + prev_move: Option<&(Position, Position)>, + ) -> bool { + for piece in self.0.iter().flatten().flatten() { + if &piece.side != side && piece.pos(self, prev_move).0.contains(pos) { + return true; + } + } + false + } + + fn render(&self, msg: &str) { + // let mut s = String::with_capacity((SIZE + 2) * (SIZE * 2 + 3)); + let mut s = String::new(); + s.push('+'); + s.push_str(&"-".repeat(SIZE * 2)); + s.push('+'); + s.push('\n'); + for (y, rank) in self.0.iter().rev().enumerate() { + s.push('|'); + for (x, square) in rank.iter().enumerate() { + let bg_color = if (x + y) % 2 == 0 { WHITE } else { BLACK }; + if let Some(piece) = square { + let fg_color = if piece.side == Side::White { BLUE } else { RED }; + s.push_str(&piece.kind.to_string().fg(fg_color).bg(bg_color)); + } else { + s.push_str(&" ".bg(bg_color)); + } + } + s.push_str(&format!("|{}\n", (SIZE - y))); + } + s.push('+'); + s.push_str(&"-".repeat(SIZE * 2)); + s.push_str("+\n A B C D E F G H "); + print!("\u{001b}c"); + println!("{}\n{}", msg, s); + } + fn new() -> Self { + Board([ + [ + Some(Piece { + kind: PieceKind::Rook, + side: Side::White, + pos: Position { x: 0, y: 0 }, + move_count: 0, + }), + Some(Piece { + kind: PieceKind::Knight, + side: Side::White, + pos: Position { x: 1, y: 0 }, + move_count: 0, + }), + Some(Piece { + kind: PieceKind::Bishop, + side: Side::White, + pos: Position { x: 2, y: 0 }, + move_count: 0, + }), + Some(Piece { + kind: PieceKind::Queen, + side: Side::White, + pos: Position { x: 3, y: 0 }, + move_count: 0, + }), + Some(Piece { + kind: PieceKind::King, + side: Side::White, + pos: Position { x: 4, y: 0 }, + move_count: 0, + }), + Some(Piece { + kind: PieceKind::Bishop, + side: Side::White, + pos: Position { x: 5, y: 0 }, + move_count: 0, + }), + Some(Piece { + kind: PieceKind::Knight, + side: Side::White, + pos: Position { x: 6, y: 0 }, + move_count: 0, + }), + Some(Piece { + kind: PieceKind::Rook, + side: Side::White, + pos: Position { x: 7, y: 0 }, + move_count: 0, + }), + ], + [ + Some(Piece { + kind: PieceKind::Pawn, + side: Side::White, + pos: Position { x: 0, y: 1 }, + move_count: 0, + }), + Some(Piece { + kind: PieceKind::Pawn, + side: Side::White, + pos: Position { x: 1, y: 1 }, + move_count: 0, + }), + Some(Piece { + kind: PieceKind::Pawn, + side: Side::White, + pos: Position { x: 2, y: 1 }, + move_count: 0, + }), + Some(Piece { + kind: PieceKind::Pawn, + side: Side::White, + pos: Position { x: 3, y: 1 }, + move_count: 0, + }), + Some(Piece { + kind: PieceKind::Pawn, + side: Side::White, + pos: Position { x: 4, y: 1 }, + move_count: 0, + }), + Some(Piece { + kind: PieceKind::Pawn, + side: Side::White, + pos: Position { x: 5, y: 1 }, + move_count: 0, + }), + Some(Piece { + kind: PieceKind::Pawn, + side: Side::White, + pos: Position { x: 6, y: 1 }, + move_count: 0, + }), + Some(Piece { + kind: PieceKind::Pawn, + side: Side::White, + pos: Position { x: 7, y: 1 }, + move_count: 0, + }), + ], + [None, None, None, None, None, None, None, None], + [None, None, None, None, None, None, None, None], + [None, None, None, None, None, None, None, None], + [None, None, None, None, None, None, None, None], + [ + Some(Piece { + kind: PieceKind::Pawn, + side: Side::Black, + pos: Position { x: 0, y: 6 }, + move_count: 0, + }), + Some(Piece { + kind: PieceKind::Pawn, + side: Side::Black, + pos: Position { x: 1, y: 6 }, + move_count: 0, + }), + Some(Piece { + kind: PieceKind::Pawn, + side: Side::Black, + pos: Position { x: 2, y: 6 }, + move_count: 0, + }), + Some(Piece { + kind: PieceKind::Pawn, + side: Side::Black, + pos: Position { x: 3, y: 6 }, + move_count: 0, + }), + Some(Piece { + kind: PieceKind::Pawn, + side: Side::Black, + pos: Position { x: 4, y: 6 }, + move_count: 0, + }), + Some(Piece { + kind: PieceKind::Pawn, + side: Side::Black, + pos: Position { x: 5, y: 6 }, + move_count: 0, + }), + Some(Piece { + kind: PieceKind::Pawn, + side: Side::Black, + pos: Position { x: 6, y: 6 }, + move_count: 0, + }), + Some(Piece { + kind: PieceKind::Pawn, + side: Side::Black, + pos: Position { x: 7, y: 6 }, + move_count: 0, + }), + ], + [ + Some(Piece { + kind: PieceKind::Rook, + side: Side::Black, + pos: Position { x: 0, y: 7 }, + move_count: 0, + }), + Some(Piece { + kind: PieceKind::Knight, + side: Side::Black, + pos: Position { x: 1, y: 7 }, + move_count: 0, + }), + Some(Piece { + kind: PieceKind::Bishop, + side: Side::Black, + pos: Position { x: 2, y: 7 }, + move_count: 0, + }), + Some(Piece { + kind: PieceKind::Queen, + side: Side::Black, + pos: Position { x: 3, y: 7 }, + move_count: 0, + }), + Some(Piece { + kind: PieceKind::King, + side: Side::Black, + pos: Position { x: 4, y: 7 }, + move_count: 0, + }), + Some(Piece { + kind: PieceKind::Bishop, + side: Side::Black, + pos: Position { x: 5, y: 7 }, + move_count: 0, + }), + Some(Piece { + kind: PieceKind::Knight, + side: Side::Black, + pos: Position { x: 6, y: 7 }, + move_count: 0, + }), + Some(Piece { + kind: PieceKind::Rook, + side: Side::Black, + pos: Position { x: 7, y: 7 }, + move_count: 0, + }), + ], + ]) + } +} + +impl Piece { + fn move_piece( + &mut self, + pos: &Position, + board: &Board, + prev_move: Option<&(Position, Position)>, + ) -> Result, MoveError> { + let (moves, special_move) = self.pos(board, prev_move); + if !moves.contains(pos) { + return Err(MoveError::MoveNotFound); + } + let prev = Position { + x: self.pos.x, + y: self.pos.y, + }; + self.pos.x = pos.x; + self.pos.y = pos.y; + + let mut board: Board = board.clone(); + if let Some(mut piece) = board.0[prev.y][prev.x].take() { + piece.pos = pos.clone(); + board.0[pos.y][pos.x] = Some(piece); + } + + if let Some(side) = board.check_for_check(prev_move) { + if side == self.side { + self.pos.x = prev.x; + self.pos.y = prev.y; + return Err(MoveError::InCheck); + } + } + Ok(special_move) + } + + fn pos( + &self, + board: &Board, + prev_move: Option<&(Position, Position)>, + ) -> (Vec, Option) { + match self.kind { + PieceKind::Pawn => self.pawn_moves(board, prev_move), + PieceKind::Knight => (self.knight_moves(board), None), + PieceKind::Bishop => (self.bishop_moves(board), None), + PieceKind::Rook => (self.rook_moves(board), None), + PieceKind::Queen => (self.queen_moves(board), None), + PieceKind::King => self.king_moves(board), + } + } + + fn test_pos(&self, board: &Board, (x, y): (isize, isize), piece: bool) -> Option { + let (x, y): (isize, isize) = (self.pos.x as isize + x, self.pos.y as isize + y); + if x >= 0 && x < SIZE as isize && y >= 0 && y < SIZE as isize { + let (x, y) = (x as usize, y as usize); + if piece { + if let Some(piece) = &board.0[y][x] { + if piece.side != self.side { + return Some(Position { x, y }); + } + } + } else if board.0[y][x].is_none() { + return Some(Position { x, y }); + } + } + None + } + + fn pawn_moves( + &self, + board: &Board, + prev_move: Option<&(Position, Position)>, + ) -> (Vec, Option) { + let mut result = Vec::new(); + let mut en_passant = None; + if (self.pos.y == SIZE - 1 && self.side == Side::White) + || (self.pos.y == 0 && self.side == Side::Black) + { + return (result, None); + } + if self.side == Side::White { + if let Some(pos) = self.test_pos(board, (0, 1), false) { + result.push(pos); + } + if self.pos.y == 1 && self.test_pos(board, (0, 1), false).is_some() { + if let Some(pos) = self.test_pos(board, (0, 2), false) { + result.push(pos) + } + } + if let Some(pos) = self.test_pos(board, (1, 1), true) { + result.push(pos); + } + if let Some(pos) = self.test_pos(board, (-1, 1), true) { + result.push(pos); + } + + // En passant for white will only happen on this rank + if self.pos.y == 4 { + if let Some(pos) = self.test_pos(board, (1, 1), false) { + if board.0[pos.y - 1][pos.x].is_some() { + if let Some((start, end)) = prev_move { + if start.y == pos.y + 1 + && start.x == pos.x + && end.y == pos.y - 1 + && end.x == pos.x + { + en_passant = + Some(SpecialMove::EnPassant(Position::new(pos.x, pos.y - 1))); + result.push(pos); + } + } + } + } + if let Some(pos) = self.test_pos(board, (-1, 1), false) { + if board.0[pos.y - 1][pos.x].is_some() { + if let Some((start, end)) = prev_move { + if start.y == pos.y + 1 + && start.x == pos.x + && end.y == pos.y - 1 + && end.x == pos.x + { + en_passant = + Some(SpecialMove::EnPassant(Position::new(pos.x, pos.y - 1))); + result.push(pos); + } + } + } + } + } + } else { + if let Some(pos) = self.test_pos(board, (0, -1), false) { + result.push(pos); + } + if self.pos.y == 6 && self.test_pos(board, (0, -1), false).is_some() { + if let Some(pos) = self.test_pos(board, (0, -2), false) { + result.push(pos) + } + } + if let Some(pos) = self.test_pos(board, (-1, -1), true) { + result.push(pos); + } + if let Some(pos) = self.test_pos(board, (1, -1), true) { + result.push(pos); + } + + // En passant for black + if self.pos.y == 3 { + if let Some(pos) = self.test_pos(board, (-1, -1), false) { + if board.0[pos.y + 1][pos.x].is_some() { + if let Some((start, end)) = prev_move { + if start.y == pos.y - 1 + && start.x == pos.x + && end.y == pos.y + 1 + && end.x == pos.x + { + en_passant = + Some(SpecialMove::EnPassant(Position::new(pos.x, pos.y + 1))); + result.push(pos); + } + } + } + } + if let Some(pos) = self.test_pos(board, (1, -1), false) { + if board.0[pos.y + 1][pos.x].is_some() { + if let Some((start, end)) = prev_move { + if start.y == pos.y - 1 + && start.x == pos.x + && end.y == pos.y + 1 + && end.x == pos.x + { + en_passant = + Some(SpecialMove::EnPassant(Position::new(pos.x, pos.y + 1))); + result.push(pos); + } + } + } + } + } + } + (result, en_passant) + } + + fn knight_moves(&self, board: &Board) -> Vec { + [ + (1, 2), + (-1, 2), + (1, -2), + (-1, -2), + (2, 1), + (2, -1), + (-2, 1), + (-2, -1), + ] + .iter() + .filter(|(x, y)| { + let (x, y) = (self.pos.x as isize + x, self.pos.y as isize + y); + x >= 0 + && y >= 0 + && x < SIZE as isize + && y < SIZE as isize + && (if let Some(piece) = &board.0[y as usize][x as usize] { + self.side != piece.side + } else { + true + }) + }) + .map(|(x, y)| Position { + x: (self.pos.x as isize + x) as usize, + y: (self.pos.y as isize + y) as usize, + }) + .collect() + } + + fn laser_test(&self, board: &Board, result: &mut Vec, (x, y): (isize, isize)) { + let mut i = 1; + while let Some(pos) = self.test_pos(board, (x * i, y * i), false) { + result.push(pos); + i += 1; + } + if let Some(pos) = self.test_pos(board, (x * i, y * i), true) { + result.push(pos); + } + } + + fn bishop_moves(&self, board: &Board) -> Vec { + let mut result = Vec::new(); + self.laser_test(board, &mut result, (1, 1)); + self.laser_test(board, &mut result, (-1, -1)); + self.laser_test(board, &mut result, (1, -1)); + self.laser_test(board, &mut result, (-1, 1)); + result + } + + fn rook_moves(&self, board: &Board) -> Vec { + let mut result = Vec::new(); + self.laser_test(board, &mut result, (0, 1)); + self.laser_test(board, &mut result, (0, -1)); + self.laser_test(board, &mut result, (1, 0)); + self.laser_test(board, &mut result, (-1, 0)); + result + } + + fn queen_moves(&self, board: &Board) -> Vec { + let mut result = self.rook_moves(board); + for pos in self.bishop_moves(board) { + result.push(pos); + } + result + } + + fn king_moves(&self, board: &Board) -> (Vec, Option) { + let mut result = Vec::new(); + let mut castle = None; + [ + (-1, -1), + (0, -1), + (1, -1), + (-1, 0), + (1, 0), + (-1, 1), + (0, 1), + (1, 1), + ] + .iter() + .for_each(|p| { + if let Some(pos) = self.test_pos(board, *p, false) { + result.push(pos); + } else if let Some(pos) = self.test_pos(board, *p, true) { + result.push(pos); + } + }); + + if self.move_count == 0 { + let mut lefty = true; + let mut righty = true; + + if let Some(rook) = &board.0[self.pos.y][0] { + if rook.move_count != 0 { + lefty = false; + } + } else { + lefty = false; + } + + for x in 1..4 { + if self.test_pos(board, (-(x as isize), 0), false).is_none() { + lefty = false; + break; + } + } + + if let Some(rook) = &board.0[self.pos.y][7] { + if rook.move_count != 0 { + righty = false; + } + } else { + righty = false; + } + + for x in 1..3 { + if self.test_pos(board, (x as isize, 0), false).is_none() { + righty = false; + break; + } + } + + if lefty { + result.push(Position::new(self.pos.x - 3, self.pos.y)); + } + if righty { + result.push(Position::new(self.pos.x + 2, self.pos.y)); + } + if lefty || righty { + castle = Some(SpecialMove::Castle((lefty, righty))); + } + } + (result, castle) + } +} + +struct Color { + r: u8, + g: u8, + b: u8, +} + +trait Colorize { + fn bg(&self, color: Color) -> String; + fn fg(&self, color: Color) -> String; + fn bold(&self) -> String; +} + +impl Colorize for T { + fn bg(&self, color: Color) -> String { + format!( + "\x1b[48;2;{};{};{}m{self}\x1b[0m", + color.r, color.g, color.b + ) + } + fn fg(&self, color: Color) -> String { + format!( + "\x1b[38;2;{};{};{}m{self}\x1b[0m", + color.r, color.g, color.b + ) + } + fn bold(&self) -> String { + format!("\x1b[1m{self}\x1b[0m",) + } +} + +use std::fmt; +impl fmt::Display for PieceKind { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + use PieceKind::*; + match self { + Pawn => write!(f, "Pa"), + Knight => write!(f, "Kn"), + Bishop => write!(f, "Bi"), + Rook => write!(f, "Ro"), + Queen => write!(f, "Qu"), + King => write!(f, "Ki"), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + enum GameResult { + CheckMate(Side), + StaleMate, + } + #[test] + fn poser() { + let b = Board::new(); + let p = Piece { + pos: Position { x: 0, y: 1 }, + kind: PieceKind::Pawn, + side: Side::White, + move_count: 0, + }; + let result = p.test_pos(&b, (0, 1), false); + assert_eq!(result, Some(Position { x: 0, y: 2 })); + } + + fn sim(moves: Vec<(Position, Position)>) -> Option { + let mut game = Game::new(); + for (piece_pos, square) in moves { + let res_pos = Position::new(piece_pos.x, piece_pos.y); + let mut piece = game.board.0[piece_pos.y][piece_pos.x].clone().unwrap(); + assert!(piece.side == game.turn); + let special_move = piece + .move_piece(&square, &game.board, game.moves.last()) + .unwrap(); + assert!( + handle_special_moves(special_move, &mut game, &mut piece, &piece_pos, &square) + .is_ok() + ); + game.turn = match game.turn { + Side::White => Side::Black, + Side::Black => Side::White, + }; + assert_ne!(piece_pos, res_pos); + game.moves.push((piece_pos, square)); + piece.move_count += 1; + let (x, y) = (piece.pos.x, piece.pos.y); + game.board.0[res_pos.y][res_pos.x] = None; + game.board.0[y][x] = Some(piece); + if game.board.count_moves(&game.turn, game.moves.last()) == 0 { + if let Some(loser) = game.board.check_for_check(game.moves.last()) { + // assert_eq!(Some((piece_pos, square)), moves.last()); + match loser { + Side::White => return Some(GameResult::CheckMate(Side::Black)), + Side::Black => return Some(GameResult::CheckMate(Side::White)), + } + } else { + return Some(GameResult::StaleMate); + } + } + } + None + } + + #[test] + fn en_passanter() { + let moves = vec![ + (Position::new(3, 1), Position::new(3, 3)), + (Position::new(4, 6), Position::new(4, 4)), + (Position::new(3, 3), Position::new(3, 4)), + (Position::new(4, 4), Position::new(4, 3)), + (Position::new(5, 1), Position::new(5, 3)), + (Position::new(4, 3), Position::new(5, 2)), + ]; + sim(moves); + + let moves = vec![ + (Position::new(3, 1), Position::new(3, 3)), + (Position::new(0, 6), Position::new(0, 4)), + (Position::new(3, 3), Position::new(3, 4)), + (Position::new(4, 6), Position::new(4, 4)), + (Position::new(3, 4), Position::new(4, 5)), + ]; + sim(moves); + } + + #[test] + fn castler() { + // Queenside + let moves = vec![ + (Position::new(3, 1), Position::new(3, 3)), // d4 + (Position::new(3, 6), Position::new(3, 4)), // d5 + (Position::new(1, 0), Position::new(2, 2)), // Knc3 + (Position::new(1, 7), Position::new(2, 5)), // Knc6 + (Position::new(2, 0), Position::new(4, 2)), // Be3 + (Position::new(2, 7), Position::new(4, 5)), // Be6 + (Position::new(3, 0), Position::new(3, 1)), + (Position::new(3, 7), Position::new(3, 6)), + (Position::new(4, 0), Position::new(1, 0)), + (Position::new(4, 7), Position::new(1, 7)), + ]; + sim(moves); + + //Kingside + let moves = vec![ + (Position::new(4, 1), Position::new(4, 3)), + (Position::new(4, 6), Position::new(4, 4)), + (Position::new(6, 0), Position::new(5, 2)), + (Position::new(6, 7), Position::new(5, 5)), + (Position::new(5, 0), Position::new(3, 2)), + (Position::new(5, 7), Position::new(3, 5)), + (Position::new(4, 0), Position::new(6, 0)), + (Position::new(4, 7), Position::new(6, 7)), + ]; + sim(moves); + } +} + +impl Position { + fn new(x: usize, y: usize) -> Self { + Self { x, y } + } +}