diff --git a/.gitignore b/.gitignore index ea8c4bf..c0ed7d0 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ /target +/logs \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index aea9e87..ddbf770 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -50,6 +50,33 @@ version = "0.1.0" dependencies = [ "bytes", "tokio", + "tracing", + "tracing-appender", + "tracing-subscriber", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33480d6946193aa8033910124896ca395333cae7e2d1113d1fef6c3272217df2" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" + +[[package]] +name = "deranged" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +dependencies = [ + "powerfmt", ] [[package]] @@ -64,12 +91,30 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" +[[package]] +name = "itoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + [[package]] name = "libc" version = "0.2.161" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1" +[[package]] +name = "log" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" + [[package]] name = "memchr" version = "2.7.4" @@ -97,6 +142,22 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + [[package]] name = "object" version = "0.36.5" @@ -106,12 +167,30 @@ dependencies = [ "memchr", ] +[[package]] +name = "once_cell" +version = "1.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" + +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + [[package]] name = "pin-project-lite" version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + [[package]] name = "proc-macro2" version = "1.0.88" @@ -136,6 +215,41 @@ version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" +[[package]] +name = "serde" +version = "1.0.210" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.210" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + [[package]] name = "socket2" version = "0.5.7" @@ -157,6 +271,67 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "thiserror" +version = "1.0.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thread_local" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +dependencies = [ + "cfg-if", + "once_cell", +] + +[[package]] +name = "time" +version = "0.3.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" + +[[package]] +name = "time-macros" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" +dependencies = [ + "num-conv", + "time-core", +] + [[package]] name = "tokio" version = "1.40.0" @@ -184,18 +359,115 @@ dependencies = [ "syn", ] +[[package]] +name = "tracing" +version = "0.1.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-appender" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3566e8ce28cc0a3fe42519fc80e6b4c943cc4c8cef275620eb8dac2d3d4e06cf" +dependencies = [ + "crossbeam-channel", + "thiserror", + "time", + "tracing-subscriber", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" +dependencies = [ + "nu-ansi-term", + "sharded-slab", + "smallvec", + "thread_local", + "tracing-core", + "tracing-log", +] + [[package]] name = "unicode-ident" version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + [[package]] name = "windows-sys" version = "0.52.0" diff --git a/Cargo.toml b/Cargo.toml index 3107756..d826235 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,3 +12,6 @@ tokio = { version = "1.38.0", features = [ "io-util", "sync", ] } +tracing = "0.1.40" +tracing-appender = "0.2.3" +tracing-subscriber = "0.3.18" diff --git a/README.md b/README.md new file mode 100644 index 0000000..4269fdb --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# TCP Connect 4 + +type `nc cqql.site 1234` to start playing connect 4 against an ai in your terminal. \ No newline at end of file diff --git a/src/connect4.rs b/src/connect4.rs index c22d0f4..28d783d 100644 --- a/src/connect4.rs +++ b/src/connect4.rs @@ -20,8 +20,13 @@ const NCOLS: usize = 7; const PAD_TOP: [i64; NCOLS] = [6, 13, 20, 27, 34, 41, 48]; // simple heuristic, where a position's value is the number of possible 4-in-a-rows from that position const SCOREMAP: [i64; NCOLS * (NROWS + 1)] = [ - 3, 4, 5, 5, 4, 3, 0, 4, 6, 8, 8, 6, 4, 0, 5, 8, 11, 11, 8, 5, 0, 7, 9, 13, 13, 9, 7, 0, 5, 8, - 11, 11, 8, 5, 0, 4, 6, 8, 8, 6, 4, 0, 3, 4, 5, 5, 4, 3, 0, + 3, 4, 5, 5, 4, 3, 0, + 4, 6, 8, 8, 6, 4, 0, + 5, 8, 11, 11, 8, 5, 0, + 7, 9, 13, 13, 9, 7, 0, + 5, 8, 11, 11, 8, 5, 0, + 4, 6, 8, 8, 6, 4, 0, + 3, 4, 5, 5, 4, 3, 0, ]; pub struct GameState { @@ -92,7 +97,7 @@ impl GameState { pub fn end_state_reached(&self) -> i64 { let winner = self.check_win(); if winner == 0 { - return 100000; + return 1000000; } // white won if winner == 1 { return -100000; @@ -109,9 +114,9 @@ impl GameState { /// assumes that the board is not in an end state pub fn evaluate(&self) -> i64 { // for each cell, add score for white and subtract score for black - let mut score = 0; + let mut score: i64 = 0; for idx in 0..48 { - score += SCOREMAP[idx] + score += SCOREMAP[idx] * (((self.bitboards[0] >> idx) & 1) - ((self.bitboards[1] >> idx) & 1)); } diff --git a/src/game_server.rs b/src/game_server.rs index 58a8105..28a7eb9 100644 --- a/src/game_server.rs +++ b/src/game_server.rs @@ -5,69 +5,66 @@ use tokio::{ net::TcpStream, }; +use tracing::{event, Level}; + use crate::connect4; -#[derive(Debug)] -pub struct GameServer {} -impl GameServer { - pub async fn handle(socket: &mut TcpStream) -> Result<(), String> { - send_text( - ("Game AI designed by https://balkarjun.github.io\n".to_string() - + "Game interface coded by https://cqql.site\n\n" - + "Connect 4\nChoose your difficulty [1-9]: ").as_str(), - socket, - ) - .await?; +pub async fn handle(socket: &mut TcpStream) -> Result<(), String> { + send_text( + ("Game AI designed by https://balkarjun.github.io\n".to_string() + + "Game interface coded by https://cqql.site\n\n" + + "Connect 4\nChoose your difficulty [1-9]: ") + .as_str(), + socket, + ) + .await?; - let depth = get_one_char(b"123456789", socket).await? - b'0' + 2; + let depth = get_one_char(b"123456789", socket).await? - b'0' + 2; - println!("Difficulty: {depth}"); + let mut board = connect4::GameState::new(); - let mut board = connect4::GameState::new(); + let mut end_score: i64 = -1; - 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?; - 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'; - send_text("[AI thinking]\n", socket).await?; - 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?; + 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'; } - false => { - let best_move = connect4::get_minimax_move(&mut board, depth.into()); - board.make_move(best_move); - end_score = board.end_state_reached(); - } + board.make_move((player_move - 1).into()); + end_score = board.end_state_reached(); + send_text("[AI thinking]\n", socket).await?; + 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?; - - send_text("Reconnect to play again\n", socket).await?; - - Ok(()) } + + 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?; + + send_text("Reconnect to play again\n", socket).await?; + + Ok(()) } async fn get_one_char(allowed: &[u8], socket: &mut TcpStream) -> Result { @@ -76,9 +73,10 @@ async fn get_one_char(allowed: &[u8], socket: &mut TcpStream) -> Result return Err(format!("socket disconnected")), Ok(read) => { - println!("read {read} bytes: {:?}", buffer); + event!(Level::TRACE, "read {read} bytes: {:?}", buffer); for byte in &buffer { if allowed.contains(byte) { + event!(Level::TRACE, "received {:?}", *byte as char); return Ok(*byte); } } @@ -90,6 +88,7 @@ async fn get_one_char(allowed: &[u8], socket: &mut TcpStream) -> Result Result<(), String> { + event!(Level::TRACE, "\n{text}"); 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 03aa893..05682de 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,12 +1,17 @@ -use game_server::GameServer; use tokio::net::TcpListener; +use tracing::{event, level_filters::LevelFilter, span, Instrument, Level}; +use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, Layer}; mod connect4; mod game_server; #[tokio::main] async fn main() { - let addr = format!("127.0.0.1:{}", 1234); + println!("Hi!"); + + let _guard = init_logging(); + + let addr = format!("0.0.0.0:{}", 1234); let listener = TcpListener::bind(&addr) .await @@ -16,9 +21,23 @@ async fn main() { match listener.accept().await { Ok((mut socket, _)) => { tokio::spawn(async move { - match GameServer::handle(&mut socket).await { - Ok(_) => {println!("Game finished")}, - Err(e) => {println!("Game failed with error: {e}")}, + let socket_span = span!( + Level::TRACE, + "socket", + address = %socket.peer_addr().unwrap() + ); + + let _enter = socket_span.enter(); + + event!(Level::INFO, "new socket connected"); + + match game_server::handle(&mut socket).instrument(socket_span.clone()).await { + Ok(_) => { + event!(Level::INFO, "Game finished"); + } + Err(e) => { + event!(Level::ERROR, "Game failed with error: {e}"); + } }; }); } @@ -27,6 +46,26 @@ async fn main() { } } } - - } + +fn init_logging() -> tracing_appender::non_blocking::WorkerGuard { + let file_appender = tracing_appender::rolling::daily("./logs", "connect4.log"); + let (non_blocking, _guard) = tracing_appender::non_blocking(file_appender); + + let file_subscriber = tracing_subscriber::fmt::layer() + .with_ansi(false) + .with_writer(non_blocking) + .with_target(false) + .with_filter(LevelFilter::TRACE); + + let stdout_subscriber = tracing_subscriber::fmt::layer() + .with_ansi(true) + .with_target(false) + .with_filter(LevelFilter::DEBUG); + + tracing_subscriber::registry() + .with(file_subscriber) + .with(stdout_subscriber) + .init(); + _guard +} \ No newline at end of file