update project name

This commit is contained in:
2025-08-15 14:12:09 +02:00
parent 3613f35cc4
commit e2849f9180
2 changed files with 197 additions and 63 deletions

View File

@@ -1,6 +1,7 @@
[package]
name = "term_chess"
name = "term-chess"
version = "0.1.0"
edition = "2021"
[dependencies]
crossterm = "0.27.0"

View File

@@ -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)
}