mirror of
https://github.com/szymon-jozef/checkers.git
synced 2026-10-27 19:20:44 +01:00
@@ -6,6 +6,7 @@ use std::{
|
|||||||
use checkers::{
|
use checkers::{
|
||||||
logic::board::pawn::{CapturePath, MovePath},
|
logic::board::pawn::{CapturePath, MovePath},
|
||||||
network::{client::Client, message::ServerMessage, network_identity::NetworkIdentity},
|
network::{client::Client, message::ServerMessage, network_identity::NetworkIdentity},
|
||||||
|
super_advanced_ai::{Bot, BotDificulty},
|
||||||
};
|
};
|
||||||
use log::{error, warn};
|
use log::{error, warn};
|
||||||
use tokio::sync::mpsc;
|
use tokio::sync::mpsc;
|
||||||
@@ -14,6 +15,7 @@ const QUIT_COMMAND: &str = "/quit";
|
|||||||
const HELP_COMMAND: &str = "/help";
|
const HELP_COMMAND: &str = "/help";
|
||||||
const SEND_COMMAND: &str = "/send";
|
const SEND_COMMAND: &str = "/send";
|
||||||
const READY_COMMAND: &str = "/ready";
|
const READY_COMMAND: &str = "/ready";
|
||||||
|
const SINGLEPLAYER_COMMAND: &str = "/single";
|
||||||
|
|
||||||
const CAPTURE_COMMAND: &str = "/capture";
|
const CAPTURE_COMMAND: &str = "/capture";
|
||||||
const MOVE_COMMAND: &str = "/move";
|
const MOVE_COMMAND: &str = "/move";
|
||||||
@@ -22,7 +24,9 @@ enum CliCommands {
|
|||||||
Quit,
|
Quit,
|
||||||
Help,
|
Help,
|
||||||
Send,
|
Send,
|
||||||
|
|
||||||
Ready,
|
Ready,
|
||||||
|
Single,
|
||||||
|
|
||||||
Capture,
|
Capture,
|
||||||
Move,
|
Move,
|
||||||
@@ -46,6 +50,7 @@ impl TryFrom<&str> for CliCommands {
|
|||||||
HELP_COMMAND => Ok(CliCommands::Help),
|
HELP_COMMAND => Ok(CliCommands::Help),
|
||||||
SEND_COMMAND => Ok(CliCommands::Send),
|
SEND_COMMAND => Ok(CliCommands::Send),
|
||||||
READY_COMMAND => Ok(CliCommands::Ready),
|
READY_COMMAND => Ok(CliCommands::Ready),
|
||||||
|
SINGLEPLAYER_COMMAND => Ok(CliCommands::Single),
|
||||||
|
|
||||||
CAPTURE_COMMAND => Ok(CliCommands::Capture),
|
CAPTURE_COMMAND => Ok(CliCommands::Capture),
|
||||||
MOVE_COMMAND => Ok(CliCommands::Move),
|
MOVE_COMMAND => Ok(CliCommands::Move),
|
||||||
@@ -74,17 +79,20 @@ impl Default for ClientContext {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn run_bot() {
|
||||||
|
let bot = Bot::new(BotDificulty::Easy).await;
|
||||||
|
bot.game_loop().await;
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
env_logger::init();
|
env_logger::init();
|
||||||
|
|
||||||
let Some(mut client) = Client::new().await else {
|
let Some(mut client) = Client::new(None).await else {
|
||||||
println!("Not worky, ending");
|
println!("Not worky, ending");
|
||||||
return Ok(());
|
return Ok(());
|
||||||
};
|
};
|
||||||
|
|
||||||
client.update();
|
|
||||||
|
|
||||||
let (stdin_sender, mut stdin_receiver) = mpsc::channel(1024);
|
let (stdin_sender, mut stdin_receiver) = mpsc::channel(1024);
|
||||||
|
|
||||||
let mut context: ClientContext = ClientContext::default();
|
let mut context: ClientContext = ClientContext::default();
|
||||||
@@ -112,12 +120,13 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
│\t {} - show this message
|
│\t {} - show this message
|
||||||
│\t {} - message the server you're ready
|
│\t {} - message the server you're ready
|
||||||
│\t {} - send text message to the server
|
│\t {} - send text message to the server
|
||||||
|
│\t {} - play alone
|
||||||
│
|
│
|
||||||
│\t {} <index>- choose a capture path from the list
|
│\t {} <index>- choose a capture path from the list
|
||||||
│\t {} <index> <index>- choose move from the list. First number is pawn you want to move and second is position it should take
|
│\t {} <index> <index>- choose move from the list. First number is pawn you want to move and second is position it should take
|
||||||
└───────────────────────────────────────
|
└───────────────────────────────────────
|
||||||
",
|
",
|
||||||
QUIT_COMMAND, HELP_COMMAND, READY_COMMAND, SEND_COMMAND, CAPTURE_COMMAND, MOVE_COMMAND
|
QUIT_COMMAND, HELP_COMMAND, READY_COMMAND, SEND_COMMAND, SINGLEPLAYER_COMMAND , CAPTURE_COMMAND, MOVE_COMMAND
|
||||||
);
|
);
|
||||||
|
|
||||||
println!("───────Type /help for help───────");
|
println!("───────Type /help for help───────");
|
||||||
@@ -199,6 +208,12 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
CliCommands::Ready => {
|
CliCommands::Ready => {
|
||||||
client.signal_readiness().await;
|
client.signal_readiness().await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CliCommands::Single => {
|
||||||
|
println!("Tried connecting bot to the server! Make sure to be /ready");
|
||||||
|
tokio::spawn(run_bot());
|
||||||
|
}
|
||||||
|
|
||||||
CliCommands::Send => {
|
CliCommands::Send => {
|
||||||
let mut args: String = words[1..].join(" ");
|
let mut args: String = words[1..].join(" ");
|
||||||
args = args.trim().to_string();
|
args = args.trim().to_string();
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
pub mod logic;
|
pub mod logic;
|
||||||
pub mod network;
|
pub mod network;
|
||||||
pub mod settings;
|
pub mod settings;
|
||||||
|
pub mod super_advanced_ai;
|
||||||
|
|||||||
@@ -23,22 +23,24 @@ pub enum ClientCommands {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub struct Client {
|
pub struct Client {
|
||||||
conn_reciever_incoming: Option<Receiver<(SocketAddr, Message<ServerMessage>)>>,
|
|
||||||
conn_sender_outgoing: Sender<Message<ClientMessage>>,
|
conn_sender_outgoing: Sender<Message<ClientMessage>>,
|
||||||
settings: ClientSettings,
|
|
||||||
|
|
||||||
update_sender: Sender<ServerMessage>,
|
|
||||||
update_receiver: Option<Receiver<ServerMessage>>,
|
update_receiver: Option<Receiver<ServerMessage>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Client {
|
impl Client {
|
||||||
pub async fn new() -> Option<Client> {
|
/// Creates a new instance of clients, connects it to the server and starts update loop
|
||||||
let settings = ClientSettings::new();
|
pub async fn new(settings: Option<ClientSettings>) -> Option<Client> {
|
||||||
|
let Some(settings) = settings.or(Some(ClientSettings::new())) else {
|
||||||
|
error!("Could not load client settings");
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
|
||||||
let (conn_sender, conn_reciever) =
|
let (conn_sender, mut conn_reciever) =
|
||||||
mpsc::channel::<(SocketAddr, Message<ServerMessage>)>(1024);
|
mpsc::channel::<(SocketAddr, Message<ServerMessage>)>(1024);
|
||||||
|
|
||||||
let mut conn: Connection<ServerMessage, ClientMessage> =
|
let mut conn: Connection<ServerMessage, ClientMessage> =
|
||||||
Connection::new(ConnectionType::Server, conn_sender);
|
Connection::new(ConnectionType::Server, conn_sender);
|
||||||
|
|
||||||
let conn_sender_outgoing = conn.get_sender();
|
let conn_sender_outgoing = conn.get_sender();
|
||||||
|
|
||||||
let (update_sender, update_receiver) = mpsc::channel(1024);
|
let (update_sender, update_receiver) = mpsc::channel(1024);
|
||||||
@@ -52,32 +54,18 @@ impl Client {
|
|||||||
conn.start_listening().await;
|
conn.start_listening().await;
|
||||||
});
|
});
|
||||||
|
|
||||||
Some(Client {
|
let conn_sender_outgoing_clone = conn_sender_outgoing.clone();
|
||||||
conn_reciever_incoming: Some(conn_reciever),
|
let settings_clone = settings.clone();
|
||||||
conn_sender_outgoing,
|
let update_sender_clone = update_sender.clone();
|
||||||
|
|
||||||
settings,
|
let (hanshake_tx, handshake_rx) = tokio::sync::oneshot::channel::<bool>();
|
||||||
|
|
||||||
update_sender,
|
|
||||||
update_receiver: Some(update_receiver),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn update(&mut self) {
|
|
||||||
let Some(mut conn_reciever_incoming) = self.conn_reciever_incoming.take() else {
|
|
||||||
error!("Tried updating, but there's no conn_reciever of incoming messages!");
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
let conn_sender = self.conn_sender_outgoing.clone();
|
|
||||||
|
|
||||||
let settings = self.settings.clone();
|
|
||||||
let update_sender = self.update_sender.clone();
|
|
||||||
|
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
|
let mut hanshake_tx = Some(hanshake_tx);
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
tokio::select! {
|
tokio::select! {
|
||||||
oldest_msg = conn_reciever_incoming.recv() => {
|
oldest_msg = conn_reciever.recv() => {
|
||||||
if let Some(oldest_msg) = oldest_msg {
|
if let Some(oldest_msg) = oldest_msg {
|
||||||
let (addr, msg) = oldest_msg;
|
let (addr, msg) = oldest_msg;
|
||||||
debug!("Got message from: {:?}", addr);
|
debug!("Got message from: {:?}", addr);
|
||||||
@@ -86,24 +74,30 @@ impl Client {
|
|||||||
ServerMessage::RequestHandshake => {
|
ServerMessage::RequestHandshake => {
|
||||||
info!("Server requested handshake!");
|
info!("Server requested handshake!");
|
||||||
let msg = Message::new(ClientMessage::AnswerHandshake {
|
let msg = Message::new(ClientMessage::AnswerHandshake {
|
||||||
player_name: settings.name.clone(),
|
player_name: settings_clone.name.clone(),
|
||||||
});
|
});
|
||||||
|
|
||||||
let _ = conn_sender.send(msg.unwrap()).await;
|
let _ = conn_sender_outgoing_clone.send(msg.unwrap()).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
ServerMessage::AcceptHandshake => {
|
ServerMessage::AcceptHandshake => {
|
||||||
info!("Server accepted us! Yay :D");
|
info!("Server accepted us! Yay :D");
|
||||||
|
if let Some(hanshake_tx) = hanshake_tx.take() {
|
||||||
|
let _ = hanshake_tx.send(true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ServerMessage::DeclineHandshake { reason } => {
|
ServerMessage::DeclineHandshake { reason } => {
|
||||||
error!("Server declined connection: {}", reason);
|
error!("Server declined connection: {}", reason);
|
||||||
|
if let Some(hanshake_tx) = hanshake_tx.take() {
|
||||||
|
let _ = hanshake_tx.send(false);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
_ => { // Pass all messages that require player intervention to the
|
_ => { // Pass all messages that require player intervention to the
|
||||||
// local client
|
// local client
|
||||||
let _ = update_sender.send(msg.content).await;
|
let _ = update_sender_clone.send(msg.content).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -114,6 +108,23 @@ impl Client {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let is_handshake_accepted: bool = match handshake_rx.await {
|
||||||
|
Ok(is_accepted) => is_accepted,
|
||||||
|
Err(e) => {
|
||||||
|
error!("Error while receiving handshake: {}", e);
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if is_handshake_accepted {
|
||||||
|
Some(Client {
|
||||||
|
conn_sender_outgoing,
|
||||||
|
update_receiver: Some(update_receiver),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn send_message(&mut self, msg: Result<Message<ClientMessage>, postcard::Error>) {
|
async fn send_message(&mut self, msg: Result<Message<ClientMessage>, postcard::Error>) {
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ pub struct NetworkServerIdentity {
|
|||||||
// writing this since 10 a.m. and it's 9 p.m. already)
|
// writing this since 10 a.m. and it's 9 p.m. already)
|
||||||
pub identity: NetworkIdentity,
|
pub identity: NetworkIdentity,
|
||||||
pub sender: Sender<Message<ServerMessage>>,
|
pub sender: Sender<Message<ServerMessage>>,
|
||||||
|
pub is_handshaken: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl NetworkServerIdentity {
|
impl NetworkServerIdentity {
|
||||||
@@ -40,6 +41,7 @@ impl NetworkServerIdentity {
|
|||||||
Self {
|
Self {
|
||||||
identity: NetworkIdentity::default(),
|
identity: NetworkIdentity::default(),
|
||||||
sender,
|
sender,
|
||||||
|
is_handshaken: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -186,7 +186,7 @@ impl Server {
|
|||||||
|
|
||||||
ClientMessage::SignalReadiness => {
|
ClientMessage::SignalReadiness => {
|
||||||
self.handle_readiness(addr).await
|
self.handle_readiness(addr).await
|
||||||
},
|
}
|
||||||
|
|
||||||
ClientMessage::RequestCapture { capture_path } => {
|
ClientMessage::RequestCapture { capture_path } => {
|
||||||
self.process_capture(capture_path).await;
|
self.process_capture(capture_path).await;
|
||||||
@@ -282,7 +282,8 @@ impl Server {
|
|||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
identity.identity.name = fixed_name
|
identity.identity.name = fixed_name;
|
||||||
|
identity.is_handshaken = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn process_capture(&mut self, capture_path: CapturePath) {
|
async fn process_capture(&mut self, capture_path: CapturePath) {
|
||||||
@@ -313,6 +314,14 @@ impl Server {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_readiness(&mut self, addr: SocketAddr) {
|
async fn handle_readiness(&mut self, addr: SocketAddr) {
|
||||||
|
if !self.connections[&addr].is_handshaken {
|
||||||
|
warn!(
|
||||||
|
"Connection: {} tried sending readiness, but it have not shaken hands. Ignoring...",
|
||||||
|
addr
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
self.state.ready_count += 1;
|
self.state.ready_count += 1;
|
||||||
info!(
|
info!(
|
||||||
"{} signaled readiness. Currently ready: {}/{}",
|
"{} signaled readiness. Currently ready: {}/{}",
|
||||||
|
|||||||
117
src/super_advanced_ai.rs
Normal file
117
src/super_advanced_ai.rs
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
use log::{error, info};
|
||||||
|
use rand::seq::IndexedRandom;
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
logic::game_master::GameResult,
|
||||||
|
network::client::Client,
|
||||||
|
settings::{client_settings::ClientSettings, general_settings::DEFAULT_URL},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(PartialEq, Eq, Clone, Copy)]
|
||||||
|
pub enum BotDificulty {
|
||||||
|
Easy,
|
||||||
|
Normal,
|
||||||
|
Hard,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Bot {
|
||||||
|
dificulty: BotDificulty,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Bot {
|
||||||
|
pub async fn new(dificulty: BotDificulty) -> Self {
|
||||||
|
if dificulty == BotDificulty::Normal || dificulty == BotDificulty::Hard {
|
||||||
|
todo!("Only easy is implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
Self { dificulty }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn game_loop(&self) -> Option<Uuid> {
|
||||||
|
let mut client = Client::new(Some(ClientSettings {
|
||||||
|
name: "Bot".to_string(),
|
||||||
|
server_url: DEFAULT_URL,
|
||||||
|
}))
|
||||||
|
.await
|
||||||
|
.expect("Couldn't connect to the server. Is it online?");
|
||||||
|
|
||||||
|
client.signal_readiness().await; // bot is always ready
|
||||||
|
|
||||||
|
let mut receiver = client
|
||||||
|
.get_update_receiver()
|
||||||
|
.expect("Could not receive the receiver");
|
||||||
|
|
||||||
|
let dificulty = self.dificulty;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
match receiver.recv().await {
|
||||||
|
Some(message) => match message {
|
||||||
|
crate::network::message::ServerMessage::GameStart { identity } => {
|
||||||
|
info!("Bot got identity: {}", identity);
|
||||||
|
}
|
||||||
|
crate::network::message::ServerMessage::AvailableCaptures { captures } => {
|
||||||
|
let capture = captures
|
||||||
|
.choose(&mut rand::rng())
|
||||||
|
.expect("Could not select random capture");
|
||||||
|
client.send_capture(capture.clone()).await;
|
||||||
|
}
|
||||||
|
crate::network::message::ServerMessage::AvailableMoves { moves } => {
|
||||||
|
let rand_pawn = moves
|
||||||
|
.choose(&mut rand::rng())
|
||||||
|
.expect("Couldn't select random move");
|
||||||
|
let rand_move = rand_pawn
|
||||||
|
.available_steps
|
||||||
|
.choose(&mut rand::rng())
|
||||||
|
.expect("Could not get random move");
|
||||||
|
|
||||||
|
client.send_move(rand_pawn.from, rand_move.clone()).await;
|
||||||
|
}
|
||||||
|
crate::network::message::ServerMessage::BroadcastBoardState { board: _ } => {
|
||||||
|
info!("Got server state!");
|
||||||
|
|
||||||
|
match dificulty.clone() {
|
||||||
|
BotDificulty::Easy => {} // we do nothing with it because easy bot
|
||||||
|
// just does random stuff. In future
|
||||||
|
// board_view can be used to calculate the
|
||||||
|
// best possible move atm
|
||||||
|
BotDificulty::Normal => todo!(),
|
||||||
|
BotDificulty::Hard => todo!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
crate::network::message::ServerMessage::BroadcastCurrentTurn {
|
||||||
|
active_player: _,
|
||||||
|
} => {} // we do nothing with this - bot doesn't need to know if it's the
|
||||||
|
// active player, as it's only responding to messages
|
||||||
|
crate::network::message::ServerMessage::BroadCastTextMessage {
|
||||||
|
sender,
|
||||||
|
content,
|
||||||
|
} => {
|
||||||
|
info!("Bot got message from [{}] - {}", sender, content);
|
||||||
|
}
|
||||||
|
|
||||||
|
crate::network::message::ServerMessage::GameEnd { result } => {
|
||||||
|
info!("Game has ended!");
|
||||||
|
match result {
|
||||||
|
GameResult::Draw => {
|
||||||
|
info!("Draw!");
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
GameResult::Lost(loser) => {
|
||||||
|
info!("ID: {} lost!", loser);
|
||||||
|
return Some(loser);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_ => {}
|
||||||
|
},
|
||||||
|
|
||||||
|
None => {
|
||||||
|
error!("Connection broken! Super advanced ai logging out!");
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
44
tests/integration.rs
Normal file
44
tests/integration.rs
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
|
||||||
|
use tokio::time::Duration;
|
||||||
|
|
||||||
|
use checkers::{
|
||||||
|
network::server::Server,
|
||||||
|
super_advanced_ai::{Bot, BotDificulty},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn game_integration() {
|
||||||
|
let _ = env_logger::builder().is_test(true).try_init();
|
||||||
|
|
||||||
|
tokio::spawn(async move {
|
||||||
|
let mut server = Server::new().await;
|
||||||
|
server.start().await;
|
||||||
|
server.update().await;
|
||||||
|
});
|
||||||
|
|
||||||
|
tokio::time::sleep(Duration::from_secs(2)).await;
|
||||||
|
|
||||||
|
let bot1_handle = tokio::spawn(async move {
|
||||||
|
let bot1 = Bot::new(BotDificulty::Easy).await;
|
||||||
|
bot1.game_loop().await
|
||||||
|
});
|
||||||
|
|
||||||
|
let bot2_handle = tokio::spawn(async move {
|
||||||
|
let bot2 = Bot::new(BotDificulty::Easy).await;
|
||||||
|
bot2.game_loop().await
|
||||||
|
});
|
||||||
|
|
||||||
|
let bot1_result = bot1_handle.await.unwrap();
|
||||||
|
let bot2_result = bot2_handle.await.unwrap();
|
||||||
|
|
||||||
|
if bot1_result.is_some() && bot2_result.is_some() {
|
||||||
|
assert_eq!(bot1_result.unwrap(), bot2_result.unwrap());
|
||||||
|
} else if bot1_result.is_none() && bot2_result.is_none() {
|
||||||
|
()
|
||||||
|
} else {
|
||||||
|
panic!("Bot result differanciate");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user