use crate::connection::ServerMessage2; use crate::connection::{ConnectionMap, GameMatch, MatchMap, WaitingQueue, broadcast_to_match}; use log::{error, info, warn}; use rand::random; use uuid::Uuid; pub struct MatchmakingSystem { connections: ConnectionMap, matches: MatchMap, waiting_queue: WaitingQueue, } impl MatchmakingSystem { pub fn new(connections: ConnectionMap, matches: MatchMap, waiting_queue: WaitingQueue) -> Self { Self { connections, matches, waiting_queue, } } pub async fn run(&self) { loop { self.try_create_match().await; tokio::time::sleep(tokio::time::Duration::from_millis(500)).await; } } pub async fn clean_up(&self, match_id: Uuid) { self.matches.lock().await.remove(&match_id); } async fn try_create_match(&self) { info!("Checking for new matches!"); let mut queue = self.waiting_queue.lock().await; while queue.len() >= 2 { let player1 = queue.pop_front().unwrap(); let player2 = queue.pop_front().unwrap(); info!("Creating new match. Players: {}, {}", &player1, &player2); let match_id = Uuid::new_v4(); let (white_player, black_player) = if random::() { info!("player1 is white, player2 is black"); (player1, player2) } else { info!("player2 is white, player1 is black"); (player2, player1) }; let game_match = GameMatch { id: match_id, player_white: white_player, player_black: black_player, board_state: "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1".to_string(), move_history: Vec::new(), }; info!("Match id: {}", &game_match.id); // Store the match self.matches.lock().await.insert(match_id, game_match); // Update player connections { let mut conn_map = self.connections.lock().await; if let Some(player) = conn_map.get_mut(&white_player) { player.current_match = Some(match_id); } else { error!("Could not store match id for white player"); } if let Some(player) = conn_map.get_mut(&black_player) { player.current_match = Some(match_id); } else { error!("Could not store match id for black player"); } } // Notify players info!( "Notifying player for a match: {:?} | {:?}", white_player, black_player ); self.notify_players(white_player, black_player, match_id) .await; } } async fn notify_players(&self, white: Uuid, black: Uuid, match_id: Uuid) { let mut conn_map = self.connections.lock().await; // Notify white player if let Some(_) = conn_map.get(&white) { let message = ServerMessage2::MatchFound { match_id: match_id.clone(), color: String::from("white"), opponent_name: conn_map .get(&white) .and_then(|c| c.username.as_deref()) .unwrap_or("Opponent") .to_string(), }; let _ = crate::connection::send_message_to_player_connection( conn_map.get_mut(&white), &serde_json::to_string(&message).unwrap(), ) .await; } // Notify black player if let Some(_) = conn_map.get(&black) { let message = ServerMessage2::MatchFound { match_id: match_id.clone(), color: String::from("black"), opponent_name: conn_map .get(&black) .and_then(|c| c.username.as_deref()) .unwrap_or("Opponent") .to_string(), }; let _ = crate::connection::send_message_to_player_connection( conn_map.get_mut(&white), &serde_json::to_string(&message).unwrap(), ) .await; } info!("Match created: {} (white) vs {} (black)", white, black); } } #[cfg(test)] mod tests { use super::*; use uuid::Uuid; use crate::connection::new_connection_map; use crate::connection::new_match_map; use crate::connection::new_waiting_queue; #[tokio::test] async fn test_matchmaking_creates_matches() { let connections = new_connection_map(); let matches = new_match_map(); let waiting_queue = new_waiting_queue(); let matchmaking = MatchmakingSystem::new(connections.clone(), matches.clone(), waiting_queue.clone()); let player1 = Uuid::new_v4(); let player2 = Uuid::new_v4(); { waiting_queue.lock().await.push_back(player1); waiting_queue.lock().await.push_back(player2); } matchmaking.try_create_match().await; { let matches_map = matches.lock().await; assert_eq!(matches_map.len(), 1, "Should create one match"); let game_match = matches_map.values().next().unwrap(); assert!(game_match.player_white == player1 || game_match.player_white == player2); assert!(game_match.player_black == player1 || game_match.player_black == player2); assert_ne!( game_match.player_white, game_match.player_black, "Players should be different" ); } { let queue = waiting_queue.lock().await; assert!( queue.is_empty(), "Waiting queue should be empty after matchmaking" ); } } #[tokio::test] async fn test_matchmaking_with_odd_players() { let connections = new_connection_map(); let matches = new_match_map(); let waiting_queue = new_waiting_queue(); let matchmaking = MatchmakingSystem::new(connections.clone(), matches.clone(), waiting_queue.clone()); let player1 = Uuid::new_v4(); { waiting_queue.lock().await.push_back(player1); } matchmaking.try_create_match().await; { let matches_map = matches.lock().await; assert!( matches_map.is_empty(), "Should not create match with only one player" ); let queue = waiting_queue.lock().await; assert_eq!(queue.len(), 1, "Should keep single player in queue"); } } }