Compare commits

..

19 Commits

Author SHA1 Message Date
Varga Dávid Lajos
f9a302c9a0 implemented method add_checker for CheckInfo in bitboard::checkinfo.rs 2025-11-15 12:40:52 +01:00
Varga Dávid Lajos
ad530e9155 added constructor for struct CheckInfo in bitboard::checkinfo.rs 2025-11-15 12:39:35 +01:00
Varga Dávid Lajos
182aa59ee1 defined shape of struct CheckInfo in bitboard::checkinfo.rs 2025-11-15 12:37:47 +01:00
Varga Dávid Lajos
296f1f6c3a added file and module structure for checkinfo.rs 2025-11-15 12:36:11 +01:00
Varga Dávid Lajos
f7355f8e74 implemented use of calc_pinned_squares in board.rs 2025-11-15 12:33:18 +01:00
Varga Dávid Lajos
cd58a7a321 added methods for detecting pinned pieces 2025-11-15 12:30:01 +01:00
Varga Dávid Lajos
2beb8ab303 added file and module structure for bitboard::legality.rs 2025-11-15 10:31:02 +01:00
Varga Dávid Lajos
5425ccc5cd added partial constructor for starting from a fen position 2025-11-15 10:26:31 +01:00
Varga Dávid Lajos
1104c8e6c5 added helper method for fen parsing to utils.rs 2025-11-15 10:21:00 +01:00
Varga Dávid Lajos
274ffcf5ec added method to get the current square of a king 2025-11-15 10:14:37 +01:00
Varga Dávid Lajos
5854dbc20b set each field to pub(in super) for later use 2025-11-15 10:12:52 +01:00
Varga Dávid Lajos
f36a196b2f added getters for fields 2025-11-15 10:11:34 +01:00
Varga Dávid Lajos
11f26809df used calc_occupancy and calc_piece_board methods in the initial state constructor 2025-11-15 10:07:23 +01:00
Varga Dávid Lajos
c88fbe68e3 added and used necessary methods the initial state constructor 2025-11-15 10:06:12 +01:00
Varga Dávid Lajos
7f4c53ddb7 added partial constructor for initial board state to board.rs 2025-11-15 10:03:24 +01:00
Varga Dávid Lajos
38b38845d6 added constructor for clear board to board.rs 2025-11-15 09:58:38 +01:00
Varga Dávid Lajos
66dd2877b2 defined board representation shape in board.rs 2025-11-15 09:56:17 +01:00
Varga Dávid Lajos
3cd53c7d70 added file and module structure for board representation 2025-11-15 09:51:07 +01:00
Bence
5b3bdc750f Merge pull request #8 from htamas1210/Server/websocket
Server/websocket
2025-11-13 19:12:00 +01:00
7 changed files with 277 additions and 213 deletions

View File

@@ -1,2 +1,6 @@
mod attackmaps;
mod utils;
mod utils;
mod legality;
mod checkinfo;
pub mod board;

View File

@@ -0,0 +1,182 @@
use super::utils::try_get_square_number_from_notation;
pub struct Board {
pub(in super) bitboards: [u64; 12], // 0-5 -> white pieces (P, N, B, R, Q, K), 6-11 -> black pieces (p, n, b, r, q, k)
pub(in super) piece_board: [u8; 64], // same as board indexes, 12 -> empty square
pub(in super) occupancy: [u64; 3], // 0 -> white, 1 -> black, 2 -> combined
pub(in super) castling_rights: u8, // 0b0000_KQkq
pub(in super) pinned_squares: [u8; 64], // 0 -> E-W, 1 -> NE-SW, 2 -> N-S, 3 -> SE-NW, 4 -> no pin
pub(in super) pin_mask: u64, // 1 -> pin, 0 -> no pin
pub(in super) en_passant_square: u64, // 1 -> ep square, 0 -> no ep square
pub(in super) side_to_move: u8 // 0 -> white to play, 1 -> black to play
}
impl Board {
pub fn new_clear() -> Self {
let mut bit_board: Self = Self {
bitboards: [0x0000_0000_0000_0000; 12],
piece_board: [12; 64],
occupancy: [0x0000_0000_0000_0000; 3],
castling_rights: 0b0000_0000,
pinned_squares: [4; 64],
pin_mask: 0u64,
en_passant_square: 0x0000_0000_0000_0000,
side_to_move: 0
};
return bit_board;
}
pub fn new() -> Self {
let mut bit_board: Board = Self {
bitboards: [0x0000_0000_0000_FF00,
0x0000_0000_0000_0042,
0x0000_0000_0000_0024,
0x0000_0000_0000_0081,
0x0000_0000_0000_0008,
0x0000_0000_0000_0010,
0x00FF_0000_0000_0000,
0x4200_0000_0000_0000,
0x2400_0000_0000_0000,
0x8100_0000_0000_0000,
0x0800_0000_0000_0000,
0x1000_0000_0000_0000],
piece_board: [12; 64],
occupancy: [0; 3],
castling_rights: 0b0000_1111,
pinned_squares: [4; 64],
pin_mask: 0u64,
en_passant_square: 0x0000_0000_0000_0000,
side_to_move: 0
};
bit_board.calc_occupancy();
bit_board.calc_piece_board();
return bit_board;
}
pub fn build(fen: &str) -> Self {
let mut board: Board = Board::new_clear();
let mut col: i32 = 0;
let mut row: i32 = 7;
let pieces: [char; 12] = ['p', 'n', 'b', 'r', 'q', 'k', 'P', 'N', 'B', 'R', 'Q', 'K'];
let mut coming_up: &str = fen;
for (i, c) in coming_up.chars().enumerate() {
if pieces.contains(&c) {
// board.place_piece(row*8 + col, c);
col += 1;
}
else if ('1'..='8').contains(&c) {
col += c.to_string().parse::<i32>().unwrap();
}
else if c == '/' {
row -= 1;
col = 0;
}
else {
coming_up = &coming_up[i+1..];
break;
}
}
board.calc_occupancy();
match coming_up.chars().next().unwrap() {
'w' => board.side_to_move = 0,
'b' => board.side_to_move = 1,
_ => panic!("invalid fen notation / to be handled later")
}
coming_up = &coming_up[2..];
for (i, c) in coming_up.chars().enumerate() {
match c {
'K' => board.castling_rights |= 1 << 3,
'Q' => board.castling_rights |= 1 << 2,
'k' => board.castling_rights |= 1 << 1,
'q' => board.castling_rights |= 1,
'-' => {
coming_up = &coming_up[i+2..];
break;
}
_ => {
coming_up = &coming_up[i+1..];
break;
}
}
}
match coming_up.chars().next().unwrap() {
'-' => {
coming_up = &coming_up[1..];
}
_ => {
let notation = coming_up.split(' ').next().unwrap();
if let Ok(epsq_index) = try_get_square_number_from_notation(notation) {
board.en_passant_square = 1 << epsq_index;
}
}
}
board.calc_pinned_squares();
board.calc_piece_board();
return board;
}
#[inline(always)]
pub fn bitboards(&self, index: usize) -> u64 {
return self.bitboards[index];
}
#[inline(always)]
pub fn piece_board(&self, sq: u8) -> u8 {
return self.piece_board[sq as usize];
}
#[inline(always)]
pub fn occupancy(&self, side: usize) -> u64 {
return self.occupancy[side];
}
#[inline(always)]
pub fn castling_rights(&self) -> u8 {
return self.castling_rights;
}
#[inline(always)]
pub fn pinned_squares(&self, sq: usize) -> u8 {
return self.pinned_squares[sq];
}
#[inline(always)]
pub fn pin_mask(&self) -> u64 {
return self.pin_mask;
}
#[inline(always)]
pub fn en_passant_square(&self) -> u64 {
return self.en_passant_square;
}
#[inline(always)]
pub fn side_to_move(&self) -> u8 {
return self.side_to_move;
}
#[inline(always)]
pub fn current_king_square(&self) -> u32 {
return if self.side_to_move == 0 { self.bitboards[5].trailing_zeros() } else { self.bitboards[11].trailing_zeros() };
}
fn calc_occupancy(&mut self) {
self.occupancy = [0u64; 3];
for b in 0..6 {
self.occupancy[0] |= self.bitboards[b];
}
for b in 6..12 {
self.occupancy[1] |= self.bitboards[b];
}
self.occupancy[2] = self.occupancy[0] | self.occupancy[1];
}
fn calc_piece_board(&mut self) {
for sq in 0..64 {
for b in 0..12 {
if (self.bitboards[b as usize] & 1 << sq) != 0 {
self.piece_board[sq] = b;
}
}
}
}
}

View File

@@ -0,0 +1,21 @@
pub struct CheckInfo {
pub check_count: u8,
pub move_mask: u64
}
impl CheckInfo {
pub fn new() -> Self {
return Self {
check_count: 0,
move_mask: 0xFFFF_FFFF_FFFF_FFFF
}
}
#[inline(always)]
pub fn add_checker(&mut self, move_mask: u64) {
self.move_mask &= move_mask;
self.check_count += 1;
}
}

View File

@@ -0,0 +1,48 @@
use super::board::Board;
use super::attackmaps::RAY_TABLE;
impl Board {
pub(in super) fn calc_pinned_squares(&mut self) {
self.pinned_squares = [4; 64];
self.pin_mask = 0u64;
let friendly_pieces: u64 = self.occupancy[self.side_to_move as usize];
let offset: usize = 6 * self.side_to_move as usize;
let king_board: u64 = self.bitboards[5 + offset];
let king_sq: u32 = king_board.trailing_zeros();
let opponent_queen_bishop_mask: u64 = self.bitboards[8 - offset] | self.bitboards[10 - offset];
let opponent_queen_rook_mask: u64 = self.bitboards[9 - offset] | self.bitboards[10 - offset];
// Queen-Rook directions
self.set_pinned_in_ray_direction(king_sq, friendly_pieces, opponent_queen_rook_mask, 0);
self.set_pinned_in_ray_direction(king_sq, friendly_pieces, opponent_queen_rook_mask, 2);
self.set_pinned_in_ray_direction(king_sq, friendly_pieces, opponent_queen_rook_mask, 4);
self.set_pinned_in_ray_direction(king_sq, friendly_pieces, opponent_queen_rook_mask, 6);
// Queen-Bishop directions
self.set_pinned_in_ray_direction(king_sq, friendly_pieces, opponent_queen_bishop_mask, 1);
self.set_pinned_in_ray_direction(king_sq, friendly_pieces, opponent_queen_bishop_mask, 3);
self.set_pinned_in_ray_direction(king_sq, friendly_pieces, opponent_queen_bishop_mask, 5);
self.set_pinned_in_ray_direction(king_sq, friendly_pieces, opponent_queen_bishop_mask, 7);
}
pub(in super) fn set_pinned_in_ray_direction(&mut self, king_sq: u32, friendly_pieces: u64, attackers: u64, dir: u8) {
let is_up: bool = dir / 4 == 0;
let mask: u64 = RAY_TABLE[king_sq as usize][dir as usize];
let blockers: u64 = self.occupancy[2] & mask;
if blockers == 0 { return; }
let first_blocker_sq: u32 = if is_up { blockers.trailing_zeros() } else { 63 - blockers.leading_zeros() };
if (friendly_pieces & 1 << first_blocker_sq) != 0 {
let blockers: u64 = blockers & !(1 << first_blocker_sq);
if blockers == 0 { return; }
let second_blocker_sq: u32 = if is_up { blockers.trailing_zeros() } else { 63 - blockers.leading_zeros() };
if (attackers & 1 << second_blocker_sq) != 0 {
self.pinned_squares[first_blocker_sq as usize] = dir % 4;
self.pin_mask |= 1 << first_blocker_sq;
}
}
}
}

View File

@@ -27,6 +27,27 @@ pub fn notation_from_square_number(sq: u8) -> String {
return notation;
}
pub fn try_get_square_number_from_notation(notation: &str) -> Result<u8, ()> {
let file = match notation.chars().nth(0).unwrap() {
'a' => 0,
'b' => 1,
'c' => 2,
'd' => 3,
'e' => 4,
'f' => 5,
'g' => 6,
'h' => 7,
_ => { return Result::Err(()); }
};
if let Some(rank) = notation.chars().nth(1) {
return Result::Ok(file + 8 * (rank.to_digit(10).unwrap() as u8) - 8);
}
else {
return Result::Err(());
}
}
// <----- TESTS ----->

View File

@@ -14,7 +14,3 @@ url = "2.5.7"
uuid = {version = "1.18.1", features = ["v4", "serde"] }
anyhow = "1.0.100"
rand = "0.9.2"
[[bin]]
name = "client"
path = "src/bin/client.rs"

View File

@@ -1,208 +0,0 @@
use futures_util::{SinkExt, StreamExt};
use serde::{Deserialize, Serialize};
use std::io::{self, Write};
use tokio_tungstenite::{connect_async, tungstenite::Message};
use url::Url;
#[derive(Serialize, Deserialize, Debug)]
#[serde(tag = "type")]
enum ClientMessage {
Join { username: String },
FindMatch,
Move { from: String, to: String },
Resign,
Chat { text: String },
}
#[derive(Serialize, Deserialize, Debug)]
struct ServerMessage {
#[serde(rename = "type")]
message_type: String,
player_id: Option<String>,
match_id: Option<String>,
opponent: Option<String>,
color: Option<String>,
reason: Option<String>,
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("Knightly Chess Client");
println!("========================");
// Get server address from user
print!("Enter server address [ws://127.0.0.1:9001]: ");
io::stdout().flush()?;
let mut server_addr = String::new();
io::stdin().read_line(&mut server_addr)?;
let server_addr = server_addr.trim();
let server_addr = if server_addr.is_empty() {
"ws://127.0.0.1:9001".to_string()
} else {
server_addr.to_string()
};
// Connect to server
println!("Connecting to {}...", server_addr);
let url = Url::parse(&server_addr)?;
let (ws_stream, _) = connect_async(url).await?;
println!("Connected to server!");
let (mut write, mut read) = ws_stream.split();
// Spawn a task to handle incoming messages
let read_handle = tokio::spawn(async move {
while let Some(message) = read.next().await {
match message {
Ok(msg) => {
if msg.is_text() {
let text = msg.to_text().unwrap();
println!("\nServer: {}", text);
// Try to parse as structured message
if let Ok(parsed) = serde_json::from_str::<ServerMessage>(text) {
match parsed.message_type.as_str() {
"welcome" => {
if let Some(player_id) = parsed.player_id {
println!("Welcome! Your player ID: {}", player_id);
}
}
"match_found" => {
if let (Some(opponent), Some(color), Some(match_id)) =
(parsed.opponent, parsed.color, parsed.match_id)
{
println!(
"Match found! Opponent: {}, Color: {}, Match ID: {}",
opponent, color, match_id
);
}
}
"error" => {
if let Some(reason) = parsed.reason {
println!("Error: {}", reason);
}
}
_ => {}
}
}
}
}
Err(e) => {
eprintln!("Error receiving message: {}", e);
break;
}
}
}
});
// Main loop for sending messages
println!("\nAvailable commands:");
println!(" join <username> - Join the server");
println!(" findmatch - Find a match");
println!(" move <from> <to> - Make a move (e.g., move e2 e4)");
println!(" chat <message> - Send chat message");
println!(" resign - Resign from current game");
println!(" quit - Exit client");
println!();
loop {
print!("➡️ Enter command: ");
io::stdout().flush()?;
let mut input = String::new();
io::stdin().read_line(&mut input)?;
let input = input.trim();
if input.is_empty() {
continue;
}
let parts: Vec<&str> = input.split_whitespace().collect();
let command = parts[0].to_lowercase();
match command.as_str() {
"quit" | "exit" => {
println!("👋 Goodbye!");
break;
}
"join" => {
if parts.len() >= 2 {
let username = parts[1..].join(" ");
let message = ClientMessage::Join { username };
send_message(&mut write, &message).await?;
} else {
println!("Usage: join <username>");
}
}
"findmatch" | "find" => {
let message = ClientMessage::FindMatch;
send_message(&mut write, &message).await?;
println!("🔍 Searching for a match...");
}
"move" => {
if parts.len() >= 3 {
let from = parts[1].to_string();
let to = parts[2].to_string();
let message = ClientMessage::Move { from, to };
send_message(&mut write, &message).await?;
println!("♟️ Sent move: {} -> {}", parts[1], parts[2]);
} else {
println!("Usage: move <from> <to> (e.g., move e2 e4)");
}
}
"chat" => {
if parts.len() >= 2 {
let text = parts[1..].join(" ");
let message = ClientMessage::Chat { text };
send_message(&mut write, &message).await?;
} else {
println!("Usage: chat <message>");
}
}
"resign" => {
let message = ClientMessage::Resign;
send_message(&mut write, &message).await?;
println!("Resigned from current game");
}
"help" => {
print_help();
}
_ => {
println!(
"Unknown command: {}. Type 'help' for available commands.",
command
);
}
}
}
// Cleanup
read_handle.abort();
Ok(())
}
async fn send_message(
write: &mut futures_util::stream::SplitSink<
tokio_tungstenite::WebSocketStream<
tokio_tungstenite::MaybeTlsStream<tokio::net::TcpStream>,
>,
Message,
>,
message: &ClientMessage,
) -> Result<(), Box<dyn std::error::Error>> {
let json = serde_json::to_string(message)?;
write.send(Message::Text(json)).await?;
Ok(())
}
fn print_help() {
println!("\n📖 Available Commands:");
println!(" join <username> - Register with a username");
println!(" findmatch - Enter matchmaking queue");
println!(" move <from> <to> - Make a chess move");
println!(" chat <message> - Send chat to opponent");
println!(" resign - Resign from current game");
println!(" help - Show this help");
println!(" quit - Exit the client");
println!();
}