From c5738741f11e74460f12cca8835dccb7dcf55d1d Mon Sep 17 00:00:00 2001 From: Joshua Higgins Date: Sun, 22 Mar 2026 14:51:00 -0400 Subject: [PATCH] misc: formatting and compile fix --- .rustfmt.toml | 3 +- src/lib.rs | 46 +- src/main.rs | 608 ++++---- src/server.rs | 2539 ++++++++++++++++---------------- src/tournaments/mod.rs | 22 +- src/tournaments/round_robin.rs | 340 ++--- src/types.rs | 235 ++- 7 files changed, 1890 insertions(+), 1903 deletions(-) diff --git a/.rustfmt.toml b/.rustfmt.toml index 8172f78..e95aca5 100644 --- a/.rustfmt.toml +++ b/.rustfmt.toml @@ -1,2 +1,3 @@ combine_control_expr = false -chain_width = 100 \ No newline at end of file +chain_width = 100 +tab_spaces = 2 \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 937fbe3..0111fb4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,15 +2,15 @@ use std::{collections::HashMap, net::SocketAddr, sync::Arc}; use rand::RngExt; use tokio::sync::{ - mpsc::{error::SendError, UnboundedSender}, - RwLock, + mpsc::{error::SendError, UnboundedSender}, + RwLock, }; use tokio_tungstenite::tungstenite::Message; use tracing::error; use crate::{ - tournaments::Tournament, - types::{Client, Color, Match}, + tournaments::Tournament, + types::{Client, Color, Match}, }; pub mod server; @@ -28,34 +28,34 @@ pub const SERVER_PLAYER_USERNAME: &str = "The Server"; pub const SERVER_PLAYER_ADDR: &str = "127.0.0.1:6666"; pub async fn broadcast_message(observers: &Observers, addrs: &Vec, msg: &str) { - for addr in addrs { - let observers_guard = observers.read().await; - let tx = observers_guard.get(addr); - if tx.is_none() { - continue; - } - let _ = send(tx.unwrap(), msg); + for addr in addrs { + let observers_guard = observers.read().await; + let tx = observers_guard.get(addr); + if tx.is_none() { + continue; } + let _ = send(tx.unwrap(), msg); + } } pub async fn gen_match_id(matches: &Matches) -> u32 { - let matches_guard = matches.read().await; - let mut result = rand::rng().random_range(100000..=999999); - while matches_guard.get(&result).is_some() { - result = rand::rng().random_range(100000..=999999); - } - result + let matches_guard = matches.read().await; + let mut result = rand::rng().random_range(100000..=999999); + while matches_guard.get(&result).is_some() { + result = rand::rng().random_range(100000..=999999); + } + result } pub fn random_move(board: &[Vec]) -> usize { - let mut random = rand::rng().random_range(0..7); - while board[random][5] != Color::None { - random = rand::rng().random_range(0..7); - } + let mut random = rand::rng().random_range(0..7); + while board[random][5] != Color::None { + random = rand::rng().random_range(0..7); + } - random + random } pub fn send(tx: &UnboundedSender, text: &str) -> Result<(), SendError> { - tx.send(Message::text(text)) + tx.send(Message::text(text)) } diff --git a/src/main.rs b/src/main.rs index ae5bb6a..ca228f7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,340 +10,330 @@ use tracing::{error, info}; #[tokio::main] async fn main() -> Result<(), anyhow::Error> { - // Initialize logging - tracing_subscriber::fmt::init(); + // Initialize logging + tracing_subscriber::fmt::init(); - let args: Vec = env::args().collect(); - let demo_mode = args.get(1).is_some() && args.get(1).unwrap() == "demo"; - if demo_mode { - info!("Starting server in DEMO MODE"); - } - let admin_password = env::var("ADMIN_AUTH").unwrap_or_else(|_| String::from("admin")); - info!("Admin password: {}", admin_password); - let admin_password = Arc::new(admin_password); + let args: Vec = env::args().collect(); + let demo_mode = args.get(1).is_some() && args.get(1).unwrap() == "demo"; + if demo_mode { + info!("Starting server in DEMO MODE"); + } + let admin_password = env::var("ADMIN_AUTH").unwrap_or_else(|_| String::from("admin")); + info!("Admin password: {}", admin_password); + let admin_password = Arc::new(admin_password); - let addr = "0.0.0.0:8080"; - let listener = TcpListener::bind(&addr).await?; - let hosted_addr = if local_ip().is_ok() { - local_ip()?.to_string() + ":8080" - } else { - addr.to_string() - }; - info!("WebSocket server listening on: {}", hosted_addr); + let addr = "0.0.0.0:8080"; + let listener = TcpListener::bind(&addr).await?; + let hosted_addr = if local_ip().is_ok() { + local_ip()?.to_string() + ":8080" + } else { + addr.to_string() + }; + info!("WebSocket server listening on: {}", hosted_addr); - let server_data = Arc::new(Server::new(admin_password.as_ref().clone(), demo_mode)); + let server_data = Arc::new(Server::new(admin_password.as_ref().clone(), demo_mode)); - while let Ok((stream, addr)) = listener.accept().await { - tokio::spawn(handle_connection(stream, addr, server_data.clone())); - } + while let Ok((stream, addr)) = listener.accept().await { + tokio::spawn(handle_connection(stream, addr, server_data.clone())); + } - Ok(()) + Ok(()) } async fn handle_connection( - stream: TcpStream, - addr: SocketAddr, - sd: Arc, + stream: TcpStream, + addr: SocketAddr, + sd: Arc, ) -> Result<(), anyhow::Error> { - info!("New WebSocket connection from: {}", addr); + info!("New WebSocket connection from: {}", addr); - let ws_stream = accept_async(stream).await?; - let (mut ws_sender, mut ws_receiver) = ws_stream.split(); - let (tx, mut rx) = tokio::sync::mpsc::unbounded_channel(); + let ws_stream = accept_async(stream).await?; + let (mut ws_sender, mut ws_receiver) = ws_stream.split(); + let (tx, mut rx) = tokio::sync::mpsc::unbounded_channel(); - // Store the client - sd.observers.write().await.insert(addr, tx.clone()); + // Store the client + sd.observers.write().await.insert(addr, tx.clone()); - // Spawn task to handle outgoing messages - let send_task = tokio::spawn(async move { - while let Some(msg) = rx.recv().await { - if ws_sender.send(msg.clone()).await.is_err() { - break; + // Spawn task to handle outgoing messages + let send_task = tokio::spawn(async move { + while let Some(msg) = rx.recv().await { + if ws_sender.send(msg.clone()).await.is_err() { + break; + } + } + }); + + // Handle incoming messages + while let Some(msg) = ws_receiver.next().await { + match msg { + Ok(Message::Text(text)) => { + info!("Received text from {}: {}", addr, text); + let parts: Vec<&str> = text.split(':').collect(); + let cmd = parts[0]; + match cmd { + "CONNECT" => { + if parts.len() > 1 { + let requested_username = parts[1].to_string(); + if let Err(e) = sd.handle_connect_cmd(addr, tx.clone(), requested_username).await { + error!("handle_connect: {}", e); + let _ = send(&tx, e.to_string().as_str()); + } + } else { + let _ = send(&tx, "ERROR:INVALID:ID:"); } - } - }); - - // Handle incoming messages - while let Some(msg) = ws_receiver.next().await { - match msg { - Ok(Message::Text(text)) => { - info!("Received text from {}: {}", addr, text); - let parts: Vec<&str> = text.split(':').collect(); - let cmd = parts[0]; - match cmd { - "CONNECT" => { - if parts.len() > 1 { - let requested_username = parts[1].to_string(); - if let Err(e) = - sd.handle_connect_cmd(addr, tx.clone(), requested_username).await - { - error!("handle_connect: {}", e); - let _ = send(&tx, e.to_string().as_str()); - } - } else { - let _ = send(&tx, "ERROR:INVALID:ID:"); - } - } - "RECONNECT" => { - if parts.len() > 1 { - let requested_username = parts[1].to_string(); - if let Err(e) = - sd.handle_reconnect_cmd(addr, tx.clone(), requested_username).await - { - error!("handle_reconnect: {}", e); - let _ = send(&tx, e.to_string().as_str()); - } - } else { - let _ = send(&tx, "ERROR:INVALID:RECONNECT:"); - } - } - "DISCONNECT" => { - if let Err(e) = sd.handle_disconnect_cmd(addr, tx.clone()).await { - error!("handle_disconnect: {}", e); - let _ = send(&tx, e.to_string().as_str()); - } - } - "READY" => { - if let Err(e) = sd.handle_ready(addr, tx.clone()).await { - error!("handle_ready: {}", e); - let _ = send(&tx, e.to_string().as_str()); - } - } - "PLAY" => { - if parts.len() > 1 { - match parts[1].parse::() { - Ok(column) => { - if let Err(e) = sd.handle_play(addr, tx.clone(), column).await { - error!("handle_play: {}", e); - let _ = send(&tx, e.to_string().as_str()); - } - } - Err(_) => { - let _ = send(&tx, "ERROR:INVALID:MOVE"); - } - } - } else { - let _ = send(&tx, "ERROR:INVALID:MOVE"); - } - } - "PLAYER" => { - if parts.get(1) == Some(&"LIST") { - if let Err(e) = sd.handle_player_list(tx.clone()).await { - error!("handle_player_list: {}", e); - let _ = send(&tx, e.to_string().as_str()); - } - } else { - let _ = send(&tx, "ERROR:INVALID:PLAYER"); - } - } - "GAME" => { - if parts.get(1) == Some(&"LIST") { - if let Err(e) = sd.handle_game_list(tx.clone()).await { - error!("handle_game_list: {}", e); - let _ = send(&tx, e.to_string().as_str()); - } - } else if parts.get(1) == Some(&"WATCH") && parts.len() > 2 { - match parts[2].parse::() { - Ok(match_id) => { - if let Err(e) = - sd.handle_game_watch(tx.clone(), match_id, addr).await - { - error!("handle_game_watch: {}", e); - let _ = send(&tx, e.to_string().as_str()); - } - } - Err(_) => { - let _ = send(&tx, "ERROR:INVALID:WATCH"); - } - } - } else if parts.get(1) == Some(&"TERMINATE") && parts.len() > 2 { - match parts[2].parse::() { - Ok(match_id) => { - if let Err(e) = sd.handle_game_terminate(addr, match_id).await { - error!("handle_game_terminate: {}", e); - let _ = send(&tx, e.to_string().as_str()); - } - } - Err(_) => { - let _ = send(&tx, "ERROR:INVALID:TERMINATE"); - } - } - } else if parts.get(1) == Some(&"AWARD") && parts.len() > 3 { - match parts[2].parse::() { - Ok(match_id) => { - let winner = parts[3].to_string(); - if let Err(e) = - sd.handle_game_award_winner(addr, match_id, winner).await - { - error!("handle_game_award_winner: {}", e); - let _ = send(&tx, e.to_string().as_str()); - } - } - Err(_) => { - let _ = send(&tx, "ERROR:INVALID:AWARD"); - } - } - } else { - let _ = send(&tx, "ERROR:INVALID:GAME"); - } - } - "ADMIN" => { - if parts.get(1) == Some(&"AUTH") && parts.len() > 2 { - if let Err(e) = - sd.handle_admin_auth(tx.clone(), addr, parts[2].to_string()).await - { - error!("handle_admin_auth: {}", e); - let _ = send(&tx, e.to_string().as_str()); - } - } else if parts.get(1) == Some(&"KICK") && parts.len() > 2 { - if let Err(e) = sd.handle_admin_kick(addr, parts[2].to_string()).await { - error!("handle_admin_kick: {}", e); - let _ = send(&tx, e.to_string().as_str()); - } - } else { - let _ = send(&tx, "ERROR:INVALID:ADMIN"); - } - } - "TOURNAMENT" => { - if parts.get(1) == Some(&"START") && parts.len() > 2 { - if let Err(e) = - sd.handle_tournament_start(addr, parts[2].to_string()).await - { - error!("handle_tournament_start: {}", e); - let _ = send(&tx, e.to_string().as_str()); - } - } else if parts.get(1) == Some(&"CANCEL") { - if let Err(e) = sd.handle_tournament_cancel(addr).await { - error!("handle_tournament_cancel: {}", e); - let _ = send(&tx, e.to_string().as_str()); - } - } else { - let _ = send(&tx, "ERROR:INVALID:TOURNAMENT"); - } - } - "GET" => { - if let Some(data_id) = parts.get(1) { - if let Err(e) = - sd.handle_get_data(tx.clone(), data_id.to_string()).await - { - error!("handle_get_data: {}", e); - let _ = send(&tx, e.to_string().as_str()); - } - } else { - let _ = send(&tx, "ERROR:INVALID:GET"); - } - } - "SET" => { - if parts.len() > 2 { - let data_id = parts[1].to_string(); - let data_value = parts[2].to_string(); - if let Err(e) = - sd.handle_set_data(tx.clone(), addr, data_id, data_value).await - { - error!("handle_set_data: {}", e); - let _ = send(&tx, e.to_string().as_str()); - } - } else { - let _ = send(&tx, "ERROR:INVALID:SET"); - } - } - "RESERVATION" => { - if parts.get(1) == Some(&"ADD") && parts.len() == 3 { - let usernames = parts[2].split(",").collect::>(); - if usernames.len() != 2 { - error!("handle_reservation_add: invalid number of usernames"); - let _ = send(&tx, "ERROR:INVALID:RESERVATION"); - continue; - } - - let player1_username = usernames[0].to_string(); - let player2_username = usernames[1].to_string(); - - if let Err(e) = sd.handle_reservation_add(tx.clone(), addr, player1_username, player2_username).await { - error!("handle_reservation_add: {}", e); - let _ = send(&tx, e.to_string().as_str()); - } - } else if parts.get(1) == Some(&"DELETE") && parts.len() == 3 { - let usernames = parts[2].split(",").collect::>(); - if usernames.len() != 2 { - error!("handle_reservation_delete: invalid number of usernames"); - let _ = send(&tx, "ERROR:INVALID:RESERVATION"); - continue; - } - - let player1_username = usernames[0].to_string(); - let player2_username = usernames[1].to_string(); - - if let Err(e) = sd.handle_reservation_delete(tx.clone(), addr, player1_username, player2_username).await { - error!("handle_reservation_delete: {}", e); - let _ = send(&tx, e.to_string().as_str()); - } - } else if parts.get(1) == Some(&"GET") { - if let Err(e) = sd.handle_reservation_get(tx.clone(), addr).await { - error!("handle_reservation_get: {}", e); - let _ = send(&tx, e.to_string().as_str()); - } - } else { - let _ = send(&tx, "ERROR:INVALID:RESERVATION"); - } - } - _ => { - let _ = send(&tx, "ERROR:UNKNOWN"); - } + } + "RECONNECT" => { + if parts.len() > 1 { + let requested_username = parts[1].to_string(); + if let Err(e) = sd.handle_reconnect_cmd(addr, tx.clone(), requested_username).await { + error!("handle_reconnect: {}", e); + let _ = send(&tx, e.to_string().as_str()); + } + } else { + let _ = send(&tx, "ERROR:INVALID:RECONNECT:"); + } + } + "DISCONNECT" => { + if let Err(e) = sd.handle_disconnect_cmd(addr, tx.clone()).await { + error!("handle_disconnect: {}", e); + let _ = send(&tx, e.to_string().as_str()); + } + } + "READY" => { + if let Err(e) = sd.handle_ready(addr, tx.clone()).await { + error!("handle_ready: {}", e); + let _ = send(&tx, e.to_string().as_str()); + } + } + "PLAY" => { + if parts.len() > 1 { + match parts[1].parse::() { + Ok(column) => { + if let Err(e) = sd.handle_play(addr, tx.clone(), column).await { + error!("handle_play: {}", e); + let _ = send(&tx, e.to_string().as_str()); + } } + Err(_) => { + let _ = send(&tx, "ERROR:INVALID:MOVE"); + } + } + } else { + let _ = send(&tx, "ERROR:INVALID:MOVE"); } - Ok(Message::Close(_)) => { - info!("Client {} disconnected", addr); - break; + } + "PLAYER" => { + if parts.get(1) == Some(&"LIST") { + if let Err(e) = sd.handle_player_list(tx.clone()).await { + error!("handle_player_list: {}", e); + let _ = send(&tx, e.to_string().as_str()); + } + } else { + let _ = send(&tx, "ERROR:INVALID:PLAYER"); } - Ok(Message::Binary(_)) => { - let _ = send(&tx, "ERROR:UNKNOWN"); + } + "GAME" => { + if parts.get(1) == Some(&"LIST") { + if let Err(e) = sd.handle_game_list(tx.clone()).await { + error!("handle_game_list: {}", e); + let _ = send(&tx, e.to_string().as_str()); + } + } else if parts.get(1) == Some(&"WATCH") && parts.len() > 2 { + match parts[2].parse::() { + Ok(match_id) => { + if let Err(e) = sd.handle_game_watch(tx.clone(), match_id, addr).await { + error!("handle_game_watch: {}", e); + let _ = send(&tx, e.to_string().as_str()); + } + } + Err(_) => { + let _ = send(&tx, "ERROR:INVALID:WATCH"); + } + } + } else if parts.get(1) == Some(&"TERMINATE") && parts.len() > 2 { + match parts[2].parse::() { + Ok(match_id) => { + if let Err(e) = sd.handle_game_terminate(addr, match_id).await { + error!("handle_game_terminate: {}", e); + let _ = send(&tx, e.to_string().as_str()); + } + } + Err(_) => { + let _ = send(&tx, "ERROR:INVALID:TERMINATE"); + } + } + } else if parts.get(1) == Some(&"AWARD") && parts.len() > 3 { + match parts[2].parse::() { + Ok(match_id) => { + let winner = parts[3].to_string(); + if let Err(e) = sd.handle_game_award_winner(addr, match_id, winner).await { + error!("handle_game_award_winner: {}", e); + let _ = send(&tx, e.to_string().as_str()); + } + } + Err(_) => { + let _ = send(&tx, "ERROR:INVALID:AWARD"); + } + } + } else { + let _ = send(&tx, "ERROR:INVALID:GAME"); } - Ok(_) => {} // Ping packets, we can ignore, they get handled for us - Err(e) => { - error!("WebSocket error for {}: {}", addr, e); - break; + } + "ADMIN" => { + if parts.get(1) == Some(&"AUTH") && parts.len() > 2 { + if let Err(e) = sd.handle_admin_auth(tx.clone(), addr, parts[2].to_string()).await { + error!("handle_admin_auth: {}", e); + let _ = send(&tx, e.to_string().as_str()); + } + } else if parts.get(1) == Some(&"KICK") && parts.len() > 2 { + if let Err(e) = sd.handle_admin_kick(addr, parts[2].to_string()).await { + error!("handle_admin_kick: {}", e); + let _ = send(&tx, e.to_string().as_str()); + } + } else { + let _ = send(&tx, "ERROR:INVALID:ADMIN"); } + } + "TOURNAMENT" => { + if parts.get(1) == Some(&"START") && parts.len() > 2 { + if let Err(e) = sd.handle_tournament_start(addr, parts[2].to_string()).await { + error!("handle_tournament_start: {}", e); + let _ = send(&tx, e.to_string().as_str()); + } + } else if parts.get(1) == Some(&"CANCEL") { + if let Err(e) = sd.handle_tournament_cancel(addr).await { + error!("handle_tournament_cancel: {}", e); + let _ = send(&tx, e.to_string().as_str()); + } + } else { + let _ = send(&tx, "ERROR:INVALID:TOURNAMENT"); + } + } + "GET" => { + if let Some(data_id) = parts.get(1) { + if let Err(e) = sd.handle_get_data(tx.clone(), data_id.to_string()).await { + error!("handle_get_data: {}", e); + let _ = send(&tx, e.to_string().as_str()); + } + } else { + let _ = send(&tx, "ERROR:INVALID:GET"); + } + } + "SET" => { + if parts.len() > 2 { + let data_id = parts[1].to_string(); + let data_value = parts[2].to_string(); + if let Err(e) = sd.handle_set_data(tx.clone(), addr, data_id, data_value).await { + error!("handle_set_data: {}", e); + let _ = send(&tx, e.to_string().as_str()); + } + } else { + let _ = send(&tx, "ERROR:INVALID:SET"); + } + } + "RESERVATION" => { + if parts.get(1) == Some(&"ADD") && parts.len() == 3 { + let usernames = parts[2].split(",").collect::>(); + if usernames.len() != 2 { + error!("handle_reservation_add: invalid number of usernames"); + let _ = send(&tx, "ERROR:INVALID:RESERVATION"); + continue; + } + + let player1_username = usernames[0].to_string(); + let player2_username = usernames[1].to_string(); + + if let Err(e) = sd + .handle_reservation_add(tx.clone(), addr, player1_username, player2_username) + .await + { + error!("handle_reservation_add: {}", e); + let _ = send(&tx, e.to_string().as_str()); + } + } else if parts.get(1) == Some(&"DELETE") && parts.len() == 3 { + let usernames = parts[2].split(",").collect::>(); + if usernames.len() != 2 { + error!("handle_reservation_delete: invalid number of usernames"); + let _ = send(&tx, "ERROR:INVALID:RESERVATION"); + continue; + } + + let player1_username = usernames[0].to_string(); + let player2_username = usernames[1].to_string(); + + if let Err(e) = sd + .handle_reservation_delete(tx.clone(), addr, player1_username, player2_username) + .await + { + error!("handle_reservation_delete: {}", e); + let _ = send(&tx, e.to_string().as_str()); + } + } else if parts.get(1) == Some(&"GET") { + if let Err(e) = sd.handle_reservation_get(tx.clone(), addr).await { + error!("handle_reservation_get: {}", e); + let _ = send(&tx, e.to_string().as_str()); + } + } else { + let _ = send(&tx, "ERROR:INVALID:RESERVATION"); + } + } + _ => { + let _ = send(&tx, "ERROR:UNKNOWN"); + } } + } + Ok(Message::Close(_)) => { + info!("Client {} disconnected", addr); + break; + } + Ok(Message::Binary(_)) => { + let _ = send(&tx, "ERROR:UNKNOWN"); + } + Ok(_) => {} // Ping packets, we can ignore, they get handled for us + Err(e) => { + error!("WebSocket error for {}: {}", addr, e); + break; + } + } + } + + // Clean up + send_task.abort(); + + // Remove and terminate any matches + // We may not be a client disconnecting, do this check + let clients_guard = sd.clients.read().await; + if clients_guard.get(&addr).is_some() { + let client = clients_guard.get(&addr).unwrap().read().await; + let username = client.username.clone(); + let tournament_guard = sd.tournament.read().await; + if client.current_match.is_some() { + sd.disconnected_clients.write().await.push(username.clone()); + } else if tournament_guard.is_some() { + let tourney = tournament_guard.clone().unwrap(); + if tourney.read().await.contains_player(addr) { + sd.disconnected_clients.write().await.push(username.clone()); + } } - // Clean up - send_task.abort(); + drop(client); + drop(clients_guard); - // Remove and terminate any matches - // We may not be a client disconnecting, do this check - let clients_guard = sd.clients.read().await; - if clients_guard.get(&addr).is_some() { - let client = clients_guard.get(&addr).unwrap().read().await; - let username = client.username.clone(); - let tournament_guard = sd.tournament.read().await; - if client.current_match.is_some() { - sd.disconnected_clients.write().await.push(username.clone()); - } else if tournament_guard.is_some() { - let tourney = tournament_guard.clone().unwrap(); - if tourney.read().await.contains_player(addr) { - sd.disconnected_clients.write().await.push(username.clone()); - } - } + sd.clients.write().await.remove(&addr); + sd.usernames.write().await.remove(&username); + } - drop(client); - drop(clients_guard); + sd.observers.write().await.remove(&addr); - sd.clients.write().await.remove(&addr); - sd.usernames.write().await.remove(&username); + let mut admin_guard = sd.admin.write().await; + if let Some(admin_addr) = *admin_guard { + if admin_addr == addr { + *admin_guard = None; } + } + drop(admin_guard); - sd.observers.write().await.remove(&addr); + info!("Client {} removed", addr); - let mut admin_guard = sd.admin.write().await; - if let Some(admin_addr) = *admin_guard { - if admin_addr == addr { - *admin_guard = None; - } - } - drop(admin_guard); - - info!("Client {} removed", addr); - - Ok(()) + Ok(()) } diff --git a/src/server.rs b/src/server.rs index f4384b7..f1a27b7 100644 --- a/src/server.rs +++ b/src/server.rs @@ -4,1326 +4,1325 @@ use std::time::Instant; use crate::{tournaments::*, types::*, *}; pub struct Server { - pub clients: Clients, - pub disconnected_clients: Arc>>, - pub usernames: Usernames, - pub observers: Observers, - pub matches: Matches, - pub reservations: Reservations, - pub admin: Arc>>, - pub admin_password: Arc, - pub tournament: WrappedTournament, - pub waiting_timeout: Arc>, - pub max_timeout: Arc>, - pub demo_mode: Arc>, + pub clients: Clients, + pub disconnected_clients: Arc>>, + pub usernames: Usernames, + pub observers: Observers, + pub matches: Matches, + pub reservations: Reservations, + pub admin: Arc>>, + pub admin_password: Arc, + pub tournament: WrappedTournament, + pub waiting_timeout: Arc>, + pub max_timeout: Arc>, + pub demo_mode: Arc>, } impl Server { - pub fn new(admin_password: String, demo_mode: bool) -> Server { - Server { - clients: Arc::new(RwLock::new(HashMap::new())), - disconnected_clients: Arc::new(RwLock::new(Vec::new())), - usernames: Arc::new(RwLock::new(HashMap::new())), - observers: Arc::new(RwLock::new(HashMap::new())), - matches: Arc::new(RwLock::new(HashMap::new())), - reservations: Arc::new(RwLock::new(Vec::new())), - admin: Arc::new(RwLock::new(None)), - admin_password: Arc::new(admin_password), - tournament: Arc::new(RwLock::new(None)), - waiting_timeout: Arc::new(RwLock::new(5000)), - max_timeout: Arc::new(RwLock::new(30000)), - demo_mode: Arc::new(RwLock::new(demo_mode)), - } + pub fn new(admin_password: String, demo_mode: bool) -> Server { + Server { + clients: Arc::new(RwLock::new(HashMap::new())), + disconnected_clients: Arc::new(RwLock::new(Vec::new())), + usernames: Arc::new(RwLock::new(HashMap::new())), + observers: Arc::new(RwLock::new(HashMap::new())), + matches: Arc::new(RwLock::new(HashMap::new())), + reservations: Arc::new(RwLock::new(Vec::new())), + admin: Arc::new(RwLock::new(None)), + admin_password: Arc::new(admin_password), + tournament: Arc::new(RwLock::new(None)), + waiting_timeout: Arc::new(RwLock::new(5000)), + max_timeout: Arc::new(RwLock::new(30000)), + demo_mode: Arc::new(RwLock::new(demo_mode)), + } + } + + pub async fn handle_connect_cmd( + &self, + addr: SocketAddr, + tx: UnboundedSender, + requested_username: String, + ) -> Result<(), anyhow::Error> { + if requested_username.is_empty() { + return Err(anyhow::anyhow!(format!( + "ERROR:INVALID:ID:{}", + requested_username + ))); } - pub async fn handle_connect_cmd( - &self, - addr: SocketAddr, - tx: UnboundedSender, - requested_username: String, - ) -> Result<(), anyhow::Error> { - if requested_username.is_empty() { - return Err(anyhow::anyhow!(format!( - "ERROR:INVALID:ID:{}", - requested_username - ))); + if requested_username == SERVER_PLAYER_USERNAME { + return Err(anyhow::anyhow!(format!( + "ERROR:INVALID:ID:{}", + requested_username + ))); + } + + let mut reconnecting = false; + let disconnected_guard = self.disconnected_clients.read().await; + if disconnected_guard.contains(&requested_username) { + reconnecting = true; + } + + let clients_guard = self.clients.read().await; + let mut reconnecting_client = None; + for client in clients_guard.values() { + if requested_username == client.read().await.username { + if reconnecting { + reconnecting_client = Some(client.clone()); + break; } - if requested_username == SERVER_PLAYER_USERNAME { - return Err(anyhow::anyhow!(format!( - "ERROR:INVALID:ID:{}", - requested_username - ))); - } + return Err(anyhow::anyhow!(format!( + "ERROR:INVALID:ID:{}", + requested_username + ))); + } + } - let mut reconnecting = false; - let disconnected_guard = self.disconnected_clients.read().await; + drop(clients_guard); + + self.remove_observer_from_all_matches(addr).await; + self.observers.write().await.remove(&addr); + self.usernames.write().await.insert(requested_username.clone(), addr); + let _ = send(&tx, "CONNECT:ACK"); + + if !reconnecting { + self.clients.write().await.insert( + addr.to_string().parse()?, + Arc::new(RwLock::new(Client::new( + requested_username, + tx.clone(), + addr.to_string().parse()?, + ))), + ); + + return Ok(()); + } + + // reconnecting + self.disconnected_clients.write().await.retain(|name| name != &requested_username); + let client_guard = reconnecting_client.unwrap(); + let mut client = client_guard.write().await; + let old_addr = client.addr; + client.addr = addr; + client.connection = tx.clone(); + // I don't think this will fail + let match_id = client.current_match.unwrap(); + let client_color = client.color; + + drop(client); + + let mut clients_guard = self.clients.write().await; + clients_guard.remove(&old_addr); + clients_guard.insert(addr, client_guard.clone()); + drop(clients_guard); + + let tournament_guard = self.tournament.read().await; + if tournament_guard.is_some() { + let tourney = tournament_guard.clone().unwrap(); + tourney.write().await.inform_reconnect(old_addr, addr); + } + drop(tournament_guard); + + let matches_guard = self.matches.read().await; + let mut the_match = matches_guard.get(&match_id).unwrap().write().await; + if the_match.demo_mode { + drop(the_match); + drop(matches_guard); + self.terminate_match(match_id).await; + return Ok(()); + } else { + the_match.ledger.clear(); + the_match.board = vec![vec![Color::None; 6]; 7]; + let opponent_addr = if the_match.player1 == addr { + the_match.player2 + } else { + the_match.player1 + }; + + if the_match.wait_thread.is_some() { + the_match.wait_thread.as_ref().unwrap().abort(); + } + + if the_match.timeout_thread.is_some() { + the_match.timeout_thread.as_ref().unwrap().abort(); + } + + let clients_guard = self.clients.read().await; + let opponent = clients_guard.get(&opponent_addr).unwrap().read().await; + let _ = send(&opponent.connection, "GAME:TERMINATED"); + let _ = send( + &tx, + &format!("GAME:START:{}", bool::from(client_color) as u8), + ); + let _ = send( + &opponent.connection, + &format!("GAME:START:{}", bool::from(opponent.color) as u8), + ); + } + + Ok(()) + } + + pub async fn handle_reconnect_cmd( + &self, + addr: SocketAddr, + tx: UnboundedSender, + requested_username: String, + ) -> Result<(), anyhow::Error> { + let clients_guard = self.clients.read().await; + let disconnected_guard = self.disconnected_clients.read().await; + let mut found_client = None; + + for client in clients_guard.values() { + if requested_username == client.read().await.username { if disconnected_guard.contains(&requested_username) { - reconnecting = true; + found_client = Some(client.clone()); } + break; + } + } - let clients_guard = self.clients.read().await; - let mut reconnecting_client = None; - for client in clients_guard.values() { - if requested_username == client.read().await.username { - if reconnecting { - reconnecting_client = Some(client.clone()); - break; - } + drop(clients_guard); + drop(disconnected_guard); - return Err(anyhow::anyhow!(format!( - "ERROR:INVALID:ID:{}", - requested_username - ))); - } - } + if let Some(client_guard) = found_client { + self.disconnected_clients.write().await.retain(|name| name != &requested_username); + let mut client = client_guard.write().await; + let old_addr = client.addr; + client.addr = addr; + client.connection = tx.clone(); - drop(clients_guard); + let mut clients_guard = self.clients.write().await; + clients_guard.remove(&old_addr); + clients_guard.insert(addr, client_guard.clone()); + drop(clients_guard); - self.remove_observer_from_all_matches(addr).await; - self.observers.write().await.remove(&addr); - self.usernames.write().await.insert(requested_username.clone(), addr); - let _ = send(&tx, "CONNECT:ACK"); + let _ = send(&tx, "RECONNECT:ACK"); - if !reconnecting { - self.clients.write().await.insert( - addr.to_string().parse()?, - Arc::new(RwLock::new(Client::new( - requested_username, - tx.clone(), - addr.to_string().parse()?, - ))), - ); + let matches_guard = self.matches.read().await; + let the_match = matches_guard.get(&client.current_match.unwrap()).unwrap().read().await; - return Ok(()); - } + let last = the_match.ledger.last(); + if last.is_some() && last.unwrap().0 != client.color { + let _ = send( + &tx, + &format!("OPPONENT:{}", the_match.ledger.last().unwrap().1), + ); + } + } else { + return Err(anyhow::anyhow!(format!( + "ERROR:INVALID:RECONNECT:{}", + requested_username + ))); + } - // reconnecting - self.disconnected_clients.write().await.retain(|name| name != &requested_username); - let client_guard = reconnecting_client.unwrap(); - let mut client = client_guard.write().await; - let old_addr = client.addr; - client.addr = addr; - client.connection = tx.clone(); - // I don't think this will fail - let match_id = client.current_match.unwrap(); - let client_color = client.color; + Ok(()) + } - drop(client); + pub async fn handle_disconnect_cmd( + &self, + addr: SocketAddr, + tx: UnboundedSender, + ) -> Result<(), anyhow::Error> { + let clients_guard = self.clients.read().await; + let client_opt = clients_guard.get(&addr).cloned(); + + if client_opt.is_none() { + return Err(anyhow::anyhow!("ERROR:INVALID:DISCONNECT")); + } + + drop(clients_guard); + + let mut client = client_opt.as_ref().unwrap().write().await; + self.usernames.write().await.remove(&client.username); + client.ready = false; + client.color = Color::None; + + if client.current_match.is_some() { + let match_id = client.current_match.unwrap(); + drop(client); + + self.terminate_match(match_id).await; + } + + self.clients.write().await.remove(&addr); + self.observers.write().await.insert(addr, tx.clone()); + let _ = send(&tx, "DISCONNECT:ACK"); + + Ok(()) + } + + pub async fn handle_ready( + &self, + addr: SocketAddr, + tx: UnboundedSender, + ) -> Result<(), anyhow::Error> { + let clients_guard = self.clients.read().await; + if clients_guard.get(&addr).is_none() { + return Err(anyhow::anyhow!("ERROR:INVALID")); + } + + if clients_guard.get(&addr).unwrap().read().await.ready { + return Err(anyhow::anyhow!("ERROR:INVALID:READY")); + } + + let mut client = clients_guard.get(&addr).unwrap().write().await; + let client_username = client.username.clone(); + client.ready = true; + let _ = send(&tx, "READY:ACK"); + drop(client); + drop(clients_guard); + + if let Some(opponent_addr) = self.find_reservation_opponent(client_username).await { + let clients_guard = self.clients.read().await; + let mut client = clients_guard.get(&addr).unwrap().write().await; + let mut opponent = clients_guard.get(&opponent_addr).unwrap().write().await; + + let match_id: u32 = gen_match_id(&self.matches).await; + let new_match = Arc::new(RwLock::new(Match::new( + match_id, + addr, + opponent_addr, + false, + ))); + self.matches.write().await.insert(match_id, new_match.clone()); + + client.ready = false; + client.current_match = Some(match_id); + client.color = if new_match.read().await.player1 == addr { + let _ = send(&tx, "GAME:START:1"); + let _ = send(&opponent.connection, "GAME:START:0"); + Color::Red + } else { + let _ = send(&tx, "GAME:START:0"); + let _ = send(&opponent.connection, "GAME:START:1"); + Color::Yellow + }; + + opponent.ready = false; + opponent.current_match = Some(match_id); + opponent.color = !client.color; + + self + .reservations + .write() + .await + .retain(|(p1, p2)| !(p1 == &client.username && p2 == &opponent.username)); + + return Ok(()); + } + + let clients_guard = self.clients.read().await; + let mut client = clients_guard.get(&addr).unwrap().write().await; + let is_demo_mode = self.demo_mode.read().await.clone(); + if is_demo_mode { + let match_id: u32 = gen_match_id(&self.matches).await; + let new_match = Arc::new(RwLock::new(Match::new( + match_id, + addr.to_string().parse()?, + SERVER_PLAYER_ADDR.to_string().parse()?, + is_demo_mode, + ))); + self.matches.write().await.insert(match_id, new_match.clone()); + client.ready = false; + client.current_match = Some(match_id); + client.color = if new_match.read().await.player1 == addr { + let _ = send(&tx, "GAME:START:1"); + Color::Red + } else { + let _ = send(&tx, "GAME:START:0"); + Color::Yellow + }; + } + + Ok(()) + } + + pub async fn handle_play( + &self, + addr: SocketAddr, + tx: UnboundedSender, + column: usize, + ) -> Result<(), anyhow::Error> { + let clients_guard = self.clients.read().await; + let client_opt = clients_guard.get(&addr); + + // Check if client is valid + if client_opt.is_none() || client_opt.unwrap().read().await.current_match.is_none() { + return Err(anyhow::anyhow!("ERROR:INVALID:MOVE")); + } + let client = client_opt.unwrap().read().await; + + let matches_guard = self.matches.read().await; + let current_match = matches_guard.get(&client.current_match.unwrap()).unwrap().read().await; + + let opponent = { + let mut result = None; + + if !current_match.demo_mode { + let opponent_addr = if addr == current_match.player1 { + current_match.player2 + } else { + current_match.player1 + }; + + result = Some(clients_guard.get(&opponent_addr).cloned().unwrap()); + } + + result + }; + + // Check if it's their move + let mut invalid = false; + if (current_match.ledger.is_empty() && current_match.player1 != addr) + || (current_match.ledger.last().is_some() + && current_match.ledger.last().unwrap().0 == client.color) + { + let _ = send(&tx, "ERROR:INVALID:MOVE"); + invalid = true; + } + + drop(current_match); + drop(matches_guard); + + let mut matches_guard = self.matches.write().await; + let mut current_match = + matches_guard.get_mut(&client.current_match.unwrap()).unwrap().write().await; + + // Check if valid move + if column >= 7 && !invalid { + let _ = send(&tx, "ERROR:INVALID:MOVE"); + invalid = true; + } + + if current_match.board[column][5] != Color::None && !invalid { + let _ = send(&tx, "ERROR:INVALID:MOVE"); + invalid = true; + } + + // Terminate games if a player makes an invalid move + if invalid { + let current_match_id = current_match.id; + let is_demo_mode = current_match.demo_mode; + let viewers = current_match.viewers.clone(); + + drop(current_match); + drop(matches_guard); + drop(client); + drop(clients_guard); + + if is_demo_mode { + self.terminate_match(current_match_id).await; + tx.send(Message::Close(None))?; + } else { + let opponent = opponent.unwrap(); + let mut opponent = opponent.write().await; + + let _ = send(&tx, "GAME:LOSS"); + let _ = send(&opponent.connection, "GAME:WINS"); + self.broadcast_message(&viewers, &format!("GAME:WIN:{}", opponent.username)).await; + + opponent.current_match = None; + opponent.color = Color::None; + let opponent_addr = opponent.addr; + drop(opponent); let mut clients_guard = self.clients.write().await; - clients_guard.remove(&old_addr); - clients_guard.insert(addr, client_guard.clone()); - drop(clients_guard); - - let tournament_guard = self.tournament.read().await; - if tournament_guard.is_some() { - let tourney = tournament_guard.clone().unwrap(); - tourney.write().await.inform_reconnect(old_addr, addr); - } - drop(tournament_guard); - - let matches_guard = self.matches.read().await; - let mut the_match = matches_guard.get(&match_id).unwrap().write().await; - if the_match.demo_mode { - drop(the_match); - drop(matches_guard); - self.terminate_match(match_id).await; - return Ok(()); - } else { - the_match.ledger.clear(); - the_match.board = vec![vec![Color::None; 6]; 7]; - let opponent_addr = if the_match.player1 == addr { - the_match.player2 - } else { - the_match.player1 - }; - - if the_match.wait_thread.is_some() { - the_match.wait_thread.as_ref().unwrap().abort(); - } - - if the_match.timeout_thread.is_some() { - the_match.timeout_thread.as_ref().unwrap().abort(); - } - - let clients_guard = self.clients.read().await; - let opponent = clients_guard.get(&opponent_addr).unwrap().read().await; - let _ = send(&opponent.connection, "GAME:TERMINATED"); - let _ = send( - &tx, - &format!("GAME:START:{}", bool::from(client_color) as u8), - ); - let _ = send( - &opponent.connection, - &format!("GAME:START:{}", bool::from(opponent.color) as u8), - ); - } - - Ok(()) - } - - pub async fn handle_reconnect_cmd( - &self, - addr: SocketAddr, - tx: UnboundedSender, - requested_username: String, - ) -> Result<(), anyhow::Error> { - let clients_guard = self.clients.read().await; - let disconnected_guard = self.disconnected_clients.read().await; - let mut found_client = None; - - for client in clients_guard.values() { - if requested_username == client.read().await.username { - if disconnected_guard.contains(&requested_username) { - found_client = Some(client.clone()); - } - break; - } - } - - drop(clients_guard); - drop(disconnected_guard); - - if let Some(client_guard) = found_client { - self.disconnected_clients.write().await.retain(|name| name != &requested_username); - let mut client = client_guard.write().await; - let old_addr = client.addr; - client.addr = addr; - client.connection = tx.clone(); - - let mut clients_guard = self.clients.write().await; - clients_guard.remove(&old_addr); - clients_guard.insert(addr, client_guard.clone()); - drop(clients_guard); - - let _ = send(&tx, "RECONNECT:ACK"); - - let matches_guard = self.matches.read().await; - let the_match = matches_guard.get(&client.current_match.unwrap()).unwrap().read().await; - - let last = the_match.ledger.last(); - if last.is_some() && last.unwrap().0 != client.color { - let _ = send( - &tx, - &format!("OPPONENT:{}", the_match.ledger.last().unwrap().1), - ); - } - } else { - return Err(anyhow::anyhow!(format!( - "ERROR:INVALID:RECONNECT:{}", - requested_username - ))); - } - - Ok(()) - } - - pub async fn handle_disconnect_cmd( - &self, - addr: SocketAddr, - tx: UnboundedSender, - ) -> Result<(), anyhow::Error> { - let clients_guard = self.clients.read().await; - let client_opt = clients_guard.get(&addr).cloned(); - - if client_opt.is_none() { - return Err(anyhow::anyhow!("ERROR:INVALID:DISCONNECT")); - } - - drop(clients_guard); - - let mut client = client_opt.as_ref().unwrap().write().await; - self.usernames.write().await.remove(&client.username); - client.ready = false; + let mut client = clients_guard.get_mut(&addr).unwrap().write().await; + client.current_match = None; client.color = Color::None; - - if client.current_match.is_some() { - let match_id = client.current_match.unwrap(); - drop(client); - - self.terminate_match(match_id).await; - } - - self.clients.write().await.remove(&addr); - self.observers.write().await.insert(addr, tx.clone()); - let _ = send(&tx, "DISCONNECT:ACK"); - - Ok(()) - } - - pub async fn handle_ready( - &self, - addr: SocketAddr, - tx: UnboundedSender, - ) -> Result<(), anyhow::Error> { - let clients_guard = self.clients.read().await; - if clients_guard.get(&addr).is_none() { - return Err(anyhow::anyhow!("ERROR:INVALID")); - } - - if clients_guard.get(&addr).unwrap().read().await.ready { - return Err(anyhow::anyhow!("ERROR:INVALID:READY")); - } - - let mut client = clients_guard.get(&addr).unwrap().write().await; - let client_username = client.username.clone(); - client.ready = true; - let _ = send(&tx, "READY:ACK"); drop(client); - drop(clients_guard); - - if let Some(opponent_addr) = self.find_reservation_opponent(client_username).await { - let clients_guard = self.clients.read().await; - let mut client = clients_guard.get(&addr).unwrap().write().await; - let mut opponent = clients_guard.get(&opponent_addr).unwrap().write().await; - - let match_id: u32 = gen_match_id(&self.matches).await; - let new_match = Arc::new(RwLock::new(Match::new( - match_id, - addr, - opponent_addr, - false, - ))); - self.matches.write().await.insert(match_id, new_match.clone()); - - client.ready = false; - client.current_match = Some(match_id); - client.color = if new_match.read().await.player1 == addr { - let _ = send(&tx, "GAME:START:1"); - let _ = send(&opponent.connection, "GAME:START:0"); - Color::Red - } else { - let _ = send(&tx, "GAME:START:0"); - let _ = send(&opponent.connection, "GAME:START:1"); - Color::Yellow - }; - - opponent.ready = false; - opponent.current_match = Some(match_id); - opponent.color = !client.color; - - self.reservations - .write() - .await - .retain(|(p1, p2)| !(p1 == &client.username && p2 == &opponent.username)); - - return Ok(()); - } - - let clients_guard = self.clients.read().await; - let mut client = clients_guard.get(&addr).unwrap().write().await; - let is_demo_mode = self.demo_mode.read().await.clone(); - if is_demo_mode { - let match_id: u32 = gen_match_id(&self.matches).await; - let new_match = Arc::new(RwLock::new(Match::new( - match_id, - addr.to_string().parse()?, - SERVER_PLAYER_ADDR.to_string().parse()?, - is_demo_mode, - ))); - self.matches.write().await.insert(match_id, new_match.clone()); - client.ready = false; - client.current_match = Some(match_id); - client.color = if new_match.read().await.player1 == addr { - let _ = send(&tx, "GAME:START:1"); - Color::Red - } else { - let _ = send(&tx, "GAME:START:0"); - Color::Yellow - }; - } - - Ok(()) - } - - pub async fn handle_play( - &self, - addr: SocketAddr, - tx: UnboundedSender, - column: usize, - ) -> Result<(), anyhow::Error> { - let clients_guard = self.clients.read().await; - let client_opt = clients_guard.get(&addr); - - // Check if client is valid - if client_opt.is_none() || client_opt.unwrap().read().await.current_match.is_none() { - return Err(anyhow::anyhow!("ERROR:INVALID:MOVE")); - } - let client = client_opt.unwrap().read().await; - - let matches_guard = self.matches.read().await; - let current_match = matches_guard.get(&client.current_match.unwrap()).unwrap().read().await; - - let opponent = { - let mut result = None; - - if !current_match.demo_mode { - let opponent_addr = if addr == current_match.player1 { - current_match.player2 - } else { - current_match.player1 - }; - - result = Some(clients_guard.get(&opponent_addr).cloned().unwrap()); - } - - result - }; - - // Check if it's their move - let mut invalid = false; - if (current_match.ledger.is_empty() && current_match.player1 != addr) - || (current_match.ledger.last().is_some() - && current_match.ledger.last().unwrap().0 == client.color) - { - let _ = send(&tx, "ERROR:INVALID:MOVE"); - invalid = true; - } - - drop(current_match); - drop(matches_guard); - - let mut matches_guard = self.matches.write().await; - let mut current_match = - matches_guard.get_mut(&client.current_match.unwrap()).unwrap().write().await; - - // Check if valid move - if column >= 7 && !invalid { - let _ = send(&tx, "ERROR:INVALID:MOVE"); - invalid = true; - } - - if current_match.board[column][5] != Color::None && !invalid { - let _ = send(&tx, "ERROR:INVALID:MOVE"); - invalid = true; - } - - // Terminate games if a player makes an invalid move - if invalid { - let current_match_id = current_match.id; - let is_demo_mode = current_match.demo_mode; - let viewers = current_match.viewers.clone(); - - drop(current_match); - drop(matches_guard); - drop(client); - drop(clients_guard); - - if is_demo_mode { - self.terminate_match(current_match_id).await; - tx.send(Message::Close(None))?; - } else { - let opponent = opponent.unwrap(); - let mut opponent = opponent.write().await; - - let _ = send(&tx, "GAME:LOSS"); - let _ = send(&opponent.connection, "GAME:WINS"); - self.broadcast_message(&viewers, &format!("GAME:WIN:{}", opponent.username)).await; - - opponent.current_match = None; - opponent.color = Color::None; - let opponent_addr = opponent.addr; - drop(opponent); - - let mut clients_guard = self.clients.write().await; - let mut client = clients_guard.get_mut(&addr).unwrap().write().await; - client.current_match = None; - client.color = Color::None; - drop(client); - - let mut tournament_guard = self.tournament.write().await; - let tourney = tournament_guard.as_mut().unwrap(); - tourney.write().await.inform_winner(opponent_addr, false); - drop(tournament_guard); - - self.matches.write().await.remove(¤t_match_id).unwrap(); - } - return Ok(()); - } - - current_match.place_token(client.color.clone(), column); - - if let Some(timeout_thread) = ¤t_match.timeout_thread { - timeout_thread.abort(); - } - - let mut viewer_messages = Vec::new(); - let viewers = current_match.viewers.clone(); - - viewer_messages.push(format!("GAME:MOVE:{}:{}", client.username, column)); - - // Check game end conditions - let (winner, filled) = current_match.end_game_check(); - - // Send match results - if winner == client.color { - let _ = send(&tx, "GAME:WINS"); - if !current_match.demo_mode { - let opponent = opponent.clone().unwrap(); - let opponent = opponent.read().await; - let _ = send(&opponent.connection, "GAME:LOSS"); - } - viewer_messages.push(format!("GAME:WIN:{}", client.username)); - } else if filled { - let _ = send(&tx, "GAME:DRAW"); - if !current_match.demo_mode { - let opponent = opponent.clone().unwrap(); - let opponent = opponent.read().await; - let _ = send(&opponent.connection, "GAME:DRAW"); - } - viewer_messages.push("GAME:DRAW".to_string()); - } - - // remove match from matchmaker - if winner != Color::None || filled { - let current_match_id = current_match.id; - let is_demo_mode = current_match.demo_mode; - - drop(client); - drop(current_match); - drop(clients_guard); - - let clients_guard = self.clients.read().await; - let mut client = clients_guard.get(&addr).unwrap().write().await; - client.current_match = None; - client.color = Color::None; - drop(client); - - if !is_demo_mode { - let opponent = opponent.clone().unwrap(); - let mut opponent = opponent.write().await; - opponent.current_match = None; - opponent.color = Color::None; - drop(opponent); - } - - matches_guard.remove(¤t_match_id).unwrap(); - - if self.tournament.read().await.is_some() && matches_guard.is_empty() { - drop(matches_guard); - drop(clients_guard); - - let mut tournament_guard = self.tournament.write().await; - let tourney = tournament_guard.as_mut().unwrap(); - tourney.write().await.inform_winner(addr, filled); - tourney.write().await.next(&self).await; - if tourney.read().await.is_completed() { - *tournament_guard = None; - } - } else if self.tournament.read().await.is_none() { - let _ = send(&tx, "TOURNAMENT:END"); - if !is_demo_mode { - let opponent = opponent.clone().unwrap(); - let opponent = opponent.read().await; - let _ = send(&opponent.connection, "TOURNAMENT:END"); - } - } - - return Ok(()); - } - - let default_waiting_time = *self.waiting_timeout.read().await; - let mut adjusted_waiting = - default_waiting_time as i64 + (rand::rng().random_range(0..=50) - 25); - let current_move_time = Instant::now(); - - if current_match.ledger.is_empty() { - adjusted_waiting = 0; - } else { - let last_move_time = current_match.ledger.last().unwrap().2; - let elapsed = current_move_time.duration_since(last_move_time).as_millis() as i64; - adjusted_waiting -= elapsed; - if adjusted_waiting < 0 { - adjusted_waiting = 0; - } - } - - current_match.ledger.push((client.color.clone(), column, current_move_time)); - - let demo_mode = current_match.demo_mode; - let demo_move = random_move(¤t_match.board); - let no_winner = winner == Color::None && !filled; - let observers = self.observers.clone(); - let opponent_move = opponent.clone(); - let client_tx = tx.clone(); - if current_match.demo_mode { - current_match.ledger.push((!client.color, demo_move, Instant::now())); - current_match.place_token(!client.color, demo_move); - } - - current_match.wait_thread = Some(tokio::spawn(async move { - tokio::time::sleep(tokio::time::Duration::from_millis(adjusted_waiting as u64)).await; - - if !demo_mode && no_winner { - let opponent = opponent_move.unwrap(); - let opponent = opponent.read().await; - let _ = send( - &opponent.connection.clone(), - &format!("OPPONENT:{}", column), - ); - } - - for msg in viewer_messages { - broadcast_message(&observers, &viewers, &msg).await; - } - - if demo_mode && no_winner { - tokio::time::sleep(tokio::time::Duration::from_millis(default_waiting_time)).await; - let _ = send(&client_tx, &format!("OPPONENT:{}", demo_move)); - broadcast_message( - &observers, - &viewers, - &format!("GAME:MOVE:{}:{}", SERVER_PLAYER_USERNAME, demo_move), - ) - .await; - } - })); - - let max_timeout = *self.max_timeout.read().await; - let matches = self.matches.clone(); - let tournament = self.tournament.clone(); - let clients = self.clients.clone(); - let match_id = current_match.id; - let ledger_size = current_match.ledger.len(); - let client_username = client.username.clone(); - let client_tx = tx.clone(); - let client_addr = addr.clone(); - let observers = self.observers.clone(); - let viewers = current_match.viewers.clone(); - let opponent_move = opponent.clone(); - current_match.timeout_thread = Some(tokio::spawn(async move { - if demo_mode { - return; - } - tokio::time::sleep(tokio::time::Duration::from_millis(adjusted_waiting as u64)).await; - tokio::time::sleep(tokio::time::Duration::from_millis(max_timeout as u64)).await; - - let matches_guard = matches.read().await; - let the_match = matches_guard.get(&match_id); - if let Some(the_match) = the_match { - let the_match = the_match.read().await; - if the_match.ledger.len() == ledger_size { - // forfeit the match - let _ = send(&client_tx, "GAME:WINS"); - let opponent = opponent_move.clone().unwrap(); - let opponent = opponent.read().await; - let _ = send(&opponent.connection, "GAME:LOSS"); - drop(opponent); - broadcast_message( - &observers, - &viewers, - &format!("GAME:WIN:{}", client_username), - ) - .await; - - let mut clients_guard = clients.write().await; - let mut client = clients_guard.get_mut(&client_addr).unwrap().write().await; - client.current_match = None; - client.color = Color::None; - drop(client); - - let opponent = opponent_move.unwrap(); - let mut opponent = opponent.write().await; - opponent.current_match = None; - opponent.color = Color::None; - drop(opponent); - - let mut tournament_guard = tournament.write().await; - let tourney = tournament_guard.as_mut().unwrap(); - tourney.write().await.inform_winner(client_addr, false); - drop(tournament_guard); - - matches.write().await.remove(&match_id).unwrap(); - } - } - })); - - Ok(()) - } - - pub async fn handle_player_list( - &self, - tx: UnboundedSender, - ) -> Result<(), anyhow::Error> { - let clients_guard = self.clients.read().await; - let mut to_send = "PLAYER:LIST:".to_string(); - for client_guard in clients_guard.values() { - let player = client_guard.read().await; - to_send += player.username.as_str(); - to_send += ","; - to_send += if player.ready { "true" } else { "false" }; - to_send += ","; - to_send += if player.current_match.is_some() { - "true" - } else { - "false" - }; - to_send += "|"; - } - - if !to_send.ends_with(":") { - to_send.remove(to_send.len() - 1); - } - - let _ = send(&tx, to_send.as_str()); - Ok(()) - } - - pub async fn handle_game_list( - &self, - tx: UnboundedSender, - ) -> Result<(), anyhow::Error> { - let matches_guard = self.matches.read().await; - let clients_guard = self.clients.read().await; - let mut to_send = "GAME:LIST:".to_string(); - for match_guard in matches_guard.values() { - let a_match = match_guard.read().await; - let player1 = if a_match.player1.to_string() == SERVER_PLAYER_ADDR { - SERVER_PLAYER_USERNAME.to_string() - } else { - clients_guard.get(&a_match.player1).unwrap().read().await.username.clone() - }; - let player2 = if a_match.player2.to_string() == SERVER_PLAYER_ADDR { - SERVER_PLAYER_USERNAME.to_string() - } else { - clients_guard.get(&a_match.player2).unwrap().read().await.username.clone() - }; - to_send += a_match.id.to_string().as_str(); - to_send += ","; - to_send += player1.as_str(); - to_send += ","; - to_send += player2.as_str(); - to_send += "|"; - } - - if !to_send.ends_with(":") { - to_send.remove(to_send.len() - 1); - } - - let _ = send(&tx, to_send.as_str()); - Ok(()) - } - - pub async fn handle_game_watch( - &self, - tx: UnboundedSender, - match_id: u32, - addr: SocketAddr, - ) -> Result<(), anyhow::Error> { - let result = self.watch(match_id, addr).await; - if result.is_err() { - return Err(anyhow::anyhow!("ERROR:INVALID:WATCH")); - } - - let clients_guard = self.clients.read().await; - let matches_guard = self.matches.read().await; - let the_match = matches_guard.get(&match_id).unwrap().read().await; - - let player1 = if !the_match.player1.to_string().eq(SERVER_PLAYER_ADDR) { - clients_guard.get(&the_match.player1).unwrap().read().await.username.clone() - } else { - SERVER_PLAYER_USERNAME.to_string() - }; - - let player2 = if !the_match.player2.to_string().eq(SERVER_PLAYER_ADDR) { - clients_guard.get(&the_match.player2).unwrap().read().await.username.clone() - } else { - SERVER_PLAYER_USERNAME.to_string() - }; - - let ledger = the_match.ledger.clone(); - - drop(clients_guard); - drop(the_match); - drop(matches_guard); - - let mut message = format!("GAME:WATCH:ACK:{},{},{}|", match_id, player1, player2); - - for a_move in ledger { - if a_move.0 == Color::Red { - message += &format!("{},{}|", player1, a_move.1); - } else { - message += &format!("{},{}|", player2, a_move.1); - } - } - - message.pop(); - - let _ = send(&tx, &message); - Ok(()) - } - - pub async fn handle_admin_auth( - &self, - tx: UnboundedSender, - addr: SocketAddr, - password: String, - ) -> Result<(), anyhow::Error> { - if self.admin.read().await.is_some() { - return Err(anyhow::anyhow!("ERROR:INVALID:AUTH")); - } - - if password != *self.admin_password { - return Err(anyhow::anyhow!("ERROR:INVALID:AUTH")); - } - - let mut admin_guard = self.admin.write().await; - *admin_guard = Some(addr.to_string().parse()?); - let _ = send(&tx, "ADMIN:AUTH:ACK"); - Ok(()) - } - - pub async fn handle_admin_kick( - &self, - addr: SocketAddr, - kick_username: String, - ) -> Result<(), anyhow::Error> { - if !self.auth_check(addr).await { - return Err(anyhow::anyhow!("ERROR:INVALID:AUTH")); - } - - let usernames_guard = self.usernames.read().await; - let clients_guard = self.clients.read().await; - - let kick_addr_result = usernames_guard.get(&kick_username); - match kick_addr_result { - Some(kick_addr) => { - let kick_client = clients_guard.get(kick_addr).unwrap().read().await; - kick_client.connection.send(Message::Close(None))?; - } - None => return Err(anyhow::anyhow!("ERROR:INVALID:KICK")), - } - Ok(()) - } - - pub async fn handle_game_terminate( - &self, - addr: SocketAddr, - match_id: u32, - ) -> Result<(), anyhow::Error> { - if !self.auth_check(addr).await { - return Err(anyhow::anyhow!("ERROR:INVALID:AUTH")); - } - - self.terminate_match(match_id).await; - - if self.tournament.read().await.is_some() && self.matches.read().await.is_empty() { - let mut tournament_guard = self.tournament.write().await; - let tourney = tournament_guard.as_mut().unwrap(); - tourney.write().await.next(&self).await; - if tourney.read().await.is_completed() { - *tournament_guard = None; - } - } - Ok(()) - } - - pub async fn handle_game_award_winner( - &self, - addr: SocketAddr, - match_id: u32, - winner_username: String, - ) -> Result<(), anyhow::Error> { - if !self.auth_check(addr).await { - return Err(anyhow!("ERROR:INVALID:AUTH")); - } - - let matches_guard = self.matches.read().await; - let found_match = - matches_guard.get(&match_id).ok_or_else(|| anyhow!("ERROR:INVALID:AWARD"))?.clone(); - drop(matches_guard); - - let the_match = found_match.read().await; - - // Validate that the declared winner is actually one of the players in this match - let clients_guard = self.clients.read().await; - let player1_client = clients_guard.get(&the_match.player1); - let player2_client = clients_guard.get(&the_match.player2); - - // If we cannot resolve both players, or the winner username doesn't match either, reject - if let (Some(p1_arc), Some(p2_arc)) = (player1_client, player2_client) { - let p1 = p1_arc.read().await; - let p2 = p2_arc.read().await; - - if winner_username != p1.username && winner_username != p2.username { - return Err(anyhow!("ERROR:INVALID:AWARD")); - } - } else { - return Err(anyhow!("ERROR:INVALID:AWARD")); - } - drop(clients_guard); - - self.matches.write().await.remove(&match_id); - - if let Some(wait_thread) = &the_match.wait_thread { - wait_thread.abort(); - } - - if let Some(timeout_thread) = &the_match.timeout_thread { - timeout_thread.abort(); - } - - self.broadcast_message(&the_match.viewers, &format!("GAME:WIN:{}", winner_username)).await; - - let clients_guard = self.clients.read().await; - if the_match.demo_mode { - let player_win = if winner_username != SERVER_PLAYER_USERNAME { - "WINS" - } else { - "LOSS" - }; - let mut the_player = if the_match.player1 != SERVER_PLAYER_ADDR.parse()? { - clients_guard.get(&the_match.player1).unwrap().write().await - } else { - clients_guard.get(&the_match.player2).unwrap().write().await - }; - - let _ = send(&the_player.connection, &format!("GAME:{}", player_win)); - let _ = send(&the_player.connection, "TOURNAMENT:END"); - - the_player.color = Color::None; - the_player.current_match = None; - - return Ok(()); - } - - let mut player1 = clients_guard.get(&the_match.player1).unwrap().write().await; - let mut player2 = clients_guard.get(&the_match.player2).unwrap().write().await; - - player1.current_match = None; - player1.color = Color::None; - - player2.current_match = None; - player2.color = Color::None; - - let winner_tx = if player1.username == winner_username { - player1.connection.clone() - } else { - player2.connection.clone() - }; - - let loser_tx = if player1.username != winner_username { - player1.connection.clone() - } else { - player2.connection.clone() - }; - - let winner_addr = if player1.username == winner_username { - player1.addr.clone() - } else { - player2.addr.clone() - }; - - drop(player1); - drop(player2); - drop(clients_guard); - - let _ = send(&winner_tx, "GAME:WINS"); - let _ = send(&loser_tx, "GAME:LOSS"); - - if self.tournament.read().await.is_some() { - let mut tournament_guard = self.tournament.write().await; - let tourney = tournament_guard.as_mut().unwrap(); - tourney.write().await.inform_winner(winner_addr, false); - if self.matches.read().await.is_empty() { - tourney.write().await.next(&self).await; - if tourney.read().await.is_completed() { - *tournament_guard = None; - } - } - } else { - let _ = send(&winner_tx, "TOURNAMENT:END"); - let _ = send(&loser_tx, "TOURNAMENT:END"); - } - - Ok(()) - } - - pub async fn handle_tournament_start( - &self, - addr: SocketAddr, - tournament_type: String, - ) -> Result<(), anyhow::Error> { - if !self.auth_check(addr).await { - return Err(anyhow::anyhow!("ERROR:INVALID:AUTH")); - } - - if self.tournament.read().await.is_some() { - return Err(anyhow::anyhow!("ERROR:INVALID:TOURNAMENT")); - } - - let mut clients_guard = self.clients.write().await; - let mut ready_players = Vec::new(); - for (client_addr, client_guard) in clients_guard.iter_mut() { - if client_guard.read().await.ready { - ready_players.push(*client_addr); - } - } - - if ready_players.len() < 3 { - return Err(anyhow::anyhow!("ERROR:INVALID:TOURNAMENT")); - } - - drop(clients_guard); - - let mut tourney = match tournament_type.as_str() { - "RoundRobin" => RoundRobin::new(&ready_players), - &_ => RoundRobin::new(&ready_players), - }; - tourney.start(&self).await; - - let mut tournament_guard = self.tournament.write().await; - *tournament_guard = Some(Arc::new(RwLock::new(tourney))); - - // Clear any pending reservations when a tournament starts - self.reservations.write().await.clear(); - - self.broadcast_message_all_observers(&format!("TOURNAMENT:START:{}", tournament_type)) - .await; - Ok(()) - } - - pub async fn handle_tournament_cancel(&self, addr: SocketAddr) -> Result<(), anyhow::Error> { - if !self.auth_check(addr).await { - return Err(anyhow::anyhow!("ERROR:INVALID:AUTH")); - } - - if self.tournament.read().await.is_none() { - return Err(anyhow::anyhow!("ERROR:INVALID:TOURNAMENT")); - } let mut tournament_guard = self.tournament.write().await; let tourney = tournament_guard.as_mut().unwrap(); - tourney.write().await.cancel(&self).await; - *tournament_guard = None; + tourney.write().await.inform_winner(opponent_addr, false); + drop(tournament_guard); - self.broadcast_message_all_observers("TOURNAMENT:CANCEL").await; - Ok(()) + self.matches.write().await.remove(¤t_match_id).unwrap(); + } + return Ok(()); } - pub async fn handle_get_data( - &self, - tx: UnboundedSender, - data_id: String, - ) -> Result<(), anyhow::Error> { - let mut msg = format!("GET:{}:", data_id); - match data_id.as_str() { - "TOURNAMENT_STATUS" => { - let tournament = self.tournament.read().await.clone(); - if tournament.is_some() { - msg += tournament.as_ref().unwrap().read().await.get_type().as_str(); - } else { - msg += "false"; - } - } - "MOVE_WAIT" => { - let wait_time = *self.waiting_timeout.read().await as f64 / 1000f64; - msg += wait_time.to_string().as_str(); - } - "DEMO_MODE" => { - let demo_mode = *self.demo_mode.read().await; - msg += demo_mode.to_string().as_str(); - } - "MAX_TIMEOUT" => { - let max_time = *self.max_timeout.read().await as f64 / 1000f64; - msg += max_time.to_string().as_str(); - } - &_ => return Err(anyhow::anyhow!("ERROR:INVALID:GET")), - } + current_match.place_token(client.color.clone(), column); - let _ = send(&tx, &msg); - Ok(()) + if let Some(timeout_thread) = ¤t_match.timeout_thread { + timeout_thread.abort(); } - pub async fn handle_set_data( - &self, - tx: UnboundedSender, - addr: SocketAddr, - data_id: String, - data_value: String, - ) -> Result<(), anyhow::Error> { - if !self.auth_check(addr).await { - return Err(anyhow::anyhow!("ERROR:INVALID:AUTH")); - } + let mut viewer_messages = Vec::new(); + let viewers = current_match.viewers.clone(); - match data_id.as_str() { - "DEMO_MODE" => { - let demo_mode = data_value.parse::(); - if demo_mode.is_err() { - return Err(anyhow::anyhow!("ERROR:INVALID:SET")); - } - *self.demo_mode.write().await = demo_mode.unwrap(); - } - "MOVE_WAIT" => { - let wait_time = data_value.parse::(); - if wait_time.is_err() { - return Err(anyhow::anyhow!("ERROR:INVALID:SET")); - } - *self.waiting_timeout.write().await = (wait_time.unwrap() * 1000.0) as u64; - } - "MAX_TIMEOUT" => { - let max_time = data_value.parse::(); - if max_time.is_err() { - return Err(anyhow::anyhow!("ERROR:INVALID:SET")); - } - *self.max_timeout.write().await = (max_time.unwrap() * 1000.0) as u64; - } - &_ => return Err(anyhow::anyhow!("ERROR:INVALID:SET")), - } + viewer_messages.push(format!("GAME:MOVE:{}:{}", client.username, column)); - let _ = send(&tx, &format!("SET:{}:ACK", data_id)); - Ok(()) + // Check game end conditions + let (winner, filled) = current_match.end_game_check(); + + // Send match results + if winner == client.color { + let _ = send(&tx, "GAME:WINS"); + if !current_match.demo_mode { + let opponent = opponent.clone().unwrap(); + let opponent = opponent.read().await; + let _ = send(&opponent.connection, "GAME:LOSS"); + } + viewer_messages.push(format!("GAME:WIN:{}", client.username)); + } else if filled { + let _ = send(&tx, "GAME:DRAW"); + if !current_match.demo_mode { + let opponent = opponent.clone().unwrap(); + let opponent = opponent.read().await; + let _ = send(&opponent.connection, "GAME:DRAW"); + } + viewer_messages.push("GAME:DRAW".to_string()); } - pub async fn handle_reservation_add( - &self, - tx: UnboundedSender, - addr: SocketAddr, - player1_username: String, - player2_username: String, - ) -> Result<(), anyhow::Error> { - if !self.auth_check(addr).await { - return Err(anyhow::anyhow!("ERROR:INVALID:AUTH")); - } + // remove match from matchmaker + if winner != Color::None || filled { + let current_match_id = current_match.id; + let is_demo_mode = current_match.demo_mode; - self.reservations.write().await.push((player1_username.clone(), player2_username.clone())); + drop(client); + drop(current_match); + drop(clients_guard); - let _ = send( - &tx, - &format!("RESERVATION:ADD:{},{}", player1_username, player2_username), - ); + let clients_guard = self.clients.read().await; + let mut client = clients_guard.get(&addr).unwrap().write().await; + client.current_match = None; + client.color = Color::None; + drop(client); - let player1_addr = self.usernames.read().await.get(&player1_username).cloned(); - let player2_addr = self.usernames.read().await.get(&player2_username).cloned(); + if !is_demo_mode { + let opponent = opponent.clone().unwrap(); + let mut opponent = opponent.write().await; + opponent.current_match = None; + opponent.color = Color::None; + drop(opponent); + } - let clients_guard = self.clients.read().await; - if player1_addr.is_some() && player2_addr.is_some() { - let mut player1 = clients_guard.get(&player1_addr.unwrap()).unwrap().write().await; - let mut player2 = clients_guard.get(&player2_addr.unwrap()).unwrap().write().await; + matches_guard.remove(¤t_match_id).unwrap(); - if player1.ready && player2.ready { - let match_id: u32 = gen_match_id(&self.matches).await; - let new_match = Arc::new(RwLock::new(Match::new( - match_id, - player1_addr.unwrap(), - player2_addr.unwrap(), - false, - ))); - self.matches.write().await.insert(match_id, new_match.clone()); - - player1.ready = false; - player1.current_match = Some(match_id); - player1.color = if new_match.read().await.player1 == player1_addr.unwrap() { - let _ = send(&tx, "GAME:START:1"); - let _ = send(&player2.connection, "GAME:START:0"); - Color::Red - } else { - let _ = send(&tx, "GAME:START:0"); - let _ = send(&player2.connection, "GAME:START:1"); - Color::Yellow - }; - - player2.ready = false; - player2.current_match = Some(match_id); - player2.color = !player1.color; - - self.reservations - .write() - .await - .retain(|(p1, p2)| !(p1 == &player1_username && p2 == &player2_username)); - } - } - - Ok(()) - } - - pub async fn handle_reservation_delete( - &self, - tx: UnboundedSender, - addr: SocketAddr, - player1_username: String, - player2_username: String, - ) -> Result<(), anyhow::Error> { - if !self.auth_check(addr).await { - return Err(anyhow::anyhow!("ERROR:INVALID:AUTH")); - } - - self.reservations - .write() - .await - .retain(|(p1, p2)| !(p1 == &player1_username && p2 == &player2_username)); - - let _ = send( - &tx, - &format!( - "RESERVATION:DELETE:{},{}", - player1_username, player2_username - ), - ); - - Ok(()) - } - - pub async fn handle_reservation_get( - &self, - tx: UnboundedSender, - addr: SocketAddr, - ) -> Result<(), anyhow::Error> { - if !self.auth_check(addr).await { - return Err(anyhow::anyhow!("ERROR:INVALID:AUTH")); - } - - let reservations_guard = self.reservations.read().await; - let mut msg = "RESERVATION:LIST:".to_string(); - for (p1, p2) in reservations_guard.iter() { - msg += &format!("{},{}|", p1, p2); - } - if msg.ends_with("|") { - msg.pop(); - } - - let _ = send(&tx, &msg); - Ok(()) - } - - pub async fn watch(&self, new_match_id: u32, addr: SocketAddr) -> Result<(), String> { - let matches_guard = self.matches.read().await; - - for match_guard in matches_guard.values() { - let mut found = false; - let mut a_match = match_guard.write().await; - for i in 0..a_match.viewers.len() { - if a_match.viewers[i] == addr { - a_match.viewers.remove(i); - found = true; - break; - } - } - - if found { - break; - } - } - - let result = matches_guard.get(&new_match_id); - if result.is_none() { - return Err("Match not found".to_string()); - } - result.unwrap().write().await.viewers.push(addr); - - Ok(()) - } - - pub async fn remove_observer_from_all_matches(&self, addr: SocketAddr) { - let matches_guard = self.matches.read().await; - - for match_guard in matches_guard.values() { - let mut found = false; - let mut a_match = match_guard.write().await; - for i in 0..a_match.viewers.len() { - if a_match.viewers[i] == addr { - a_match.viewers.remove(i); - found = true; - break; - } - } - - if found { - break; - } - } - } - - pub async fn terminate_match(&self, match_id: u32) { - let matches_guard = self.matches.read().await; - let the_match = matches_guard.get(&match_id); - if the_match.is_none() { - error!( - "Tried to call terminate_match on invalid matchID: {}", - match_id - ); - } - let the_match = the_match.unwrap().read().await; - - if let Some(wait_thread) = &the_match.wait_thread { - wait_thread.abort(); - } - - if let Some(timeout_thread) = &the_match.timeout_thread { - timeout_thread.abort(); - } - - self.broadcast_message(&the_match.viewers, "GAME:TERMINATED").await; - - let clients_guard = self.clients.read().await; - if the_match.player1 != SERVER_PLAYER_ADDR.to_string().parse().unwrap() { - let mut player1 = clients_guard.get(&the_match.player1).unwrap().write().await; - let _ = send(&player1.connection, "GAME:TERMINATED"); - player1.current_match = None; - player1.color = Color::None; - } - - if the_match.player2 != SERVER_PLAYER_ADDR.to_string().parse().unwrap() { - let mut player2 = clients_guard.get(&the_match.player2).unwrap().write().await; - let _ = send(&player2.connection, "GAME:TERMINATED"); - player2.current_match = None; - player2.color = Color::None; - } + if self.tournament.read().await.is_some() && matches_guard.is_empty() { + drop(matches_guard); drop(clients_guard); - drop(the_match); - drop(matches_guard); - - self.matches.write().await.remove(&match_id); - } - - pub async fn broadcast_message(&self, addrs: &Vec, msg: &str) { - for addr in addrs { - let observers_guard = self.observers.read().await; - let tx = observers_guard.get(addr); - if tx.is_none() { - continue; - } - let _ = send(tx.unwrap(), msg); + let mut tournament_guard = self.tournament.write().await; + let tourney = tournament_guard.as_mut().unwrap(); + tourney.write().await.inform_winner(addr, filled); + tourney.write().await.next(&self).await; + if tourney.read().await.is_completed() { + *tournament_guard = None; } - } - - pub async fn broadcast_message_all_observers(&self, msg: &str) { - let observers_guard = self.observers.read().await; - for (_, tx) in observers_guard.iter() { - let _ = send(tx, msg); + } else if self.tournament.read().await.is_none() { + let _ = send(&tx, "TOURNAMENT:END"); + if !is_demo_mode { + let opponent = opponent.clone().unwrap(); + let opponent = opponent.read().await; + let _ = send(&opponent.connection, "TOURNAMENT:END"); } + } + + return Ok(()); } - pub async fn auth_check(&self, addr: SocketAddr) -> bool { - if self.admin.read().await.is_none() || self.admin.read().await.unwrap() != addr { - return false; + let default_waiting_time = *self.waiting_timeout.read().await; + let mut adjusted_waiting = + default_waiting_time as i64 + (rand::rng().random_range(0..=50) - 25); + let current_move_time = Instant::now(); + + if current_match.ledger.is_empty() { + adjusted_waiting = 0; + } else { + let last_move_time = current_match.ledger.last().unwrap().2; + let elapsed = current_move_time.duration_since(last_move_time).as_millis() as i64; + adjusted_waiting -= elapsed; + if adjusted_waiting < 0 { + adjusted_waiting = 0; + } + } + + current_match.ledger.push((client.color.clone(), column, current_move_time)); + + let demo_mode = current_match.demo_mode; + let demo_move = random_move(¤t_match.board); + let no_winner = winner == Color::None && !filled; + let observers = self.observers.clone(); + let opponent_move = opponent.clone(); + let client_tx = tx.clone(); + if current_match.demo_mode { + current_match.ledger.push((!client.color, demo_move, Instant::now())); + current_match.place_token(!client.color, demo_move); + } + + current_match.wait_thread = Some(tokio::spawn(async move { + tokio::time::sleep(tokio::time::Duration::from_millis(adjusted_waiting as u64)).await; + + if !demo_mode && no_winner { + let opponent = opponent_move.unwrap(); + let opponent = opponent.read().await; + let _ = send( + &opponent.connection.clone(), + &format!("OPPONENT:{}", column), + ); + } + + for msg in viewer_messages { + broadcast_message(&observers, &viewers, &msg).await; + } + + if demo_mode && no_winner { + tokio::time::sleep(tokio::time::Duration::from_millis(default_waiting_time)).await; + let _ = send(&client_tx, &format!("OPPONENT:{}", demo_move)); + broadcast_message( + &observers, + &viewers, + &format!("GAME:MOVE:{}:{}", SERVER_PLAYER_USERNAME, demo_move), + ) + .await; + } + })); + + let max_timeout = *self.max_timeout.read().await; + let matches = self.matches.clone(); + let tournament = self.tournament.clone(); + let clients = self.clients.clone(); + let match_id = current_match.id; + let ledger_size = current_match.ledger.len(); + let client_username = client.username.clone(); + let client_tx = tx.clone(); + let client_addr = addr.clone(); + let observers = self.observers.clone(); + let viewers = current_match.viewers.clone(); + let opponent_move = opponent.clone(); + current_match.timeout_thread = Some(tokio::spawn(async move { + if demo_mode { + return; + } + tokio::time::sleep(tokio::time::Duration::from_millis(adjusted_waiting as u64)).await; + tokio::time::sleep(tokio::time::Duration::from_millis(max_timeout as u64)).await; + + let matches_guard = matches.read().await; + let the_match = matches_guard.get(&match_id); + if let Some(the_match) = the_match { + let the_match = the_match.read().await; + if the_match.ledger.len() == ledger_size { + // forfeit the match + let _ = send(&client_tx, "GAME:WINS"); + let opponent = opponent_move.clone().unwrap(); + let opponent = opponent.read().await; + let _ = send(&opponent.connection, "GAME:LOSS"); + drop(opponent); + broadcast_message( + &observers, + &viewers, + &format!("GAME:WIN:{}", client_username), + ) + .await; + + let mut clients_guard = clients.write().await; + let mut client = clients_guard.get_mut(&client_addr).unwrap().write().await; + client.current_match = None; + client.color = Color::None; + drop(client); + + let opponent = opponent_move.unwrap(); + let mut opponent = opponent.write().await; + opponent.current_match = None; + opponent.color = Color::None; + drop(opponent); + + let mut tournament_guard = tournament.write().await; + let tourney = tournament_guard.as_mut().unwrap(); + tourney.write().await.inform_winner(client_addr, false); + drop(tournament_guard); + + matches.write().await.remove(&match_id).unwrap(); } - true + } + })); + + Ok(()) + } + + pub async fn handle_player_list( + &self, + tx: UnboundedSender, + ) -> Result<(), anyhow::Error> { + let clients_guard = self.clients.read().await; + let mut to_send = "PLAYER:LIST:".to_string(); + for client_guard in clients_guard.values() { + let player = client_guard.read().await; + to_send += player.username.as_str(); + to_send += ","; + to_send += if player.ready { "true" } else { "false" }; + to_send += ","; + to_send += if player.current_match.is_some() { + "true" + } else { + "false" + }; + to_send += "|"; } - pub async fn find_reservation_opponent(&self, username: String) -> Option { - let reservations_guard = self.reservations.read().await; - for (player1, player2) in reservations_guard.iter() { - if player1 == &username || player2 == &username { - let opponent_username = if player1 == &username { - player2 - } else { - player1 - }; + if !to_send.ends_with(":") { + to_send.remove(to_send.len() - 1); + } - let usernames_guard = self.usernames.read().await; - if let Some(opponent_addr) = usernames_guard.get(opponent_username) { - let clients_guard = self.clients.read().await; - let opponent = clients_guard.get(opponent_addr).unwrap().read().await; - if opponent.ready { - return Some(*opponent_addr); - } - } - } + let _ = send(&tx, to_send.as_str()); + Ok(()) + } + + pub async fn handle_game_list(&self, tx: UnboundedSender) -> Result<(), anyhow::Error> { + let clients_guard = self.clients.read().await; + let matches_guard = self.matches.read().await; + let mut to_send = "GAME:LIST:".to_string(); + for match_guard in matches_guard.values() { + let a_match = match_guard.read().await; + let player1 = if a_match.player1.to_string() == SERVER_PLAYER_ADDR { + SERVER_PLAYER_USERNAME.to_string() + } else { + clients_guard.get(&a_match.player1).unwrap().read().await.username.clone() + }; + let player2 = if a_match.player2.to_string() == SERVER_PLAYER_ADDR { + SERVER_PLAYER_USERNAME.to_string() + } else { + clients_guard.get(&a_match.player2).unwrap().read().await.username.clone() + }; + to_send += a_match.id.to_string().as_str(); + to_send += ","; + to_send += player1.as_str(); + to_send += ","; + to_send += player2.as_str(); + to_send += "|"; + } + + if !to_send.ends_with(":") { + to_send.remove(to_send.len() - 1); + } + + let _ = send(&tx, to_send.as_str()); + Ok(()) + } + + pub async fn handle_game_watch( + &self, + tx: UnboundedSender, + match_id: u32, + addr: SocketAddr, + ) -> Result<(), anyhow::Error> { + let result = self.watch(match_id, addr).await; + if result.is_err() { + return Err(anyhow::anyhow!("ERROR:INVALID:WATCH")); + } + + let clients_guard = self.clients.read().await; + let matches_guard = self.matches.read().await; + let the_match = matches_guard.get(&match_id).unwrap().read().await; + + let player1 = if !the_match.player1.to_string().eq(SERVER_PLAYER_ADDR) { + clients_guard.get(&the_match.player1).unwrap().read().await.username.clone() + } else { + SERVER_PLAYER_USERNAME.to_string() + }; + + let player2 = if !the_match.player2.to_string().eq(SERVER_PLAYER_ADDR) { + clients_guard.get(&the_match.player2).unwrap().read().await.username.clone() + } else { + SERVER_PLAYER_USERNAME.to_string() + }; + + let ledger = the_match.ledger.clone(); + + drop(clients_guard); + drop(the_match); + drop(matches_guard); + + let mut message = format!("GAME:WATCH:ACK:{},{},{}|", match_id, player1, player2); + + for a_move in ledger { + if a_move.0 == Color::Red { + message += &format!("{},{}|", player1, a_move.1); + } else { + message += &format!("{},{}|", player2, a_move.1); + } + } + + message.pop(); + + let _ = send(&tx, &message); + Ok(()) + } + + pub async fn handle_admin_auth( + &self, + tx: UnboundedSender, + addr: SocketAddr, + password: String, + ) -> Result<(), anyhow::Error> { + if self.admin.read().await.is_some() { + return Err(anyhow::anyhow!("ERROR:INVALID:AUTH")); + } + + if password != *self.admin_password { + return Err(anyhow::anyhow!("ERROR:INVALID:AUTH")); + } + + let mut admin_guard = self.admin.write().await; + *admin_guard = Some(addr.to_string().parse()?); + let _ = send(&tx, "ADMIN:AUTH:ACK"); + Ok(()) + } + + pub async fn handle_admin_kick( + &self, + addr: SocketAddr, + kick_username: String, + ) -> Result<(), anyhow::Error> { + if !self.auth_check(addr).await { + return Err(anyhow::anyhow!("ERROR:INVALID:AUTH")); + } + + let usernames_guard = self.usernames.read().await; + let clients_guard = self.clients.read().await; + + let kick_addr_result = usernames_guard.get(&kick_username); + match kick_addr_result { + Some(kick_addr) => { + let kick_client = clients_guard.get(kick_addr).unwrap().read().await; + kick_client.connection.send(Message::Close(None))?; + } + None => return Err(anyhow::anyhow!("ERROR:INVALID:KICK")), + } + Ok(()) + } + + pub async fn handle_game_terminate( + &self, + addr: SocketAddr, + match_id: u32, + ) -> Result<(), anyhow::Error> { + if !self.auth_check(addr).await { + return Err(anyhow::anyhow!("ERROR:INVALID:AUTH")); + } + + self.terminate_match(match_id).await; + + if self.tournament.read().await.is_some() && self.matches.read().await.is_empty() { + let mut tournament_guard = self.tournament.write().await; + let tourney = tournament_guard.as_mut().unwrap(); + tourney.write().await.next(&self).await; + if tourney.read().await.is_completed() { + *tournament_guard = None; + } + } + Ok(()) + } + + pub async fn handle_game_award_winner( + &self, + addr: SocketAddr, + match_id: u32, + winner_username: String, + ) -> Result<(), anyhow::Error> { + if !self.auth_check(addr).await { + return Err(anyhow!("ERROR:INVALID:AUTH")); + } + + let matches_guard = self.matches.read().await; + let found_match = + matches_guard.get(&match_id).ok_or_else(|| anyhow!("ERROR:INVALID:AWARD"))?.clone(); + drop(matches_guard); + + let the_match = found_match.read().await; + + // Validate that the declared winner is actually one of the players in this match + let clients_guard = self.clients.read().await; + let player1_client = clients_guard.get(&the_match.player1); + let player2_client = clients_guard.get(&the_match.player2); + + // If we cannot resolve both players, or the winner username doesn't match either, reject + if let (Some(p1_arc), Some(p2_arc)) = (player1_client, player2_client) { + let p1 = p1_arc.read().await; + let p2 = p2_arc.read().await; + + if winner_username != p1.username && winner_username != p2.username { + return Err(anyhow!("ERROR:INVALID:AWARD")); + } + } else { + return Err(anyhow!("ERROR:INVALID:AWARD")); + } + drop(clients_guard); + + self.matches.write().await.remove(&match_id); + + if let Some(wait_thread) = &the_match.wait_thread { + wait_thread.abort(); + } + + if let Some(timeout_thread) = &the_match.timeout_thread { + timeout_thread.abort(); + } + + self.broadcast_message(&the_match.viewers, &format!("GAME:WIN:{}", winner_username)).await; + + let clients_guard = self.clients.read().await; + if the_match.demo_mode { + let player_win = if winner_username != SERVER_PLAYER_USERNAME { + "WINS" + } else { + "LOSS" + }; + let mut the_player = if the_match.player1 != SERVER_PLAYER_ADDR.parse()? { + clients_guard.get(&the_match.player1).unwrap().write().await + } else { + clients_guard.get(&the_match.player2).unwrap().write().await + }; + + let _ = send(&the_player.connection, &format!("GAME:{}", player_win)); + let _ = send(&the_player.connection, "TOURNAMENT:END"); + + the_player.color = Color::None; + the_player.current_match = None; + + return Ok(()); + } + + let mut player1 = clients_guard.get(&the_match.player1).unwrap().write().await; + let mut player2 = clients_guard.get(&the_match.player2).unwrap().write().await; + + player1.current_match = None; + player1.color = Color::None; + + player2.current_match = None; + player2.color = Color::None; + + let winner_tx = if player1.username == winner_username { + player1.connection.clone() + } else { + player2.connection.clone() + }; + + let loser_tx = if player1.username != winner_username { + player1.connection.clone() + } else { + player2.connection.clone() + }; + + let winner_addr = if player1.username == winner_username { + player1.addr.clone() + } else { + player2.addr.clone() + }; + + drop(player1); + drop(player2); + drop(clients_guard); + + let _ = send(&winner_tx, "GAME:WINS"); + let _ = send(&loser_tx, "GAME:LOSS"); + + if self.tournament.read().await.is_some() { + let mut tournament_guard = self.tournament.write().await; + let tourney = tournament_guard.as_mut().unwrap(); + tourney.write().await.inform_winner(winner_addr, false); + if self.matches.read().await.is_empty() { + tourney.write().await.next(&self).await; + if tourney.read().await.is_completed() { + *tournament_guard = None; } - - None + } + } else { + let _ = send(&winner_tx, "TOURNAMENT:END"); + let _ = send(&loser_tx, "TOURNAMENT:END"); } + + Ok(()) + } + + pub async fn handle_tournament_start( + &self, + addr: SocketAddr, + tournament_type: String, + ) -> Result<(), anyhow::Error> { + if !self.auth_check(addr).await { + return Err(anyhow::anyhow!("ERROR:INVALID:AUTH")); + } + + if self.tournament.read().await.is_some() { + return Err(anyhow::anyhow!("ERROR:INVALID:TOURNAMENT")); + } + + let mut clients_guard = self.clients.write().await; + let mut ready_players = Vec::new(); + for (client_addr, client_guard) in clients_guard.iter_mut() { + if client_guard.read().await.ready { + ready_players.push(*client_addr); + } + } + + if ready_players.len() < 3 { + return Err(anyhow::anyhow!("ERROR:INVALID:TOURNAMENT")); + } + + drop(clients_guard); + + let mut tourney = match tournament_type.as_str() { + "RoundRobin" => RoundRobin::new(&ready_players), + &_ => RoundRobin::new(&ready_players), + }; + tourney.start(&self).await; + + let mut tournament_guard = self.tournament.write().await; + *tournament_guard = Some(Arc::new(RwLock::new(tourney))); + + // Clear any pending reservations when a tournament starts + self.reservations.write().await.clear(); + + self.broadcast_message_all_observers(&format!("TOURNAMENT:START:{}", tournament_type)).await; + Ok(()) + } + + pub async fn handle_tournament_cancel(&self, addr: SocketAddr) -> Result<(), anyhow::Error> { + if !self.auth_check(addr).await { + return Err(anyhow::anyhow!("ERROR:INVALID:AUTH")); + } + + if self.tournament.read().await.is_none() { + return Err(anyhow::anyhow!("ERROR:INVALID:TOURNAMENT")); + } + + let mut tournament_guard = self.tournament.write().await; + let tourney = tournament_guard.as_mut().unwrap(); + tourney.write().await.cancel(&self).await; + *tournament_guard = None; + + self.broadcast_message_all_observers("TOURNAMENT:CANCEL").await; + Ok(()) + } + + pub async fn handle_get_data( + &self, + tx: UnboundedSender, + data_id: String, + ) -> Result<(), anyhow::Error> { + let mut msg = format!("GET:{}:", data_id); + match data_id.as_str() { + "TOURNAMENT_STATUS" => { + let tournament = self.tournament.read().await.clone(); + if tournament.is_some() { + msg += tournament.as_ref().unwrap().read().await.get_type().as_str(); + } else { + msg += "false"; + } + } + "MOVE_WAIT" => { + let wait_time = *self.waiting_timeout.read().await as f64 / 1000f64; + msg += wait_time.to_string().as_str(); + } + "DEMO_MODE" => { + let demo_mode = *self.demo_mode.read().await; + msg += demo_mode.to_string().as_str(); + } + "MAX_TIMEOUT" => { + let max_time = *self.max_timeout.read().await as f64 / 1000f64; + msg += max_time.to_string().as_str(); + } + &_ => return Err(anyhow::anyhow!("ERROR:INVALID:GET")), + } + + let _ = send(&tx, &msg); + Ok(()) + } + + pub async fn handle_set_data( + &self, + tx: UnboundedSender, + addr: SocketAddr, + data_id: String, + data_value: String, + ) -> Result<(), anyhow::Error> { + if !self.auth_check(addr).await { + return Err(anyhow::anyhow!("ERROR:INVALID:AUTH")); + } + + match data_id.as_str() { + "DEMO_MODE" => { + let demo_mode = data_value.parse::(); + if demo_mode.is_err() { + return Err(anyhow::anyhow!("ERROR:INVALID:SET")); + } + *self.demo_mode.write().await = demo_mode.unwrap(); + } + "MOVE_WAIT" => { + let wait_time = data_value.parse::(); + if wait_time.is_err() { + return Err(anyhow::anyhow!("ERROR:INVALID:SET")); + } + *self.waiting_timeout.write().await = (wait_time.unwrap() * 1000.0) as u64; + } + "MAX_TIMEOUT" => { + let max_time = data_value.parse::(); + if max_time.is_err() { + return Err(anyhow::anyhow!("ERROR:INVALID:SET")); + } + *self.max_timeout.write().await = (max_time.unwrap() * 1000.0) as u64; + } + &_ => return Err(anyhow::anyhow!("ERROR:INVALID:SET")), + } + + let _ = send(&tx, &format!("SET:{}:ACK", data_id)); + Ok(()) + } + + pub async fn handle_reservation_add( + &self, + tx: UnboundedSender, + addr: SocketAddr, + player1_username: String, + player2_username: String, + ) -> Result<(), anyhow::Error> { + if !self.auth_check(addr).await { + return Err(anyhow::anyhow!("ERROR:INVALID:AUTH")); + } + + self.reservations.write().await.push((player1_username.clone(), player2_username.clone())); + + let _ = send( + &tx, + &format!("RESERVATION:ADD:{},{}", player1_username, player2_username), + ); + + let player1_addr = self.usernames.read().await.get(&player1_username).cloned(); + let player2_addr = self.usernames.read().await.get(&player2_username).cloned(); + + let clients_guard = self.clients.read().await; + if player1_addr.is_some() && player2_addr.is_some() { + let mut player1 = clients_guard.get(&player1_addr.unwrap()).unwrap().write().await; + let mut player2 = clients_guard.get(&player2_addr.unwrap()).unwrap().write().await; + + if player1.ready && player2.ready { + let match_id: u32 = gen_match_id(&self.matches).await; + let new_match = Arc::new(RwLock::new(Match::new( + match_id, + player1_addr.unwrap(), + player2_addr.unwrap(), + false, + ))); + self.matches.write().await.insert(match_id, new_match.clone()); + + player1.ready = false; + player1.current_match = Some(match_id); + player1.color = if new_match.read().await.player1 == player1_addr.unwrap() { + let _ = send(&tx, "GAME:START:1"); + let _ = send(&player2.connection, "GAME:START:0"); + Color::Red + } else { + let _ = send(&tx, "GAME:START:0"); + let _ = send(&player2.connection, "GAME:START:1"); + Color::Yellow + }; + + player2.ready = false; + player2.current_match = Some(match_id); + player2.color = !player1.color; + + self + .reservations + .write() + .await + .retain(|(p1, p2)| !(p1 == &player1_username && p2 == &player2_username)); + } + } + + Ok(()) + } + + pub async fn handle_reservation_delete( + &self, + tx: UnboundedSender, + addr: SocketAddr, + player1_username: String, + player2_username: String, + ) -> Result<(), anyhow::Error> { + if !self.auth_check(addr).await { + return Err(anyhow::anyhow!("ERROR:INVALID:AUTH")); + } + + self + .reservations + .write() + .await + .retain(|(p1, p2)| !(p1 == &player1_username && p2 == &player2_username)); + + let _ = send( + &tx, + &format!( + "RESERVATION:DELETE:{},{}", + player1_username, player2_username + ), + ); + + Ok(()) + } + + pub async fn handle_reservation_get( + &self, + tx: UnboundedSender, + addr: SocketAddr, + ) -> Result<(), anyhow::Error> { + if !self.auth_check(addr).await { + return Err(anyhow::anyhow!("ERROR:INVALID:AUTH")); + } + + let reservations_guard = self.reservations.read().await; + let mut msg = "RESERVATION:LIST:".to_string(); + for (p1, p2) in reservations_guard.iter() { + msg += &format!("{},{}|", p1, p2); + } + if msg.ends_with("|") { + msg.pop(); + } + + let _ = send(&tx, &msg); + Ok(()) + } + + pub async fn watch(&self, new_match_id: u32, addr: SocketAddr) -> Result<(), String> { + let matches_guard = self.matches.read().await; + + for match_guard in matches_guard.values() { + let mut found = false; + let mut a_match = match_guard.write().await; + for i in 0..a_match.viewers.len() { + if a_match.viewers[i] == addr { + a_match.viewers.remove(i); + found = true; + break; + } + } + + if found { + break; + } + } + + let result = matches_guard.get(&new_match_id); + if result.is_none() { + return Err("Match not found".to_string()); + } + result.unwrap().write().await.viewers.push(addr); + + Ok(()) + } + + pub async fn remove_observer_from_all_matches(&self, addr: SocketAddr) { + let matches_guard = self.matches.read().await; + + for match_guard in matches_guard.values() { + let mut found = false; + let mut a_match = match_guard.write().await; + for i in 0..a_match.viewers.len() { + if a_match.viewers[i] == addr { + a_match.viewers.remove(i); + found = true; + break; + } + } + + if found { + break; + } + } + } + + pub async fn terminate_match(&self, match_id: u32) { + let matches_guard = self.matches.read().await; + let the_match = matches_guard.get(&match_id); + if the_match.is_none() { + error!( + "Tried to call terminate_match on invalid matchID: {}", + match_id + ); + } + let the_match = the_match.unwrap().read().await; + + if let Some(wait_thread) = &the_match.wait_thread { + wait_thread.abort(); + } + + if let Some(timeout_thread) = &the_match.timeout_thread { + timeout_thread.abort(); + } + + self.broadcast_message(&the_match.viewers, "GAME:TERMINATED").await; + + let clients_guard = self.clients.read().await; + if the_match.player1 != SERVER_PLAYER_ADDR.to_string().parse().unwrap() { + let mut player1 = clients_guard.get(&the_match.player1).unwrap().write().await; + let _ = send(&player1.connection, "GAME:TERMINATED"); + player1.current_match = None; + player1.color = Color::None; + } + + if the_match.player2 != SERVER_PLAYER_ADDR.to_string().parse().unwrap() { + let mut player2 = clients_guard.get(&the_match.player2).unwrap().write().await; + let _ = send(&player2.connection, "GAME:TERMINATED"); + player2.current_match = None; + player2.color = Color::None; + } + drop(clients_guard); + + drop(the_match); + drop(matches_guard); + + self.matches.write().await.remove(&match_id); + } + + pub async fn broadcast_message(&self, addrs: &Vec, msg: &str) { + for addr in addrs { + let observers_guard = self.observers.read().await; + let tx = observers_guard.get(addr); + if tx.is_none() { + continue; + } + let _ = send(tx.unwrap(), msg); + } + } + + pub async fn broadcast_message_all_observers(&self, msg: &str) { + let observers_guard = self.observers.read().await; + for (_, tx) in observers_guard.iter() { + let _ = send(tx, msg); + } + } + + pub async fn auth_check(&self, addr: SocketAddr) -> bool { + if self.admin.read().await.is_none() || self.admin.read().await.unwrap() != addr { + return false; + } + true + } + + pub async fn find_reservation_opponent(&self, username: String) -> Option { + let reservations_guard = self.reservations.read().await; + for (player1, player2) in reservations_guard.iter() { + if player1 == &username || player2 == &username { + let opponent_username = if player1 == &username { + player2 + } else { + player1 + }; + + let usernames_guard = self.usernames.read().await; + if let Some(opponent_addr) = usernames_guard.get(opponent_username) { + let clients_guard = self.clients.read().await; + let opponent = clients_guard.get(opponent_addr).unwrap().read().await; + if opponent.ready { + return Some(*opponent_addr); + } + } + } + } + + None + } } diff --git a/src/tournaments/mod.rs b/src/tournaments/mod.rs index 62d95f4..2e0466e 100644 --- a/src/tournaments/mod.rs +++ b/src/tournaments/mod.rs @@ -9,15 +9,15 @@ pub use round_robin::RoundRobin; #[async_trait] pub trait Tournament { - fn new(ready_players: &[SocketAddr]) -> Self - where - Self: Sized; - async fn next(&mut self, server: &Server); - async fn start(&mut self, server: &Server); - async fn cancel(&mut self, server: &Server); - fn inform_winner(&mut self, winner: SocketAddr, is_tie: bool); - fn inform_reconnect(&mut self, old_addr: SocketAddr, new_addr: SocketAddr); - fn contains_player(&self, addr: SocketAddr) -> bool; - fn is_completed(&self) -> bool; - fn get_type(&self) -> String; + fn new(ready_players: &[SocketAddr]) -> Self + where + Self: Sized; + async fn next(&mut self, server: &Server); + async fn start(&mut self, server: &Server); + async fn cancel(&mut self, server: &Server); + fn inform_winner(&mut self, winner: SocketAddr, is_tie: bool); + fn inform_reconnect(&mut self, old_addr: SocketAddr, new_addr: SocketAddr); + fn contains_player(&self, addr: SocketAddr) -> bool; + fn is_completed(&self) -> bool; + fn get_type(&self) -> String; } diff --git a/src/tournaments/round_robin.rs b/src/tournaments/round_robin.rs index 18c814f..67cac08 100644 --- a/src/tournaments/round_robin.rs +++ b/src/tournaments/round_robin.rs @@ -9,217 +9,217 @@ type ID = u32; #[derive(Clone)] pub struct RoundRobin { - pub players: HashMap, - pub top_half: Vec, - pub bottom_half: Vec, - pub is_completed: bool, + pub players: HashMap, + pub top_half: Vec, + pub bottom_half: Vec, + pub is_completed: bool, } impl RoundRobin { - async fn create_matches(&self, clients: &Clients, matches: &Matches) { - let clients_guard = clients.read().await; - for (i, id) in self.top_half.iter().enumerate() { - let player1_addr = self.players.get(id).unwrap(); - let player2_addr = self.players.get(self.bottom_half.get(i).unwrap()); + async fn create_matches(&self, clients: &Clients, matches: &Matches) { + let clients_guard = clients.read().await; + for (i, id) in self.top_half.iter().enumerate() { + let player1_addr = self.players.get(id).unwrap(); + let player2_addr = self.players.get(self.bottom_half.get(i).unwrap()); - if player2_addr.is_none() { - continue; - } - let player2_addr = player2_addr.unwrap(); + if player2_addr.is_none() { + continue; + } + let player2_addr = player2_addr.unwrap(); - let match_id: u32 = gen_match_id(matches).await; - let new_match = Arc::new(RwLock::new(Match::new( - match_id, - player1_addr.0, - player2_addr.0, - false, - ))); + let match_id: u32 = gen_match_id(matches).await; + let new_match = Arc::new(RwLock::new(Match::new( + match_id, + player1_addr.0, + player2_addr.0, + false, + ))); - let match_guard = new_match.read().await; - let mut player1 = clients_guard.get(&player1_addr.0).unwrap().write().await; + let match_guard = new_match.read().await; + let mut player1 = clients_guard.get(&player1_addr.0).unwrap().write().await; - player1.current_match = Some(match_id); - player1.ready = false; + player1.current_match = Some(match_id); + player1.ready = false; - if match_guard.player1 == player1_addr.0 { - player1.color = Color::Red; - let _ = send(&player1.connection, "GAME:START:1"); - } else { - player1.color = Color::Yellow; - let _ = send(&player1.connection, "GAME:START:0"); - } + if match_guard.player1 == player1_addr.0 { + player1.color = Color::Red; + let _ = send(&player1.connection, "GAME:START:1"); + } else { + player1.color = Color::Yellow; + let _ = send(&player1.connection, "GAME:START:0"); + } - drop(player1); + drop(player1); - let mut player2 = clients_guard.get(&player2_addr.0).unwrap().write().await; + let mut player2 = clients_guard.get(&player2_addr.0).unwrap().write().await; - player2.current_match = Some(match_id); - player2.ready = false; + player2.current_match = Some(match_id); + player2.ready = false; - if match_guard.player1 == player2_addr.0 { - player2.color = Color::Red; - let _ = send(&player2.connection, "GAME:START:1"); - } else { - player2.color = Color::Yellow; - let _ = send(&player2.connection, "GAME:START:0"); - } + if match_guard.player1 == player2_addr.0 { + player2.color = Color::Red; + let _ = send(&player2.connection, "GAME:START:1"); + } else { + player2.color = Color::Yellow; + let _ = send(&player2.connection, "GAME:START:0"); + } - drop(player2); + drop(player2); - matches.write().await.insert(match_id, new_match.clone()); - } + matches.write().await.insert(match_id, new_match.clone()); } + } } #[async_trait] impl Tournament for RoundRobin { - fn new(ready_players: &[SocketAddr]) -> RoundRobin { - let mut result = RoundRobin { - players: HashMap::new(), - top_half: Vec::new(), - bottom_half: Vec::new(), - is_completed: false, - }; + fn new(ready_players: &[SocketAddr]) -> RoundRobin { + let mut result = RoundRobin { + players: HashMap::new(), + top_half: Vec::new(), + bottom_half: Vec::new(), + is_completed: false, + }; - let size = ready_players.len(); + let size = ready_players.len(); - for (id, player) in ready_players.iter().enumerate() { - result.players.insert(id as u32, (*player, 0)); - } - - for i in 0..size / 2 { - result.top_half.push(i as u32); - } - - for i in size / 2..size { - result.bottom_half.push(i as u32); - } - - result + for (id, player) in ready_players.iter().enumerate() { + result.players.insert(id as u32, (*player, 0)); } - fn inform_winner(&mut self, winner: SocketAddr, is_tie: bool) { - if is_tie { - return; - } - - for (_, player_addr) in self.players.iter_mut() { - if player_addr.0 == winner { - player_addr.1 += 1; - break; - } - } + for i in 0..size / 2 { + result.top_half.push(i as u32); } - fn inform_reconnect(&mut self, old_addr: SocketAddr, new_addr: SocketAddr) { - for (_, (player_addr, _)) in self.players.iter_mut() { - if *player_addr == old_addr { - *player_addr = new_addr; - break; - } - } + for i in size / 2..size { + result.bottom_half.push(i as u32); } - fn contains_player(&self, addr: SocketAddr) -> bool { - for (_, (player_addr, _)) in self.players.iter() { - if *player_addr == addr { - return true; - } - } - false + result + } + + fn inform_winner(&mut self, winner: SocketAddr, is_tie: bool) { + if is_tie { + return; } - async fn next(&mut self, server: &Server) { - if self.is_completed { - return; - } + for (_, player_addr) in self.players.iter_mut() { + if player_addr.0 == winner { + player_addr.1 += 1; + break; + } + } + } - if self.top_half.len() <= 1 || self.bottom_half.is_empty() { - self.is_completed = true; - return; - } + fn inform_reconnect(&mut self, old_addr: SocketAddr, new_addr: SocketAddr) { + for (_, (player_addr, _)) in self.players.iter_mut() { + if *player_addr == old_addr { + *player_addr = new_addr; + break; + } + } + } - let last_from_top = self.top_half.pop().unwrap(); - let first_from_bottom = self.bottom_half.remove(0); + fn contains_player(&self, addr: SocketAddr) -> bool { + for (_, (player_addr, _)) in self.players.iter() { + if *player_addr == addr { + return true; + } + } + false + } - self.top_half.insert(1, first_from_bottom); - self.bottom_half.push(last_from_top); - - let expected_bottom_start = self.top_half.len() as u32; - if self.top_half[1] == 1 && self.bottom_half[0] == expected_bottom_start { - self.is_completed = true; - } - - let clients_guard = server.clients.read().await; - let mut player_scores: Vec<(String, u32)> = Vec::new(); - for (_, player_addr) in self.players.iter() { - let player = clients_guard.get(&player_addr.0).unwrap().read().await; - let _ = send(&player.connection.clone(), "TOURNAMENT:END"); - player_scores.push((player.username.clone(), player_addr.1)); - } - drop(clients_guard); - - player_scores.sort_by(|a, b| b.1.cmp(&a.1)); - - let mut message = "TOURNAMENT:SCORES:".to_string(); - for (player, score) in player_scores.iter() { - message.push_str(&format!("{},{}|", player, score)) - } - message.pop(); - - server.broadcast_message_all_observers(&message).await; - - if self.is_completed() { - // Send scores - let clients_guard = server.clients.read().await; - for (_, player_addr) in self.players.iter() { - let player = clients_guard.get(&player_addr.0).unwrap().read().await; - let _ = send(&player.connection.clone(), "TOURNAMENT:END"); - } - } else { - // Create next matches - self.create_matches(&server.clients, &server.matches).await; - } + async fn next(&mut self, server: &Server) { + if self.is_completed { + return; } - async fn start(&mut self, server: &Server) { - self.create_matches(&server.clients, &server.matches).await; + if self.top_half.len() <= 1 || self.bottom_half.is_empty() { + self.is_completed = true; + return; } - async fn cancel(&mut self, server: &Server) { - for (_, addr) in self.players.iter() { - let clients_guard = server.clients.read().await; + let last_from_top = self.top_half.pop().unwrap(); + let first_from_bottom = self.bottom_half.remove(0); - let client = clients_guard.get(&addr.0); - if client.is_none() { - continue; - } - let client = client.unwrap().read().await; - let client_connection = client.connection.clone(); - let client_ready = client.ready; + self.top_half.insert(1, first_from_bottom); + self.bottom_half.push(last_from_top); - let match_id = client.current_match; - if match_id.is_none() { - continue; - } - let match_id = match_id.unwrap(); - - drop(client); - drop(clients_guard); - - server.terminate_match(match_id).await; - - if !client_ready { - let _ = send(&client_connection, "TOURNAMENT:END"); - } - } + let expected_bottom_start = self.top_half.len() as u32; + if self.top_half[1] == 1 && self.bottom_half[0] == expected_bottom_start { + self.is_completed = true; } - fn is_completed(&self) -> bool { - self.is_completed + let clients_guard = server.clients.read().await; + let mut player_scores: Vec<(String, u32)> = Vec::new(); + for (_, player_addr) in self.players.iter() { + let player = clients_guard.get(&player_addr.0).unwrap().read().await; + let _ = send(&player.connection.clone(), "TOURNAMENT:END"); + player_scores.push((player.username.clone(), player_addr.1)); } + drop(clients_guard); - fn get_type(&self) -> String { - "RoundRobin".to_string() + player_scores.sort_by(|a, b| b.1.cmp(&a.1)); + + let mut message = "TOURNAMENT:SCORES:".to_string(); + for (player, score) in player_scores.iter() { + message.push_str(&format!("{},{}|", player, score)) } + message.pop(); + + server.broadcast_message_all_observers(&message).await; + + if self.is_completed() { + // Send scores + let clients_guard = server.clients.read().await; + for (_, player_addr) in self.players.iter() { + let player = clients_guard.get(&player_addr.0).unwrap().read().await; + let _ = send(&player.connection.clone(), "TOURNAMENT:END"); + } + } else { + // Create next matches + self.create_matches(&server.clients, &server.matches).await; + } + } + + async fn start(&mut self, server: &Server) { + self.create_matches(&server.clients, &server.matches).await; + } + + async fn cancel(&mut self, server: &Server) { + for (_, addr) in self.players.iter() { + let clients_guard = server.clients.read().await; + + let client = clients_guard.get(&addr.0); + if client.is_none() { + continue; + } + let client = client.unwrap().read().await; + let client_connection = client.connection.clone(); + let client_ready = client.ready; + + let match_id = client.current_match; + if match_id.is_none() { + continue; + } + let match_id = match_id.unwrap(); + + drop(client); + drop(clients_guard); + + server.terminate_match(match_id).await; + + if !client_ready { + let _ = send(&client_connection, "TOURNAMENT:END"); + } + } + } + + fn is_completed(&self) -> bool { + self.is_completed + } + + fn get_type(&self) -> String { + "RoundRobin".to_string() + } } diff --git a/src/types.rs b/src/types.rs index 348bc75..b09f86e 100644 --- a/src/types.rs +++ b/src/types.rs @@ -7,152 +7,149 @@ use tokio_tungstenite::tungstenite::Message; #[derive(PartialEq, Clone, Copy)] pub enum Color { - Red, - Yellow, - None, + Red, + Yellow, + None, } impl ops::Not for Color { - type Output = Color; + type Output = Color; - fn not(self) -> Color { - match self { - Color::Red => Color::Yellow, - Color::Yellow => Color::Red, - Color::None => Color::None, - } + fn not(self) -> Color { + match self { + Color::Red => Color::Yellow, + Color::Yellow => Color::Red, + Color::None => Color::None, } + } } impl From for bool { - fn from(color: Color) -> bool { - match color { - Color::Red => true, - Color::Yellow => false, - Color::None => panic!("Cannot convert Color::None to bool"), - } + fn from(color: Color) -> bool { + match color { + Color::Red => true, + Color::Yellow => false, + Color::None => panic!("Cannot convert Color::None to bool"), } + } } #[derive(Clone)] pub struct Client { - pub username: String, - pub connection: UnboundedSender, - pub ready: bool, - pub color: Color, - pub current_match: Option, - pub addr: SocketAddr, + pub username: String, + pub connection: UnboundedSender, + pub ready: bool, + pub color: Color, + pub current_match: Option, + pub addr: SocketAddr, } impl Client { - pub fn new(username: String, connection: UnboundedSender, addr: SocketAddr) -> Client { - Client { - username, - connection, - ready: false, - color: Color::None, - current_match: None, - addr, - } + pub fn new(username: String, connection: UnboundedSender, addr: SocketAddr) -> Client { + Client { + username, + connection, + ready: false, + color: Color::None, + current_match: None, + addr, } + } } pub struct Match { - pub id: u32, - pub demo_mode: bool, - pub board: Vec>, - pub viewers: Vec, - pub ledger: Vec<(Color, usize, Instant)>, - pub wait_thread: Option>, - pub timeout_thread: Option>, - pub player1: SocketAddr, - pub player2: SocketAddr, + pub id: u32, + pub demo_mode: bool, + pub board: Vec>, + pub viewers: Vec, + pub ledger: Vec<(Color, usize, Instant)>, + pub wait_thread: Option>, + pub timeout_thread: Option>, + pub player1: SocketAddr, + pub player2: SocketAddr, } impl Match { - pub fn new(id: u32, player1: SocketAddr, player2: SocketAddr, demo_mode: bool) -> Match { - let first = if rand::rng().random_range(0..=1) == 0 { - player1.to_string().parse().unwrap() - } else { - player2.to_string().parse().unwrap() - }; + pub fn new(id: u32, player1: SocketAddr, player2: SocketAddr, demo_mode: bool) -> Match { + let first = if rand::rng().random_range(0..=1) == 0 { + player1.to_string().parse().unwrap() + } else { + player2.to_string().parse().unwrap() + }; - Match { - id, - demo_mode, - board: vec![vec![Color::None; 6]; 7], - viewers: Vec::new(), - ledger: Vec::new(), - wait_thread: None, - timeout_thread: None, - player1: if player1 == first { player1 } else { player2 }, - player2: if player1 == first { player2 } else { player1 }, + Match { + id, + demo_mode, + board: vec![vec![Color::None; 6]; 7], + viewers: Vec::new(), + ledger: Vec::new(), + wait_thread: None, + timeout_thread: None, + player1: if player1 == first { player1 } else { player2 }, + player2: if player1 == first { player2 } else { player1 }, + } + } + + pub fn place_token(&mut self, color: Color, column: usize) { + for i in 0..6 { + if self.board[column][i] == Color::None { + self.board[column][i] = color; + break; + } + } + } + + pub fn end_game_check(&self) -> (Color, bool) { + let mut result = (Color::None, false); + + let mut any_empty = true; + for x in 0..7 { + for y in 0..6 { + let color = self.board[x][y].clone(); + let mut horizontal_end = true; + let mut vertical_end = true; + let mut diagonal_end_up = true; + let mut diagonal_end_down = true; + + if any_empty && color == Color::None { + any_empty = false; } + + for i in 0..4 { + if x + i >= 7 || self.board[x + i][y] != color && horizontal_end { + horizontal_end = false; + } + + if y + i >= 6 || self.board[x][y + i] != color && vertical_end { + vertical_end = false; + } + + if x + i >= 7 || y + i >= 6 || self.board[x + i][y + i] != color && diagonal_end_up { + diagonal_end_up = false; + } + + if x + i >= 7 + || (y as i32 - i as i32) < 0 + || self.board[x + i][y - i] != color && diagonal_end_down + { + diagonal_end_down = false; + } + } + + if horizontal_end || vertical_end || diagonal_end_up || diagonal_end_down { + result = (color.clone(), false); + break; + } + } + if result.0 != Color::None { + break; + } } - pub fn place_token(&mut self, color: Color, column: usize) { - for i in 0..6 { - if self.board[column][i] == Color::None { - self.board[column][i] = color; - break; - } - } + if any_empty && result.0 == Color::None { + result.1 = true; } - pub fn end_game_check(&self) -> (Color, bool) { - let mut result = (Color::None, false); - - let mut any_empty = true; - for x in 0..7 { - for y in 0..6 { - let color = self.board[x][y].clone(); - let mut horizontal_end = true; - let mut vertical_end = true; - let mut diagonal_end_up = true; - let mut diagonal_end_down = true; - - if any_empty && color == Color::None { - any_empty = false; - } - - for i in 0..4 { - if x + i >= 7 || self.board[x + i][y] != color && horizontal_end { - horizontal_end = false; - } - - if y + i >= 6 || self.board[x][y + i] != color && vertical_end { - vertical_end = false; - } - - if x + i >= 7 - || y + i >= 6 - || self.board[x + i][y + i] != color && diagonal_end_up - { - diagonal_end_up = false; - } - - if x + i >= 7 - || (y as i32 - i as i32) < 0 - || self.board[x + i][y - i] != color && diagonal_end_down - { - diagonal_end_down = false; - } - } - - if horizontal_end || vertical_end || diagonal_end_up || diagonal_end_down { - result = (color.clone(), false); - break; - } - } - if result.0 != Color::None { - break; - } - } - - if any_empty && result.0 == Color::None { - result.1 = true; - } - - result - } + result + } }