update project name
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
[package]
|
||||
name = "term_chess"
|
||||
name = "term-chess"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
crossterm = "0.27.0"
|
||||
|
||||
257
src/main.rs
257
src/main.rs
@@ -1,3 +1,10 @@
|
||||
use crossterm::{
|
||||
event,
|
||||
event::{EnableMouseCapture, Event, KeyCode, MouseButton, MouseEventKind},
|
||||
execute, terminal,
|
||||
};
|
||||
use std::io;
|
||||
|
||||
const SIZE: usize = 8;
|
||||
const WHITE: Color = Color {
|
||||
r: 255,
|
||||
@@ -22,16 +29,24 @@ enum SpecialMoveError {
|
||||
SquareAttacked,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum InputParseError {
|
||||
MissingCoordinate,
|
||||
NumberParseError,
|
||||
}
|
||||
|
||||
struct Game {
|
||||
board: Board,
|
||||
turn: Side,
|
||||
moves: Vec<(Position, Position)>,
|
||||
moves: Vec<Move>,
|
||||
}
|
||||
|
||||
struct FinishedGame {
|
||||
board: Board,
|
||||
turn: Side,
|
||||
moves: Vec<Move>,
|
||||
move_point: usize,
|
||||
}
|
||||
|
||||
struct Move {
|
||||
from: Position,
|
||||
to: Position,
|
||||
piece: Option<Piece>,
|
||||
special_move: Option<SpecialMove>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
@@ -42,7 +57,7 @@ struct Piece {
|
||||
move_count: u32,
|
||||
}
|
||||
|
||||
#[derive(PartialEq)]
|
||||
#[derive(PartialEq, Clone)]
|
||||
enum SpecialMove {
|
||||
Castle((bool, bool)),
|
||||
EnPassant(Position),
|
||||
@@ -74,27 +89,41 @@ struct Position {
|
||||
struct Board([[Option<Piece>; SIZE]; SIZE]);
|
||||
|
||||
fn main() {
|
||||
let mut stdout = io::stdout();
|
||||
let mut game = Game::new();
|
||||
let mut msg = String::new();
|
||||
game.board.render("White to play.");
|
||||
let mut s = String::with_capacity(2500);
|
||||
game.board.render(&mut s, "White to play.");
|
||||
|
||||
loop {
|
||||
let start_pos = get_input("Select piece: ");
|
||||
let start_pos = input(&mut stdout);
|
||||
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());
|
||||
let pos = input(&mut stdout);
|
||||
|
||||
let move_pos = if let Some(m) = game.moves.last() {
|
||||
Some((m.from.clone(), m.to.clone()))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let move_result = piece.move_piece(&pos, &game.board, move_pos.as_ref());
|
||||
if let Ok(special_move) = move_result {
|
||||
if handle_special_moves(special_move, &mut game, &mut piece, &start_pos, &pos)
|
||||
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));
|
||||
game.moves.push(Move::new(
|
||||
start_pos,
|
||||
pos.clone(),
|
||||
game.board.0[pos.y][pos.x].clone(),
|
||||
special_move,
|
||||
));
|
||||
piece.move_count += 1;
|
||||
} else {
|
||||
piece.pos = start_pos;
|
||||
@@ -107,8 +136,15 @@ fn main() {
|
||||
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()) {
|
||||
|
||||
let move_pos = if let Some(m) = game.moves.last() {
|
||||
Some((m.from.clone(), m.to.clone()))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
if game.board.count_moves(&game.turn, move_pos.as_ref()) == 0 {
|
||||
if let Some(loser) = game.board.check_for_check(move_pos.as_ref()) {
|
||||
msg = match loser {
|
||||
Side::White => "Black wins with checkmate!".to_string(),
|
||||
Side::Black => "White wins with checkmate!".to_string(),
|
||||
@@ -116,24 +152,41 @@ fn main() {
|
||||
} else {
|
||||
msg = "Stalemate!".to_string();
|
||||
}
|
||||
game.board.render(&msg);
|
||||
game.board.render(&mut s, &msg);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
msg.push_str("Coordinates do not correspond to piece. ");
|
||||
msg.push_str("pos: ");
|
||||
msg.push_str(&start_pos.x.to_string());
|
||||
msg.push_str(", ");
|
||||
msg.push_str(&start_pos.y.to_string());
|
||||
}
|
||||
match game.turn {
|
||||
Side::White => msg.push_str("White to play."),
|
||||
Side::Black => msg.push_str("Black to play."),
|
||||
}
|
||||
game.board.render(&msg);
|
||||
game.board.render(&mut s, &msg);
|
||||
msg.clear();
|
||||
}
|
||||
std::thread::sleep(std::time::Duration::from_millis(500));
|
||||
|
||||
s.clear();
|
||||
game.board = Board::new();
|
||||
game.turn = Side::White;
|
||||
for i in 0..game.moves.len() {
|
||||
game.board.render(&mut s, "");
|
||||
std::thread::sleep(std::time::Duration::from_millis(500));
|
||||
game.next_move(i);
|
||||
}
|
||||
game.board.render(&mut s, "");
|
||||
std::thread::sleep(std::time::Duration::from_millis(500));
|
||||
exit();
|
||||
}
|
||||
|
||||
fn handle_special_moves(
|
||||
special_move: Option<SpecialMove>,
|
||||
special_move: &Option<SpecialMove>,
|
||||
game: &mut Game,
|
||||
piece: &mut Piece,
|
||||
start_pos: &Position,
|
||||
@@ -148,15 +201,22 @@ fn handle_special_moves(
|
||||
SpecialMove::Castle((left, right)) => {
|
||||
assert!(piece.kind == PieceKind::King);
|
||||
assert!(start_pos.y == pos.y);
|
||||
|
||||
let move_pos = if let Some(m) = game.moves.last() {
|
||||
Some((m.from.clone(), m.to.clone()))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
match start_pos.x.abs_diff(pos.x) {
|
||||
2 => {
|
||||
if right {
|
||||
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(),
|
||||
move_pos.as_ref(),
|
||||
) {
|
||||
return Err(SpecialMoveError::SquareAttacked);
|
||||
}
|
||||
@@ -166,7 +226,7 @@ fn handle_special_moves(
|
||||
rook.move_piece(
|
||||
&Position::new(pos.x - 1, pos.y),
|
||||
&game.board,
|
||||
game.moves.last(),
|
||||
move_pos.as_ref(),
|
||||
)
|
||||
.expect("f rook");
|
||||
game.board.0[pos.y][pos.x - 1] = Some(rook);
|
||||
@@ -178,14 +238,14 @@ fn handle_special_moves(
|
||||
}
|
||||
}
|
||||
3 => {
|
||||
if left {
|
||||
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(),
|
||||
move_pos.as_ref(),
|
||||
) {
|
||||
return Err(SpecialMoveError::SquareAttacked);
|
||||
}
|
||||
@@ -194,7 +254,7 @@ fn handle_special_moves(
|
||||
rook.move_piece(
|
||||
&Position::new(pos.x + 1, pos.y),
|
||||
&game.board,
|
||||
game.moves.last(),
|
||||
move_pos.as_ref(),
|
||||
)
|
||||
.expect("f moving rook");
|
||||
game.board.0[pos.y][pos.x + 1] = Some(rook);
|
||||
@@ -219,36 +279,101 @@ impl Game {
|
||||
moves: Vec::new(),
|
||||
}
|
||||
}
|
||||
fn next_move(&mut self, move_point: usize) {
|
||||
let piece_move = &self.moves[move_point];
|
||||
let (start, end) = (
|
||||
self.moves[move_point].from.clone(),
|
||||
self.moves[move_point].to.clone(),
|
||||
);
|
||||
let mut piece = self.board.0[piece_move.from.y][piece_move.from.x]
|
||||
.clone()
|
||||
.unwrap();
|
||||
self.board.0[piece_move.to.y][piece_move.to.x] =
|
||||
self.board.0[piece_move.from.y][piece_move.from.x].take();
|
||||
handle_special_moves(
|
||||
&self.moves[move_point].special_move.clone(),
|
||||
self,
|
||||
&mut piece,
|
||||
&start,
|
||||
&end,
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
impl Move {
|
||||
fn new(
|
||||
from: Position,
|
||||
to: Position,
|
||||
piece: Option<Piece>,
|
||||
special_move: Option<SpecialMove>,
|
||||
) -> Self {
|
||||
Self {
|
||||
from,
|
||||
to,
|
||||
piece,
|
||||
special_move,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_input(input: &str) -> Result<Position, InputParseError> {
|
||||
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 FinishedGame {
|
||||
fn new(moves: Vec<Move>) -> Self {
|
||||
Self {
|
||||
board: Board::new(),
|
||||
turn: Side::White,
|
||||
move_point: 0,
|
||||
moves,
|
||||
}
|
||||
}
|
||||
|
||||
fn next_move(&mut self) {
|
||||
let piece_move = &self.moves[self.move_point];
|
||||
self.board.0[piece_move.to.y][piece_move.to.x] =
|
||||
self.board.0[piece_move.from.y][piece_move.from.x].take();
|
||||
// handle_special_moves(&self.moves[self.move_point].special_move, &mut self.);
|
||||
self.move_point += 1;
|
||||
}
|
||||
|
||||
fn prev_move(&mut self) {
|
||||
let piece_move = &self.moves[self.move_point];
|
||||
self.board.0[piece_move.from.y][piece_move.from.x] =
|
||||
self.board.0[piece_move.to.y][piece_move.to.x].take();
|
||||
self.board.0[piece_move.to.y][piece_move.to.x] = piece_move.piece.clone();
|
||||
}
|
||||
}
|
||||
|
||||
fn input(stdout: &mut io::Stdout) -> Position {
|
||||
execute!(stdout, EnableMouseCapture).unwrap();
|
||||
loop {
|
||||
if event::poll(std::time::Duration::MAX).unwrap() {
|
||||
while let Ok(r) = event::read() {
|
||||
if let Event::Mouse(event) = r {
|
||||
if let MouseEventKind::Down(MouseButton::Left) = event.kind {
|
||||
let (x, y) = (
|
||||
((event.column as i32 - 1) / 2) as usize,
|
||||
(9 - event.row as i32) as usize,
|
||||
);
|
||||
if x > 7 || y > 7 {
|
||||
continue;
|
||||
}
|
||||
let pos = Position::new(x, y);
|
||||
return pos;
|
||||
}
|
||||
} else if let Event::Key(key) = r {
|
||||
if key.code == KeyCode::Char('q') {
|
||||
exit();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn exit() -> ! {
|
||||
print!("\x1b[?25h");
|
||||
terminal::disable_raw_mode().unwrap();
|
||||
std::process::exit(0);
|
||||
}
|
||||
|
||||
impl Board {
|
||||
@@ -298,31 +423,32 @@ impl Board {
|
||||
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('+');
|
||||
fn render(&self, s: &mut String, msg: &str) {
|
||||
s.clear();
|
||||
s.push('┏');
|
||||
s.push_str(&"━".repeat(SIZE * 2));
|
||||
s.push('┓');
|
||||
s.push('\n');
|
||||
for (y, rank) in self.0.iter().rev().enumerate() {
|
||||
s.push('|');
|
||||
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));
|
||||
s.push_str(&piece.kind.to_string().fg(fg_color).bg(bg_color).bold());
|
||||
} else {
|
||||
s.push_str(&" ".bg(bg_color));
|
||||
}
|
||||
}
|
||||
s.push_str(&format!("|{}\n", (SIZE - y)));
|
||||
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");
|
||||
s.push('┗');
|
||||
s.push_str(&"━".repeat(SIZE * 2));
|
||||
s.push_str("┛\n A B C D E F G H ");
|
||||
print!("\x1b[?25l\x1b[2J\x1b[H");
|
||||
terminal::disable_raw_mode().unwrap();
|
||||
println!("{}\n{}", msg, s);
|
||||
terminal::enable_raw_mode().unwrap();
|
||||
}
|
||||
fn new() -> Self {
|
||||
Board([
|
||||
@@ -565,6 +691,13 @@ impl Piece {
|
||||
return Err(MoveError::InCheck);
|
||||
}
|
||||
}
|
||||
|
||||
match (self.side.clone(), self.pos.y) {
|
||||
(Side::White, 7) => self.kind = PieceKind::Queen,
|
||||
(Side::Black, 0) => self.kind = PieceKind::Queen,
|
||||
_ => (),
|
||||
}
|
||||
|
||||
Ok(special_move)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user