From 08d99427018f929450f59ac111e3558aa7b343af Mon Sep 17 00:00:00 2001 From: Joshua Higgins Date: Sat, 6 Dec 2025 00:40:10 -0500 Subject: [PATCH] feat: lots of refactors, reworked outcomes for invalid moves --- src/main.rs | 529 +++++++++++++++++++++++------------------------ src/random_ai.rs | 11 - 2 files changed, 264 insertions(+), 276 deletions(-) delete mode 100644 src/random_ai.rs diff --git a/src/main.rs b/src/main.rs index 95c68d4..13169f5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,22 +1,16 @@ -mod random_ai; mod types; -use crate::random_ai::random_move; use crate::types::{Color, Match, Tournament}; use futures_util::{SinkExt, StreamExt}; use rand::Rng; use std::collections::HashMap; use std::env; use std::net::SocketAddr; -use std::num::ParseIntError; -use std::process::abort; use std::sync::Arc; -use tokio::io::{AsyncReadExt, AsyncWriteExt}; use tokio::net::{TcpListener, TcpStream}; use tokio::sync::mpsc::error::SendError; use tokio::sync::mpsc::UnboundedSender; use tokio::sync::RwLock; -use tokio_tungstenite::tungstenite::Utf8Bytes; use tokio_tungstenite::{accept_async, tungstenite::Message}; use tracing::{error, info, warn}; use types::Client; @@ -26,6 +20,20 @@ type Usernames = Arc>>; type Observers = Arc>>>; type Matches = Arc>>>>; +// TODO: allow random player1 in demo mode + +struct Server { + clients: Clients, + usernames: Usernames, + observers: Observers, + matches: Matches, + admin: Arc>>, + admin_password: Arc, + tournament: Arc>>, + waiting_timeout: Arc>, + demo_mode: bool, +} + #[tokio::main] async fn main() -> Result<(), anyhow::Error> { // Initialize logging @@ -49,19 +57,25 @@ async fn main() -> Result<(), anyhow::Error> { let tournament: Arc>> = Arc::new(RwLock::new(None)); let waiting_timeout: Arc> = Arc::new(RwLock::new(5000)); + let server_data = Arc::new( + Server { + clients, + usernames, + observers, + matches, + admin, + admin_password, + tournament, + waiting_timeout, + demo_mode + } + ); + while let Ok((stream, addr)) = listener.accept().await { tokio::spawn(handle_connection( stream, addr, - clients.clone(), - usernames.clone(), - observers.clone(), - matches.clone(), - admin.clone(), - admin_password.clone(), - tournament.clone(), - waiting_timeout.clone(), - demo_mode, + server_data.clone(), )); } @@ -71,15 +85,7 @@ async fn main() -> Result<(), anyhow::Error> { async fn handle_connection( stream: TcpStream, addr: SocketAddr, - clients: Clients, - usernames: Usernames, - observers: Observers, - matches: Matches, - admin: Arc>>, - admin_password: Arc, - tournament: Arc>>, - waiting_timeout: Arc>, - demo_mode: bool, + sd: Arc, ) -> Result<(), anyhow::Error> { info!("New WebSocket connection from: {}", addr); @@ -88,7 +94,7 @@ async fn handle_connection( let (tx, mut rx) = tokio::sync::mpsc::unbounded_channel(); // Store the client - observers.write().await.insert(addr, tx.clone()); + sd.observers.write().await.insert(addr, tx.clone()); // Spawn task to handle outgoing messages let send_task = tokio::spawn(async move { @@ -114,7 +120,7 @@ async fn handle_connection( } let mut is_taken = false; - for client in clients.read().await.values() { + for client in sd.clients.read().await.values() { if requested_username == client.read().await.username { let _ = send(&tx, &format!("ERROR:INVALID:ID:{}", requested_username)); is_taken = true; @@ -127,9 +133,9 @@ async fn handle_connection( } // not taken - observers.write().await.remove(&addr); - usernames.write().await.insert(requested_username.clone(), addr); - clients.write().await.insert( + sd.observers.write().await.remove(&addr); + sd.usernames.write().await.insert(requested_username.clone(), addr); + sd.clients.write().await.insert( addr.to_string().parse()?, Arc::new(RwLock::new(Client::new( requested_username, @@ -141,62 +147,72 @@ async fn handle_connection( let _ = send(&tx, "CONNECT:ACK"); } else if text == "READY" { - if clients.read().await.get(&addr).is_none() { + let clients_guard = sd.clients.read().await; + if clients_guard.get(&addr).is_none() { let _ = send(&tx, "ERROR:INVALID"); continue; } - if clients.read().await.get(&addr).unwrap().read().await.ready { + if clients_guard.get(&addr).unwrap().read().await.ready { let _ = send(&tx, "ERROR:INVALID"); continue; } - clients.write().await.get_mut(&addr).unwrap().write().await.ready = true; - + let mut client = clients_guard.get(&addr).unwrap().write().await; + client.ready = true; let _ = send(&tx, "READY:ACK"); - if demo_mode { - let match_id: u32 = rand::rng().random_range(100000..=999999); + if sd.demo_mode { + let match_id: u32 = gen_match_id(&sd.matches).await; let new_match = Arc::new(RwLock::new(Match::new( match_id, addr.to_string().parse()?, addr.to_string().parse()?, ))); - matches.write().await.insert(match_id, new_match.clone()); - clients.write().await.get_mut(&addr).unwrap().write().await.ready = false; - clients.write().await.get_mut(&addr).unwrap().write().await.current_match = - Some(match_id); - clients.write().await.get_mut(&addr).unwrap().write().await.color = - Color::Red; + sd.matches.write().await.insert(match_id, new_match.clone()); + client.ready = false; + client.current_match = Some(match_id); + client.color = Color::Red; let _ = send(&tx, "GAME:START:1"); } } else if text.starts_with("PLAY:") { - let clients_guard = clients.read().await; - let client_option = clients_guard.get(&addr); + let clients_guard = sd.clients.read().await; + let client = clients_guard.get(&addr); // Check if client is valid - if client_option.is_none() || client_option.unwrap().read().await.current_match.is_none() + if client.is_none() || client.unwrap().read().await.current_match.is_none() { let _ = send(&tx, "ERROR:INVALID:MOVE"); continue; } + let client = client.unwrap().read().await; - let client = client_option.unwrap().read().await; - - let matches_guard = matches.read().await; + let matches_guard = sd.matches.read().await; let current_match = matches_guard.get(&client.current_match.unwrap()).unwrap().read().await; - let opponent = { - let result = if addr == current_match.player1 { - clients_guard.get(¤t_match.player2).unwrap().read().await - } else { - clients_guard.get(¤t_match.player1).unwrap().read().await - }; + let opponent_addr = + if addr == current_match.player1 { + current_match.player1 + } else { + current_match.player2 + }; + + let opponent_connection = + if addr == current_match.player1 { + clients_guard.get(¤t_match.player2).unwrap().read().await.connection.clone() + } else { + clients_guard.get(¤t_match.player1).unwrap().read().await.connection.clone() + }; + + let opponent_username = + if addr == current_match.player1 { + clients_guard.get(¤t_match.player2).unwrap().read().await.username.clone() + } else { + clients_guard.get(¤t_match.player1).unwrap().read().await.username.clone() + }; - result - }; // Check if it's their move if (current_match.ledger.is_empty() && current_match.player1 != addr) || @@ -211,7 +227,7 @@ async fn handle_connection( drop(current_match); drop(matches_guard); - let mut matches_guard = matches.write().await; + let mut matches_guard = sd.matches.write().await; let mut current_match = matches_guard .get_mut(&client.current_match.unwrap()) .unwrap() @@ -239,31 +255,34 @@ async fn handle_connection( // Terminate games if a player makes an invalid move if invalid { - let opponent_addr = opponent.addr; let current_match_id = current_match.id; - broadcast_message(¤t_match.viewers, &observers, "GAME:TERMINATED").await; + if sd.demo_mode { + terminate_match(current_match_id, &sd.matches, &sd.clients, &sd.observers, sd.demo_mode).await; + tx.send(Message::Close(None))?; + } else { + let _ = send(&tx, "GAME:LOSS"); + let _ = send(&opponent_connection, "GAME:WINS"); + broadcast_message(¤t_match.viewers, &sd.observers, &format!("GAME:WIN:{}", opponent_username)).await; - drop(client); - drop(opponent); - drop(current_match); - drop(clients_guard); + drop(client); + drop(current_match); + drop(clients_guard); - let mut clients_guard = clients.write().await; - let mut client = clients_guard.get_mut(&addr).unwrap().write().await; - client.current_match = None; - let _ = send(&tx, "GAME:TERMINATED"); - drop(client); + let mut clients_guard = sd.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 opponent = - clients_guard.get_mut(&opponent_addr).unwrap().write().await; - - if !demo_mode { + let mut opponent = clients_guard.get_mut(&opponent_addr).unwrap().write().await; opponent.current_match = None; - let _ = send(&opponent.connection, "GAME:TERMINATED"); - } - matches_guard.remove(¤t_match_id).unwrap(); + opponent.color = Color::None; + opponent.score += 1; + drop(opponent); + matches_guard.remove(¤t_match_id).unwrap(); + } continue; } else { // Place it @@ -274,118 +293,55 @@ async fn handle_connection( // broadcast the move to viewers broadcast_message( ¤t_match.viewers, - &observers, + &sd.observers, &format!("GAME:MOVE:{}:{}", client.username, column_parse.clone()?), ) .await; // Check game end conditions - let (winner, filled) = { - let mut result = (Color::None, false); - - let mut any_empty = true; - for x in 0..7 { - for y in 0..6 { - let color = current_match.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 - || current_match.board[x + i][y] != color && horizontal_end - { - horizontal_end = false; - } - - if y + i >= 6 - || current_match.board[x][y + i] != color && vertical_end - { - vertical_end = false; - } - - if x + i >= 7 - || y + i >= 6 - || current_match.board[x + i][y + i] != color - && diagonal_end_up - { - diagonal_end_up = false; - } - - if x + i >= 7 - || (y as i32 - i as i32) < 0 - || current_match.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 - }; + let (winner, filled) = end_game_check(¤t_match.board); if winner != Color::None { if winner == client.color { let _ = send(&tx, "GAME:WINS"); - if !demo_mode { - let _ = send(&opponent.connection, "GAME:LOSS"); + if !sd.demo_mode { + let _ = send(&opponent_connection, "GAME:LOSS"); } broadcast_message( ¤t_match.viewers, - &observers, + &sd.observers, &format!("GAME:WIN:{}", client.username), ) .await; } else { let _ = send(&tx, "GAME:LOSS"); - if !demo_mode { - let _ = send(&opponent.connection, "GAME:WINS"); + if !sd.demo_mode { + let _ = send(&opponent_connection, "GAME:WINS"); } broadcast_message( ¤t_match.viewers, - &observers, - &format!("GAME:WIN:{}", opponent.username), + &sd.observers, + &format!("GAME:WIN:{}", opponent_username), ) .await; } } else if filled { let _ = send(&tx, "GAME:DRAW"); - if !demo_mode { - let _ = send(&opponent.connection, "GAME:DRAW"); + if !sd.demo_mode { + let _ = send(&opponent_connection, "GAME:DRAW"); } - broadcast_message(¤t_match.viewers, &observers, "GAME:DRAW").await; + broadcast_message(¤t_match.viewers, &sd.observers, "GAME:DRAW").await; } // remove match from matchmaker if winner != Color::None || filled { - let opponent_addr = opponent.addr; let current_match_id = current_match.id; drop(client); - drop(opponent); drop(current_match); drop(clients_guard); - let mut clients_guard = clients.write().await; + let mut clients_guard = sd.clients.write().await; let mut client = clients_guard.get_mut(&addr).unwrap().write().await; if client.color == winner { client.score += 1; @@ -397,7 +353,7 @@ async fn handle_connection( let mut opponent = clients_guard.get_mut(&opponent_addr).unwrap().write().await; - if !demo_mode { + if !sd.demo_mode { opponent.current_match = None; if opponent.color == winner { opponent.score += 1; @@ -407,8 +363,8 @@ async fn handle_connection( matches_guard.remove(¤t_match_id).unwrap(); drop(opponent); - if !demo_mode && matches_guard.is_empty() { - let mut tournament_guard = tournament.write().await; + if !sd.demo_mode && matches_guard.is_empty() { + let mut tournament_guard = sd.tournament.write().await; let tourney = tournament_guard.as_mut().unwrap(); tourney.next(); if tourney.is_completed { @@ -429,7 +385,7 @@ async fn handle_connection( } message.pop(); - broadcast_message_all_observers(&observers, &message).await; + broadcast_message_all_observers(&sd.observers, &message).await; } else { // Create next matches // TODO: Make this a function @@ -440,8 +396,7 @@ async fn handle_connection( if player2_addr.is_none() { continue; } let player2_addr = player2_addr.unwrap(); - // TODO: gen without collisions - let match_id: u32 = rand::rng().random_range(100000..=999999); + let match_id: u32 = gen_match_id(&sd.matches).await; let new_match = Arc::new(RwLock::new(Match::new( match_id, *player1_addr, @@ -487,19 +442,20 @@ async fn handle_connection( continue; } - let connection_to_send = if !demo_mode { opponent.connection.clone() } else { tx.clone() }; - let column = if !demo_mode { column_parse.clone()? } else { random_move(¤t_match.board) }; - if demo_mode { + let connection_to_send = if !sd.demo_mode { opponent_connection.clone() } else { tx.clone() }; + let column = if !sd.demo_mode { column_parse.clone()? } else { random_move(¤t_match.board) }; + if sd.demo_mode { let move_to_dispatch = current_match.move_to_dispatch.clone(); current_match.ledger.push(move_to_dispatch); current_match.move_to_dispatch = (Color::Blue, column); current_match.place_token(Color::Blue, column); } - let waiting = *waiting_timeout.read().await as i64 + (rand::rng().random_range(0..=50) - 25); - let matches_move = matches.clone(); - let observers_move = observers.clone(); + let waiting = *sd.waiting_timeout.read().await as i64 + (rand::rng().random_range(0..=50) - 25); + let matches_move = sd.matches.clone(); + let observers_move = sd.observers.clone(); let match_id_move = current_match.id; + let demo_mode_move = sd.demo_mode; current_match.wait_thread = Some(tokio::spawn(async move { tokio::time::sleep(tokio::time::Duration::from_millis(waiting as u64)).await; @@ -509,7 +465,7 @@ async fn handle_connection( current_match.ledger.push(move_to_dispatch); current_match.move_to_dispatch = (Color::None, 0); - if demo_mode { + if demo_mode_move { broadcast_message( ¤t_match.viewers, &observers_move, @@ -525,7 +481,7 @@ async fn handle_connection( } else if text == "PLAYER:LIST" { - let clients_guard = clients.read().await; + let clients_guard = sd.clients.read().await; let mut to_send = "PLAYER:LIST:".to_string(); for client_guard in clients_guard.values() { let player = client_guard.read().await; @@ -540,8 +496,8 @@ async fn handle_connection( let _ = send(&tx, to_send.as_str()); } else if text == "GAME:LIST" { - let matches_guard = matches.read().await; - let clients_guard = clients.read().await; + let matches_guard = sd.matches.read().await; + let clients_guard = sd.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; @@ -549,7 +505,7 @@ async fn handle_connection( let player2 = clients_guard.get(&a_match.player2).unwrap().read().await; to_send += a_match.id.to_string().as_str(); to_send += ","; to_send += player1.username.as_str(); to_send += ","; - to_send += (if player1.username == player2.username { "demo" } else { player2.username.as_str() }); + to_send += if player1.username == player2.username { "demo" } else { player2.username.as_str() }; to_send += "|"; } @@ -561,15 +517,15 @@ async fn handle_connection( let match_id_parse = text.split(":").collect::>()[2].parse::(); match match_id_parse { Ok(match_id) => { - let result = watch(&matches, match_id, addr).await; + let result = watch(&sd.matches, match_id, addr).await; if result.is_err() { let _ = send(&tx, "ERROR:INVALID:WATCH"); continue; } - let clients_guard = clients.read().await; - let matches_guard = matches.read().await; + let clients_guard = sd.clients.read().await; + let matches_guard = sd.matches.read().await; let the_match = matches_guard.get(&match_id).unwrap().read().await; let player1 = clients_guard.get(&the_match.player1).unwrap().read().await.username.clone(); let mut player2 = clients_guard.get(&the_match.player2).unwrap().read().await.username.clone(); - if demo_mode { player2 = "demo".to_string(); } + if sd.demo_mode { player2 = "demo".to_string(); } let ledger = the_match.ledger.clone(); drop(clients_guard); @@ -595,31 +551,30 @@ async fn handle_connection( } else if text.starts_with("ADMIN:AUTH:") { - if admin.read().await.is_some() { + if sd.admin.read().await.is_some() { let _ = send(&tx, "ERROR:INVALID:AUTH"); continue; } let password_parse = text.split(":").collect::>()[2]; - if password_parse != *admin_password { + if password_parse != *sd.admin_password { let _ = send(&tx, "ERROR:INVALID:AUTH"); continue; } - let mut admin_guard = admin.write().await; + let mut admin_guard = sd.admin.write().await; *admin_guard = Some(addr.to_string().parse()?); let _ = send(&tx, "ADMIN:AUTH:ACK"); } else if text.starts_with("ADMIN:KICK:") { - if admin.read().await.is_none() || admin.read().await.unwrap() != addr { + if !auth_check(&sd.admin, addr).await { let _ = send(&tx, "ERROR:INVALID:AUTH"); - continue; } let kick_username = text.split(":").collect::>()[2]; - let usernames_guard = usernames.read().await; - let clients_guard = clients.read().await; + let usernames_guard = sd.usernames.read().await; + let clients_guard = sd.clients.read().await; let kick_addr_result = usernames_guard.get(kick_username); match kick_addr_result { @@ -634,62 +589,27 @@ async fn handle_connection( } } else if text.starts_with("GAME:TERMINATE:") { - if admin.read().await.is_none() || admin.read().await.unwrap() != addr { + if !auth_check(&sd.admin, addr).await { let _ = send(&tx, "ERROR:INVALID:AUTH"); - continue; } let match_id_parse = text.split(":").collect::>()[2].parse::(); if match_id_parse.is_err() { let _ = send(&tx, "ERROR:INVALID:TERMINATE"); continue; } - let match_guard = matches.read().await; - let match_id_request = match_guard.get(&match_id_parse.clone()?).unwrap(); - let the_match = match_id_request.read().await; - - if let Some(wait_thread) = &the_match.wait_thread { - wait_thread.abort(); - } - - let player1_addr = the_match.player1; - let player2_addr = the_match.player2; - broadcast_message(&the_match.viewers, &observers, "GAME:TERMINATED").await; - - drop(the_match); - drop(match_guard); - - let clients_guard = clients.write().await; - - let mut player1 = clients_guard.get(&player1_addr).unwrap().write().await; - player1.current_match = None; - player1.color = Color::None; - let _ = send(&player1.connection, "GAME:TERMINATED"); - drop(player1); - - if !demo_mode { - let mut player2 = clients_guard.get(&player2_addr).unwrap().write().await; - player2.current_match = None; - player2.color = Color::None; - let _ = send(&player2.connection, "GAME:TERMINATED"); - drop(player2); - } - - drop(clients_guard); - - matches.write().await.remove(&match_id_parse?); + terminate_match(match_id_parse?, &sd.matches, &sd.clients, &sd.observers, sd.demo_mode).await; } - + else if text == "TOURNAMENT:START" { - if admin.read().await.is_none() || admin.read().await.unwrap() != addr { + if !auth_check(&sd.admin, addr).await { let _ = send(&tx, "ERROR:INVALID:AUTH"); - continue; } - if tournament.read().await.is_some() || demo_mode { + if sd.tournament.read().await.is_some() || sd.demo_mode { let _ = send(&tx, "ERROR:INVALID:TOURNAMENT"); continue; } - let mut clients_guard = clients.write().await; + let mut clients_guard = sd.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 { @@ -704,13 +624,13 @@ async fn handle_connection( let tourney = Tournament::new(&ready_players); - let mut tournament_guard = tournament.write().await; + let mut tournament_guard = sd.tournament.write().await; *tournament_guard = Some(tourney.clone()); for (i, id) in tourney.top_half.iter().enumerate() { let player1_addr = tourney.players.get(id).unwrap(); let player2_addr = tourney.players.get(tourney.bottom_half.get(i).unwrap()).unwrap(); - let match_id: u32 = rand::rng().random_range(100000..=999999); + let match_id: u32 = gen_match_id(&sd.matches).await; let new_match = Arc::new(RwLock::new(Match::new( match_id, *player1_addr, @@ -748,19 +668,19 @@ async fn handle_connection( drop(player2); - matches.write().await.insert(match_id, new_match.clone()); + sd.matches.write().await.insert(match_id, new_match.clone()); } - + let _ = send(&tx, "TOURNAMENT:START:ACK"); } + // TODO: Cancel Tournament else if text.starts_with("TOURNAMENT:WAIT:") { - if admin.read().await.is_none() || admin.read().await.unwrap() != addr { + if !auth_check(&sd.admin, addr).await { let _ = send(&tx, "ERROR:INVALID:AUTH"); - continue; } let new_timeout = text.split(":").collect::>()[2].parse::()?; - *waiting_timeout.write().await = (new_timeout * 1000.0) as u64; + *sd.waiting_timeout.write().await = (new_timeout * 1000.0) as u64; } else { @@ -784,41 +704,22 @@ async fn handle_connection( // Clean up send_task.abort(); - // Remove and terminate any matches // TODO: Support reconnecting behaviors - if clients.read().await.get(&addr).is_some() { // We may not be a client disconnecting - if let Some(match_id) = clients.read().await.get(&addr).unwrap().read().await.current_match { - let matches_guard = matches.read().await; - let clients_guard = clients.read().await; - let the_match = matches_guard.get(&match_id).unwrap().read().await; - if let Some(wait_thread) = &the_match.wait_thread { - wait_thread.abort(); - } - let player1 = clients_guard.get(&the_match.player1).unwrap().read().await; - let player2 = clients_guard.get(&the_match.player2).unwrap().read().await; - - let _ = send(&player1.connection, "GAME:TERMINATED"); - let _ = send(&player2.connection, "GAME:TERMINATED"); - - broadcast_message(&the_match.viewers, &observers, "GAME:TERMINATED").await; - - drop(player1); - drop(player2); - drop(the_match); - drop(matches_guard); - drop(clients_guard); - - matches.write().await.remove(&match_id); + // Remove and terminate any matches + // We may not be a client disconnecting, do this check + if sd.clients.read().await.get(&addr).is_some() { + if let Some(match_id) = sd.clients.read().await.get(&addr).unwrap().read().await.current_match { + terminate_match(match_id, &sd.matches, &sd.clients, &sd.observers, sd.demo_mode).await; } - let client = clients.write().await.remove(&addr).unwrap(); + let client = sd.clients.write().await.remove(&addr).unwrap(); let username = client.read().await.username.clone(); - usernames.write().await.remove(&username); + sd.usernames.write().await.remove(&username); } - observers.write().await.remove(&addr); + sd.observers.write().await.remove(&addr); - let mut admin_guard = admin.write().await; + let mut admin_guard = sd.admin.write().await; if let Some(admin_addr) = *admin_guard { if admin_addr == addr { *admin_guard = None; @@ -831,6 +732,58 @@ async fn handle_connection( Ok(()) } +fn end_game_check(board: &[Vec]) -> (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 = 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 || board[x + i][y] != color && horizontal_end { + horizontal_end = false; + } + + if y + i >= 6 || board[x][y + i] != color && vertical_end { + vertical_end = false; + } + + if x + i >= 7 || y + i >= 6 || board[x + i][y + i] != color && diagonal_end_up { + diagonal_end_up = false; + } + + if x + i >= 7 || (y as i32 - i as i32) < 0 || + 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 +} + async fn broadcast_message(addrs: &Vec, observers: &Observers, msg: &str) { for addr in addrs { let observers_guard = observers.read().await; @@ -875,22 +828,68 @@ async fn watch(matches: &Matches, new_match_id: u32, addr: SocketAddr) -> Result Ok(()) } -async fn get_current_watching_match(matches: &Matches, addr: SocketAddr) -> Option { +async fn auth_check(admin: &Arc>>, addr: SocketAddr) -> bool { + if admin.read().await.is_none() || admin.read().await.unwrap() != addr { + return false; + } + true +} + +async fn gen_match_id(matches: &Matches) -> u32 { let matches_guard = matches.read().await; - let mut result = None; - for a_match in matches_guard.values() { - let a_match = a_match.read().await; - for viewer_addr in &a_match.viewers { - if *viewer_addr == addr { - result = Some(a_match.id); - break; - } - } - if result.is_some() { break; } + let mut result = rand::rng().random_range(100000..=999999); + while matches_guard.get(&result).is_some() { + result = rand::rng().random_range(100000..=999999); } result } +async fn terminate_match(match_id: u32, matches: &Matches, clients: &Clients, observers: &Observers, demo_mode: bool) { + let matches_guard = 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 the_match.wait_thread.is_some() { + the_match.wait_thread.as_ref().unwrap().abort(); + } + + broadcast_message(&the_match.viewers, observers, "GAME:TERMINATED").await; + + let clients_guard = clients.read().await; + 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; + drop(player1); + + if !demo_mode { + 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(player2); + } + + drop(clients_guard); + + drop(the_match); + drop(matches_guard); + + matches.write().await.remove(&match_id); +} + +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); + } + + random +} + fn send(tx: &UnboundedSender, text: &str) -> Result<(), SendError> { tx.send(Message::text(text)) } diff --git a/src/random_ai.rs b/src/random_ai.rs deleted file mode 100644 index d192e2d..0000000 --- a/src/random_ai.rs +++ /dev/null @@ -1,11 +0,0 @@ -use crate::types::Color; -use rand::Rng; - -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); - } - - random -}