diff --git a/Cargo.lock b/Cargo.lock index 0602a39..aea9e87 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -48,6 +48,7 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" name = "connect4" version = "0.1.0" dependencies = [ + "bytes", "tokio", ] diff --git a/Cargo.toml b/Cargo.toml index 73c0d04..3107756 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,7 @@ version = "0.1.0" edition = "2021" [dependencies] +bytes = "1.7.2" tokio = { version = "1.38.0", features = [ "rt-multi-thread", "macros", diff --git a/src/connect4.rs b/src/connect4.rs index 451a31a..c22d0f4 100644 --- a/src/connect4.rs +++ b/src/connect4.rs @@ -118,22 +118,25 @@ impl GameState { return score; } - pub fn display(&self) { + pub fn to_string(&self) -> String { + let mut string = String::new(); for irow in (0..NROWS).rev() { - print!("\n "); + string += " "; for icol in 0..NCOLS { let idx = irow + 7 * icol; - + if (self.bitboards[0] >> idx) & 1 != 0 { - print!("● "); + string += "● "; } else if (self.bitboards[1] >> idx) & 1 != 0 { - print!("○ "); + string += "○ "; } else { - print!("· "); + string += "· "; } } + string += "\n"; } - print!("\n 1 2 3 4 5 6 7\n\n"); + string += " 1 2 3 4 5 6 7\n\n"; + string } } diff --git a/src/game_server.rs b/src/game_server.rs index 69953c4..5b33bd8 100644 --- a/src/game_server.rs +++ b/src/game_server.rs @@ -1,31 +1,89 @@ - -use std::f32::consts::E; +use bytes::BytesMut; use tokio::{ - // AsyncWriteExt trait provides asynchronous write methods like write_all io::{AsyncReadExt, AsyncWriteExt}, - net::{TcpListener, TcpStream}, + net::TcpStream, }; -use crate::connect4::GameState; - -/// The Server struct holds the tokio TcpListener which listens for -/// incoming TCP connections. +use crate::connect4; #[derive(Debug)] -pub struct GameServer { -} +pub struct GameServer {} impl GameServer { - pub async fn handle(socket: &mut TcpStream, game_state: GameState) { - match socket.write_all("Hello!!".as_bytes()).await { - Ok(_) => {println!("socket socketed successfully")}, - Err(e) => {println!("socket didn't socket: {e}")}, - } - loop { - match socket.read_i8().await { - Ok(read) => {println!("read: {read}")}, - Err(_) => {break}, + pub async fn handle(socket: &mut TcpStream) -> Result<(), String> { + send_text("Connect 4\nChoose your difficulty [1-9]: ", socket).await?; + + let depth = get_one_char(b"123456789", socket).await? - b'0'; + + println!("Difficulty: {depth}"); + + let mut board = connect4::GameState::new(); + + let mut end_score: i64 = -1; + + while end_score == -1 { + + match board.counter % 2 == 0 { + true => { + send_text(format!("Move {}:\n", board.counter).as_str(), socket).await?; + send_text(board.to_string().as_str(), socket).await?; + + send_text("Choose column: ", socket).await?; + let mut player_move = get_one_char(b"1234567", socket).await? - b'0'; + while !board.move_is_valid((player_move - 1).into()) { + send_text("Illegal move.\nChoose column: ", socket).await?; + player_move = get_one_char(b"1234567", socket).await? - b'0'; + } + + board.make_move((player_move - 1).into()); + end_score = board.end_state_reached(); + send_text("\n", socket).await?; + }, + false => { + let best_move = connect4::get_minimax_move(&mut board, depth.into()); + + board.make_move(best_move); + end_score = board.end_state_reached(); + } } } + + if end_score == 0 { + send_text( "It's a Tie\n\n", socket).await?; + } else if end_score > 0 { + send_text("White (●) Won!\n\n", socket).await?; + } else { + send_text("Black (○) Won!\n\n", socket).await?; + } + + send_text(&board.to_string(), socket).await?; + + Ok(()) } -} \ No newline at end of file +} + +async fn get_one_char(allowed: &[u8], socket: &mut TcpStream) -> Result { + let mut buffer = BytesMut::with_capacity(100); + loop { + match socket.read_buf(&mut buffer).await { + Ok(0) => return Err(format!("socket disconnected")), + Ok(read) => { + println!("read {read} bytes: {:?}", buffer); + for byte in &buffer { + if allowed.contains(byte) { + return Ok(*byte); + } + } + buffer.clear(); + } + Err(e) => return Err(format!("socket error while reading a digit: {e}")), + } + } +} + +async fn send_text(text: &str, socket: &mut TcpStream) -> Result<(), String> { + match socket.write_all(text.as_bytes()).await { + Ok(_) => Ok(()), + Err(e) => Err(format!("socket error while sending text: {e}")), + } +} diff --git a/src/main.rs b/src/main.rs index 95f40a2..03aa893 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,3 @@ -use connect4::GameState; use game_server::GameServer; use tokio::net::TcpListener; @@ -7,21 +6,20 @@ mod game_server; #[tokio::main] async fn main() { - let addr = format!("127.0.0.1:{}", 6379); + let addr = format!("127.0.0.1:{}", 1234); - let listener = match TcpListener::bind(&addr).await { - Ok(tcp_listener) => { - println!("TCP listener started on port 6379"); - tcp_listener - } - Err(e) => panic!("Could not bind the TCP listener to {}. Err: {}", &addr, e), - }; + let listener = TcpListener::bind(&addr) + .await + .expect(format!("Could not bind the TCP listener to {}", &addr).as_str()); loop { match listener.accept().await { Ok((mut socket, _)) => { tokio::spawn(async move { - GameServer::handle(&mut socket, GameState::new()).await; + match GameServer::handle(&mut socket).await { + Ok(_) => {println!("Game finished")}, + Err(e) => {println!("Game failed with error: {e}")}, + }; }); } Err(e) => { @@ -30,28 +28,5 @@ async fn main() { } } - let mut board = connect4::GameState::new(); - let depth = 7; - - let mut end_score: i64 = -1; - - while end_score == -1 { - println!("Move {}:", board.counter); - board.display(); - let best_move = connect4::get_minimax_move(&mut board, depth); - - board.make_move(best_move); - end_score = board.end_state_reached(); - } - - println!("Move {}:", board.counter); - board.display(); - - if end_score == 0 { - println!("It's a Tie"); - } else if end_score > 0 { - println!("White (●) Won!"); - } else { - println!("Black (○) Won!"); - } + }