210 lines
6.7 KiB
Rust
210 lines
6.7 KiB
Rust
use crate::connection::ServerMessage2;
|
|
use crate::connection::{ConnectionMap, GameMatch, MatchMap, WaitingQueue};
|
|
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;
|
|
}
|
|
}
|
|
|
|
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::<bool>() {
|
|
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);
|
|
//TODO: at the end of a match delete this from player
|
|
} 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(&black),
|
|
&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");
|
|
}
|
|
}
|
|
}
|