Compare commits

..

47 Commits
v116 ... v132

Author SHA1 Message Date
Hatvani Tamás
1c1b9a96d9 Import time module and add sleep in data upload
Added time.sleep to control row insertion rate.
2025-11-19 17:34:44 +01:00
Hatvani Tamás
e99b2defa3 Merge pull request #18 from htamas1210/Engine/bitmove
Engine/bitmove
2025-11-19 17:10:03 +01:00
Varga Dávid Lajos
db333a693f added method uci_notation to struct BitMove 2025-11-18 17:38:48 +01:00
Varga Dávid Lajos
fd0d26486b switched from public fields to getters for struct BitMove 2025-11-18 17:30:04 +01:00
Varga Dávid Lajos
e1f4ae717e added constructor for castling moves in bitboard::bitmove.rs 2025-11-18 17:24:11 +01:00
Varga Dávid Lajos
7b9c1edbab added constructor for capture moves in bitboard::bitmove.rs 2025-11-18 17:23:07 +01:00
Varga Dávid Lajos
05294a7736 changed name of value of enum BitMoveType to align with Rust naming conventions 2025-11-18 17:20:40 +01:00
Varga Dávid Lajos
d8da808580 added enum BitMoveType to bitboard::bitmove.rs for readability 2025-11-18 17:19:31 +01:00
Varga Dávid Lajos
c420d8b3dd added constructor for quiet moves in bitboard::bitmove.rs 2025-11-18 17:17:11 +01:00
Varga Dávid Lajos
57af3aaae3 defined shape of struct bitboard::bitmove::BitMove 2025-11-18 17:09:39 +01:00
Varga Dávid Lajos
887b9abed8 adde file and module structure for bitboard::bitmove.rs 2025-11-18 17:02:07 +01:00
vargadavidlajos
a862d31c7d Merge pull request #17 from htamas1210/Server/event
Server/event
2025-11-18 16:58:29 +01:00
48f66aac75 added engine as a crate into server project, implemented request moves event 2025-11-18 16:43:14 +01:00
Hatvani Tamás
431c65e075 Merge pull request #16 from htamas1210/Engine/check-test
Engine/check test
2025-11-18 16:41:49 +01:00
Varga Dávid Lajos
7e64a7ca16 fixed incorrect test parameter in bitboard::legality::tests::check_test_test 2025-11-18 16:20:42 +01:00
Varga Dávid Lajos
6e0efc76f3 added missing method place_piece to struct bitboard::Board 2025-11-18 16:18:19 +01:00
Varga Dávid Lajos
1a9d7ad460 added tests for method bitboard::legality::check_test 2025-11-18 16:15:28 +01:00
6dfe92f211 Merge branch 'master' into Server/event 2025-11-18 16:12:07 +01:00
Varga Dávid Lajos
9209f1c4e0 implemented method check_test in bitboard::checktest.rs 2025-11-18 12:14:22 +01:00
4bb485e833 added findmatch event 2025-11-18 09:28:07 +01:00
Bence
3ebfc61ac7 Merge pull request #15 from htamas1210/Engine/library-functions
Engine/library functions
2025-11-17 15:04:28 +01:00
Hatvani Tamás
d8d8b58ed7 Uploaded presentation for class 2025-11-17 12:24:42 +01:00
Varga Dávid Lajos
a8970053f9 added constructor for castling moves in chessmove.rs 2025-11-16 19:05:52 +01:00
Varga Dávid Lajos
fa0a93172d added constructore for captures in chessmove.rs 2025-11-16 19:01:53 +01:00
Varga Dávid Lajos
bb74a486d8 added constructor for quiet moves in chessmove.rs 2025-11-16 19:00:00 +01:00
Varga Dávid Lajos
d1932762f9 removed field is_promotion from struct ChessMove and added an Option for field promotion_piece 2025-11-16 18:33:04 +01:00
Varga Dávid Lajos
9181b6c4ca added all arguments constructor for struct BoardSquare 2025-11-16 13:51:10 +01:00
Varga Dávid Lajos
b703d8b9a1 added parameterless constructor for struct BoardSquare 2025-11-16 13:44:41 +01:00
Varga Dávid Lajos
acaa7d078a added new library function: get_board_after_move 2025-11-15 20:40:58 +01:00
Varga Dávid Lajos
30448b5f3d switched return type of is_game_over from bool to GameEnd 2025-11-15 20:37:06 +01:00
Varga Dávid Lajos
ff38c5c475 added struct GameEnd 2025-11-15 20:28:33 +01:00
Varga Dávid Lajos
42daeb9971 added file and module structure for gameend.rs 2025-11-15 20:15:17 +01:00
Varga Dávid Lajos
c8b6bc35d5 added library functions: get_available_moves and is_game_over 2025-11-15 20:08:54 +01:00
Varga Dávid Lajos
5f2a4e1721 added struct ChessMove 2025-11-15 19:57:11 +01:00
c77e534ed8 removed event system file as it was unused and we do not neet it anymore 2025-11-15 19:41:48 +01:00
Varga Dávid Lajos
5c42b6e52a added enum MoveType 2025-11-15 19:37:30 +01:00
Varga Dávid Lajos
f7364e7939 added file and module structure for movetype.rs 2025-11-15 19:35:17 +01:00
1c92918692 finished join event 2025-11-15 19:33:40 +01:00
Varga Dávid Lajos
b4a1f0820f added struct BoardSquare 2025-11-15 19:26:21 +01:00
Varga Dávid Lajos
6d9d5c49ae added file and module structure for boardsquare.rs 2025-11-15 19:24:37 +01:00
Varga Dávid Lajos
1ca44b472b added enum PieceType 2025-11-15 19:21:46 +01:00
Varga Dávid Lajos
79aabeac8c added file and module structure for piecetype.rs 2025-11-15 19:17:50 +01:00
Varga Dávid Lajos
5eb85dab89 added file and module structure for file: chessmove.rs 2025-11-15 19:15:30 +01:00
Varga Dávid Lajos
291b7e71a2 added crate root file lib.rs for library crate 2025-11-15 19:14:08 +01:00
cca852d466 Working on event system, join implemented 2025-11-15 18:32:43 +01:00
a098c8051a planning done on how the event system works 2025-11-15 16:41:11 +01:00
e599722e45 added client for testing connection and events 2025-11-15 14:44:44 +01:00
20 changed files with 625 additions and 176 deletions

View File

@@ -20,7 +20,7 @@ jobs:
echo "$GOOGLE_SERVICE_ACCOUNT_JSON" > service_account.json
python <<'PYCODE'
import gspread, json, subprocess
import gspread, json, subprocess, time
creds = json.load(open("service_account.json"))
gc = gspread.service_account_from_dict(creds)
@@ -38,6 +38,7 @@ jobs:
for i, row in enumerate(rows_to_append):
worksheet.insert_row(row, start_row + i)
time.sleep(1)
with open(f"{v}/test_data.log", "r") as f:

BIN
Knightly prezentáció.pptx Normal file

Binary file not shown.

View File

@@ -4,4 +4,6 @@ version = "0.1.0"
edition = "2024"
[dependencies]
once_cell = "1.19"
once_cell = "1.19"
serde = { version = "1", features = ["derive"] }
serde_json = "1"

View File

@@ -3,5 +3,6 @@ mod utils;
mod legality;
mod checkinfo;
mod attacks;
mod bitmove;
pub mod board;

View File

@@ -0,0 +1,77 @@
use super::utils::*;
#[derive(Copy, Clone, PartialEq, Eq)]
pub struct BitMove {
move_type: BitMoveType,
from_square: u8,
to_square: u8,
promotion_piece: Option<u8>
}
impl BitMove {
#[inline]
pub fn quiet(from: u8, to: u8, promotion_piece: Option<u8>) -> Self {
return Self {
move_type: BitMoveType::Quiet,
from_square: from,
to_square: to,
promotion_piece: promotion_piece
};
}
#[inline]
pub fn capture(from: u8, to: u8, promotion_piece: Option<u8>) -> Self {
return Self {
move_type: BitMoveType::Capture,
from_square: from,
to_square: to,
promotion_piece: promotion_piece
};
}
#[inline]
pub fn castle(from: u8, to: u8) -> Self {
return Self {
move_type: BitMoveType::Castle,
from_square: from,
to_square: to,
promotion_piece: None
};
}
#[inline(always)]
pub fn move_type(&self) -> BitMoveType {
return self.move_type;
}
#[inline(always)]
pub fn from_square(&self) -> u8 {
return self.from_square;
}
#[inline(always)]
pub fn to_square(&self) -> u8 {
return self.to_square;
}
#[inline(always)]
pub fn promotion_piece(&self) -> Option<u8> {
return self.promotion_piece;
}
pub fn uci_notation(&self) -> String {
let mut notation = notation_from_square_number(self.from_square());
notation.push_str(&notation_from_square_number(self.to_square()));
if let Some(promotion_piece) = self.promotion_piece {
notation.push(get_character_by_piece_id(promotion_piece).to_ascii_lowercase());
}
return notation;
}
}
#[derive(Copy, Clone, PartialEq, Eq)]
pub enum BitMoveType {
Quiet,
Capture,
Castle,
EnPassant
}

View File

@@ -64,7 +64,7 @@ impl Board {
for (i, c) in coming_up.chars().enumerate() {
if pieces.contains(&c) {
// board.place_piece(row*8 + col, c);
board.place_piece(row*8 + col, c);
col += 1;
}
else if ('1'..='8').contains(&c) {
@@ -179,4 +179,21 @@ impl Board {
}
}
pub fn place_piece(&mut self, sq: i32, piece: char) {
match piece {
'p' => {self.bitboards[6] |= 1 << sq}
'n' => {self.bitboards[7] |= 1 << sq}
'b' => {self.bitboards[8] |= 1 << sq}
'r' => {self.bitboards[9] |= 1 << sq}
'q' => {self.bitboards[10] |= 1 << sq}
'k' => {self.bitboards[11] |= 1 << sq}
'P' => {self.bitboards[0] |= 1 << sq}
'N' => {self.bitboards[1] |= 1 << sq}
'B' => {self.bitboards[2] |= 1 << sq}
'R' => {self.bitboards[3] |= 1 << sq}
'Q' => {self.bitboards[4] |= 1 << sq}
'K' => {self.bitboards[5] |= 1 << sq}
_ => ()
}
}
}

View File

@@ -1,8 +1,56 @@
use super::board::Board;
use super::attackmaps::RAY_TABLE;
use super::checkinfo::CheckInfo;
use super::attacks::get_raycast_from_square_in_direction;
impl Board {
pub fn check_test(&self) -> CheckInfo {
let mut check_info: CheckInfo = CheckInfo::new();
let offset: usize = 6 * self.side_to_move as usize;
let king: u64 = self.bitboards[5 + offset];
let king_sq = king.trailing_zeros() as usize;
let occupancy = self.occupancy[2];
// queen-rook checks (+)
let attacker_mask = self.bitboards[10 - offset] | self.bitboards[9 - offset];
for dir in [0, 2, 4, 6] {
let threat_mask: u64 = get_raycast_from_square_in_direction(occupancy, king_sq, dir);
if threat_mask & attacker_mask != 0 {
check_info.add_checker(threat_mask);
}
}
// queen-bishop checks (x)
let attacker_mask = self.bitboards[10 - offset] | self.bitboards[8 - offset];
for dir in [1, 3, 5, 7] {
let threat_mask = get_raycast_from_square_in_direction(occupancy, king_sq, dir);
if threat_mask & attacker_mask != 0 {
check_info.add_checker(threat_mask);
}
}
// knight checks (L)
let attacker_mask = self.bitboards[7 - offset];
let threat_mask = self.get_pseudo_knight_moves(king_sq as u32);
let checker = threat_mask & attacker_mask;
if checker != 0 {
check_info.add_checker(checker);
}
// pawn checks (v)
let attacker_mask = self.bitboards[6 - offset];
let threat_mask = self.get_pseudo_pawn_captures(king_sq as u32);
let checker = threat_mask & attacker_mask;
if checker != 0 {
check_info.add_checker(checker);
}
return check_info;
}
pub(in super) fn calc_pinned_squares(&mut self) {
self.pinned_squares = [4; 64];
self.pin_mask = 0u64;
@@ -45,4 +93,35 @@ impl Board {
}
}
}
// <----- TESTS ----->
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn check_test_test() {
let fens = [
"rnb1k1nr/pppppppp/4q3/8/1b6/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1", // no check
"rnb1k1nr/pppppppp/4q3/8/1b1P4/8/PPP1PPPP/RNBQKBNR w KQkq d3 0 1", // single check
"rnb1k1nr/ppp1p2p/3pq1p1/8/1b1P1P2/8/PPP2PPP/RNBQKBNR w KQkq - 0 1" // double check
];
let expected_results = [
CheckInfo { check_count: 0, move_mask: 0xFFFF_FFFF_FFFF_FFFF },
CheckInfo { check_count: 1, move_mask: 0x0000_0000_0204_0800 },
CheckInfo { check_count: 2, move_mask: 0x0000_0000_0000_0000 }
];
for test_nr in 0..3 {
let fen = fens[test_nr];
let board = Board::build(fen);
let check_test_actual = board.check_test();
assert_eq!(check_test_actual.check_count, expected_results[test_nr].check_count);
assert_eq!(check_test_actual.move_mask, expected_results[test_nr].move_mask);
}
}
}

View File

@@ -48,6 +48,11 @@ pub fn try_get_square_number_from_notation(notation: &str) -> Result<u8, ()> {
}
}
const PIECE_CHARACTERS: [char; 12] = ['P', 'N', 'B', 'R', 'Q', 'K', 'p', 'n', 'b', 'r', 'q', 'k'];
pub fn get_character_by_piece_id(id: u8) -> char {
return PIECE_CHARACTERS[id as usize];
}
// <----- TESTS ----->

31
engine/src/boardsquare.rs Normal file
View File

@@ -0,0 +1,31 @@
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize)]
pub struct BoardSquare {
pub x: usize,
pub y: usize,
}
impl BoardSquare {
pub fn new() -> Self {
return Self { x: 0, y: 0 };
}
pub fn from_coord(x: usize, y: usize) -> Self {
#[cfg(debug_assertions)]
{
if x > 7 {
println!(
"Warning: x coordinate of square is bigger than 7, it might not be on the board!"
);
}
if y > 7 {
println!(
"Warning: y coordinate of square is bigger than 7, it might not be on the board!"
);
}
}
return Self { x: x, y: y };
}
}

71
engine/src/chessmove.rs Normal file
View File

@@ -0,0 +1,71 @@
use crate::piecetype;
use super::boardsquare::BoardSquare;
use super::movetype::MoveType;
use super::piecetype::PieceType;
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize)]
pub struct ChessMove {
pub move_type: MoveType,
pub piece_type: PieceType,
pub from_square: BoardSquare,
pub to_square: BoardSquare,
pub rook_from: BoardSquare,
pub rook_to: BoardSquare,
pub promotion_piece: Option<PieceType>,
}
impl ChessMove {
pub fn quiet(
piece_type: PieceType,
from_square: BoardSquare,
to_square: BoardSquare,
promotion_piece: Option<PieceType>,
) -> Self {
return Self {
move_type: MoveType::Quiet,
piece_type: piece_type,
from_square: from_square,
to_square: to_square,
rook_from: BoardSquare::new(),
rook_to: BoardSquare::new(),
promotion_piece: promotion_piece,
};
}
pub fn capture(
piece_type: PieceType,
from_square: BoardSquare,
to_square: BoardSquare,
promotion_piece: Option<PieceType>,
) -> Self {
return Self {
move_type: MoveType::Capture,
piece_type: piece_type,
from_square: from_square,
to_square: to_square,
rook_from: BoardSquare::new(),
rook_to: BoardSquare::new(),
promotion_piece: promotion_piece,
};
}
pub fn castle(
piece_type: PieceType,
from_square: BoardSquare,
to_square: BoardSquare,
rook_from: BoardSquare,
rook_to: BoardSquare,
) -> Self {
return Self {
move_type: MoveType::Quiet,
piece_type: piece_type,
from_square: from_square,
to_square: to_square,
rook_from: rook_from,
rook_to: rook_to,
promotion_piece: None,
};
}
}

6
engine/src/gameend.rs Normal file
View File

@@ -0,0 +1,6 @@
pub enum GameEnd {
WhiteWon(String),
BlackWon(String),
Draw(String)
}

24
engine/src/lib.rs Normal file
View File

@@ -0,0 +1,24 @@
mod bitboard;
pub mod chessmove;
pub mod piecetype;
pub mod boardsquare;
pub mod movetype;
pub mod gameend;
use chessmove::ChessMove;
use gameend::GameEnd;
pub fn get_available_moves(fen: &str) -> Vec<ChessMove> {
println!("get_available_moves answered");
return vec![];
}
pub fn is_game_over(fen: &str) -> Option<GameEnd> {
println!("is_game_over answered");
return None;
}
pub fn get_board_after_move(fen: &str, chess_move: &ChessMove) -> String {
println!("get_board_after_move answered");
return String::from("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1");
}

11
engine/src/movetype.rs Normal file
View File

@@ -0,0 +1,11 @@
use serde::Deserialize;
use serde::Serialize;
#[derive(Serialize, Deserialize)]
pub enum MoveType {
Quiet,
Capture,
Castle,
EnPassant,
}

18
engine/src/piecetype.rs Normal file
View File

@@ -0,0 +1,18 @@
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize)]
pub enum PieceType {
WhitePawn,
WhiteKnight,
WhiteBishop,
WhiteRook,
WhiteQueen,
WhiteKing,
BlackPawn,
BlackKnight,
BlackBishop,
BlackRook,
BlackQueen,
BlackKing,
}

View File

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

195
server/src/bin/client.rs Normal file
View File

@@ -0,0 +1,195 @@
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);
}
}
_ => {
println!("cucc: {:?}", parsed);
}
}
}
}
}
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!();
}

View File

@@ -1,3 +1,5 @@
use crate::connection::ClientEvent::*;
use engine::get_available_moves;
use futures_util::{SinkExt, StreamExt};
use serde::{Deserialize, Serialize};
use std::collections::{HashMap, VecDeque};
@@ -26,6 +28,28 @@ pub fn new_waiting_queue() -> WaitingQueue {
Arc::new(Mutex::new(VecDeque::new()))
}
#[derive(Serialize, Deserialize, Debug)]
pub struct Step {
pub from: String,
pub to: String,
}
#[derive(Serialize, Deserialize, Debug)]
#[serde(tag = "type")]
enum ClientEvent {
Join { username: String },
FindMatch,
Move { from: String, to: String },
Resign,
Chat { text: String },
RequestLegalMoves { fen: String },
}
#[derive(Serialize, Deserialize, Debug)]
pub struct EventResponse {
pub response: Result<(), String>,
}
#[derive(Debug)]
pub struct PlayerConnection {
pub id: Uuid,
@@ -43,12 +67,6 @@ pub struct GameMatch {
pub move_history: Vec<String>,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct Step {
pub from: String,
pub to: String,
}
// Message sending utilities
pub async fn send_message_to_player(
connections: &ConnectionMap,
@@ -102,7 +120,6 @@ pub async fn handle_connection(
connections: ConnectionMap,
matches: MatchMap,
waiting_queue: WaitingQueue,
event_system: crate::events::EventSystem,
) -> anyhow::Result<()> {
use tokio_tungstenite::accept_async;
@@ -141,8 +158,52 @@ pub async fn handle_connection(
let text = message.to_text()?;
println!("Received from {}: {}", player_id, text);
// TODO: Parse and handle message with event system
// This will be implemented when we integrate the event system
let client_data: ClientEvent = serde_json::from_str(text)
.expect("Failed to convert data into json at handle_connection");
println!("client: {:?}", client_data);
match client_data {
Join { username } => {
{
let mut conn_map = connections.lock().await;
let player = conn_map.get_mut(&player_id).unwrap();
player.username = Some(username);
}
//respone to client
let response: EventResponse = EventResponse {
response: core::result::Result::Ok(()),
};
println!("response: {:?}", response);
let _ = send_message_to_player(
&connections,
player_id,
&serde_json::to_string(&response).unwrap(),
)
.await;
}
FindMatch => {
let mut wait_queue = waiting_queue.lock().await;
wait_queue.push_back(player_id.clone());
println!("Appended {} to the waiting queue", player_id);
println!("queue {:?}", wait_queue);
}
Move { from, to } => {}
RequestLegalMoves { fen } => {
let moves = get_available_moves(&fen);
let _ = send_message_to_player(
&connections,
player_id,
&serde_json::to_string(&moves).unwrap(),
)
.await;
println!("Sent moves to player: {}", player_id);
}
_ => {}
}
}
}

View File

@@ -1,126 +0,0 @@
use serde::{Deserialize, Serialize};
use std::sync::Arc;
use tokio::sync::Mutex;
use tokio::sync::mpsc;
use uuid::Uuid;
#[derive(Serialize, Deserialize, Debug)]
pub struct Step {
pub from: String,
pub to: String,
}
#[derive(Serialize, Deserialize, Debug)]
#[serde(tag = "type")]
pub enum ClientEvent {
Join { username: String },
FindMatch,
Move { from: String, to: String },
Resign,
Chat { text: String },
RequestLegalMoves { fen: String },
}
#[derive(Debug)]
pub enum ServerEvent {
PlayerJoined(Uuid, String),
PlayerLeft(Uuid),
PlayerJoinedQueue(Uuid),
PlayerJoinedMatch(Uuid, Uuid), // player_id, match_id
PlayerMove(Uuid, Step),
PlayerResigned(Uuid),
MatchCreated(Uuid, Uuid, Uuid), // match_id, white_id, black_id
}
pub struct EventSystem {
sender: mpsc::UnboundedSender<(Uuid, ClientEvent)>,
receiver: Arc<Mutex<mpsc::UnboundedReceiver<(Uuid, ClientEvent)>>>,
}
impl Clone for EventSystem {
fn clone(&self) -> Self {
Self {
sender: self.sender.clone(),
receiver: Arc::clone(&self.receiver),
}
}
}
impl EventSystem {
pub fn new() -> Self {
let (sender, receiver) = mpsc::unbounded_channel();
Self {
sender,
receiver: Arc::new(Mutex::new(receiver)),
}
}
pub async fn send_event(
&self,
player_id: Uuid,
event: ClientEvent,
) -> Result<(), Box<dyn std::error::Error>> {
self.sender.send((player_id, event))?;
Ok(())
}
pub async fn next_event(&self) -> Option<(Uuid, ClientEvent)> {
let mut receiver = self.receiver.lock().await;
receiver.recv().await
}
}
impl Default for EventSystem {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use uuid::Uuid;
#[tokio::test]
async fn test_event_system_send_and_receive() {
let event_system = EventSystem::new();
let player_id = Uuid::new_v4();
let join_event = ClientEvent::Join {
username: "test_user".to_string(),
};
let send_result = event_system.send_event(player_id, join_event).await;
assert!(send_result.is_ok(), "Should send event successfully");
let received = event_system.next_event().await;
assert!(received.is_some(), "Should receive sent event");
let (received_id, received_event) = received.unwrap();
assert_eq!(received_id, player_id, "Should receive correct player ID");
match received_event {
ClientEvent::Join { username } => {
assert_eq!(username, "test_user", "Should receive correct username");
}
_ => panic!("Should receive Join event"),
}
}
#[tokio::test]
async fn test_event_system_clone() {
let event_system1 = EventSystem::new();
let event_system2 = event_system1.clone();
let player_id = Uuid::new_v4();
let event = ClientEvent::FindMatch;
event_system1.send_event(player_id, event).await.unwrap();
let received = event_system2.next_event().await;
assert!(
received.is_some(),
"Cloned event system should receive events"
);
}
}

View File

@@ -1,6 +1,6 @@
mod connection;
mod events;
mod matchmaking;
mod messages;
use tokio::net::TcpListener;
#[tokio::main]
@@ -14,15 +14,11 @@ async fn main() -> anyhow::Result<()> {
let matches = connection::new_match_map();
let waiting_queue = connection::new_waiting_queue();
// Event system for communication between components
let event_system = events::EventSystem::new();
// Start matchmaking background task
let matchmaker = matchmaking::MatchmakingSystem::new(
connections.clone(),
matches.clone(),
waiting_queue.clone(),
event_system.clone(),
);
tokio::spawn(async move {
matchmaker.run().await;
@@ -33,17 +29,10 @@ async fn main() -> anyhow::Result<()> {
let connections = connections.clone();
let matches = matches.clone();
let waiting_queue = waiting_queue.clone();
let event_system = event_system.clone();
tokio::spawn(async move {
if let Err(e) = connection::handle_connection(
stream,
connections,
matches,
waiting_queue,
event_system,
)
.await
if let Err(e) =
connection::handle_connection(stream, connections, matches, waiting_queue).await
{
eprintln!("Connection error: {}", e);
}

View File

@@ -1,5 +1,4 @@
use crate::connection::{ConnectionMap, GameMatch, MatchMap, WaitingQueue};
use crate::events::EventSystem;
use rand::random;
use uuid::Uuid;
@@ -7,21 +6,14 @@ pub struct MatchmakingSystem {
connections: ConnectionMap,
matches: MatchMap,
waiting_queue: WaitingQueue,
event_system: EventSystem,
}
impl MatchmakingSystem {
pub fn new(
connections: ConnectionMap,
matches: MatchMap,
waiting_queue: WaitingQueue,
event_system: EventSystem,
) -> Self {
pub fn new(connections: ConnectionMap, matches: MatchMap, waiting_queue: WaitingQueue) -> Self {
Self {
connections,
matches,
waiting_queue,
event_system,
}
}
@@ -114,7 +106,6 @@ impl MatchmakingSystem {
#[cfg(test)]
mod tests {
use super::*;
use crate::events::EventSystem;
use uuid::Uuid;
use crate::connection::new_connection_map;
@@ -126,14 +117,9 @@ mod tests {
let connections = new_connection_map();
let matches = new_match_map();
let waiting_queue = new_waiting_queue();
let event_system = EventSystem::new();
let matchmaking = MatchmakingSystem::new(
connections.clone(),
matches.clone(),
waiting_queue.clone(),
event_system.clone(),
);
let matchmaking =
MatchmakingSystem::new(connections.clone(), matches.clone(), waiting_queue.clone());
let player1 = Uuid::new_v4();
let player2 = Uuid::new_v4();
@@ -172,14 +158,9 @@ mod tests {
let connections = new_connection_map();
let matches = new_match_map();
let waiting_queue = new_waiting_queue();
let event_system = EventSystem::new();
let matchmaking = MatchmakingSystem::new(
connections.clone(),
matches.clone(),
waiting_queue.clone(),
event_system.clone(),
);
let matchmaking =
MatchmakingSystem::new(connections.clone(), matches.clone(), waiting_queue.clone());
let player1 = Uuid::new_v4();
{