handling differenk kinds of server messages, and matchmaking basics

This commit is contained in:
2025-11-12 17:11:23 +01:00
parent c2e29415c9
commit 5696128ac9
3 changed files with 324 additions and 32 deletions

169
server/src/connection.rs Normal file
View File

@@ -0,0 +1,169 @@
use futures_util::{SinkExt, StreamExt};
use serde::{Deserialize, Serialize};
use std::collections::{HashMap, VecDeque};
use std::sync::Arc;
use tokio::net::TcpStream;
use tokio::sync::Mutex;
use tokio_tungstenite::{WebSocketStream, tungstenite::Message};
use uuid::Uuid;
// Type definitions
pub type Tx = futures_util::stream::SplitSink<WebSocketStream<TcpStream>, Message>;
pub type ConnectionMap = Arc<Mutex<HashMap<Uuid, PlayerConnection>>>;
pub type MatchMap = Arc<Mutex<HashMap<Uuid, GameMatch>>>;
pub type WaitingQueue = Arc<Mutex<VecDeque<Uuid>>>;
// Helper functions to create new instances
pub fn new_connection_map() -> ConnectionMap {
Arc::new(Mutex::new(HashMap::new()))
}
pub fn new_match_map() -> MatchMap {
Arc::new(Mutex::new(HashMap::new()))
}
pub fn new_waiting_queue() -> WaitingQueue {
Arc::new(Mutex::new(VecDeque::new()))
}
#[derive(Debug)]
pub struct PlayerConnection {
pub id: Uuid,
pub username: Option<String>,
pub tx: Tx,
pub current_match: Option<Uuid>,
}
#[derive(Debug, Clone)]
pub struct GameMatch {
pub id: Uuid,
pub player_white: Uuid,
pub player_black: Uuid,
pub board_state: String,
pub move_history: Vec<String>,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct Step {
pub from: String,
pub to: String,
}
// Message sending utilities
pub async fn send_message_to_player(
connections: &ConnectionMap,
player_id: Uuid,
message: &str,
) -> Result<(), Box<dyn std::error::Error>> {
let mut connections_lock = connections.lock().await;
if let Some(connection) = connections_lock.get_mut(&player_id) {
connection
.tx
.send(Message::Text(message.to_string()))
.await?;
}
Ok(())
}
pub async fn broadcast_to_all(connections: &ConnectionMap, message: &str) {
let mut connections_lock = connections.lock().await;
let mut dead_connections = Vec::new();
for (id, connection) in connections_lock.iter_mut() {
if let Err(e) = connection.tx.send(Message::Text(message.to_string())).await {
eprintln!("Failed to send to {}: {}", id, e);
dead_connections.push(*id);
}
}
// Clean up dead connections
for dead_id in dead_connections {
connections_lock.remove(&dead_id);
}
}
pub async fn broadcast_to_match(
connections: &ConnectionMap,
matches: &MatchMap,
match_id: Uuid,
message: &str,
) -> Result<(), Box<dyn std::error::Error>> {
let matches_lock = matches.lock().await;
if let Some(game_match) = matches_lock.get(&match_id) {
send_message_to_player(connections, game_match.player_white, message).await?;
send_message_to_player(connections, game_match.player_black, message).await?;
}
Ok(())
}
// Connection handler
pub async fn handle_connection(
stream: TcpStream,
connections: ConnectionMap,
matches: MatchMap,
waiting_queue: WaitingQueue,
event_system: crate::events::EventSystem,
) -> anyhow::Result<()> {
use tokio_tungstenite::accept_async;
let ws_stream = accept_async(stream).await?;
let (write, mut read) = ws_stream.split();
let player_id = Uuid::new_v4();
// Store the connection
{
let mut conn_map = connections.lock().await;
conn_map.insert(
player_id,
PlayerConnection {
id: player_id,
username: None,
tx: write,
current_match: None,
},
);
}
println!("New connection: {}", player_id);
// Send welcome message
let _ = send_message_to_player(
&connections,
player_id,
&format!(r#"{{"type": "welcome", "player_id": "{}"}}"#, player_id),
)
.await;
// Message processing loop
while let Some(Ok(message)) = read.next().await {
if message.is_text() {
let text = message.to_text()?;
println!("Received from {}: {}", player_id, text);
// TODO: Parse and handle message with event system
// This will be implemented when we integrate the event system
}
}
// Cleanup on disconnect
cleanup_player(player_id, &connections, &matches, &waiting_queue).await;
println!("Connection {} closed", player_id);
Ok(())
}
async fn cleanup_player(
player_id: Uuid,
connections: &ConnectionMap,
_matches: &MatchMap,
waiting_queue: &WaitingQueue,
) {
// Remove from waiting queue
waiting_queue.lock().await.retain(|&id| id != player_id);
// Remove from connections
connections.lock().await.remove(&player_id);
println!("Cleaned up player {}", player_id);
}

View File

@@ -1,44 +1,55 @@
use futures_util::{SinkExt, StreamExt};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::sync::Arc;
mod connection;
mod events;
mod matchmaking;
use tokio::net::TcpListener;
use tokio::sync::Mutex;
use tokio_tungstenite::accept_async;
use uuid::Uuid;
mod broadcast_message;
mod handle_connection;
mod server_event;
use handle_connection::handle_connection;
#[derive(Serialize, Deserialize, Debug)]
struct MessageData {
username: String,
userid: u32,
text: String,
}
type Tx = futures_util::stream::SplitSink<
tokio_tungstenite::WebSocketStream<tokio::net::TcpStream>,
tokio_tungstenite::tungstenite::Message,
>;
type ConnectionMap = Arc<Mutex<HashMap<Uuid, Tx>>>;
#[tokio::main]
async fn main() {
let address = "0.0.0.0:9001"; //address to connect to
let listener = TcpListener::bind(address).await.unwrap();
async fn main() -> anyhow::Result<()> {
let address = "0.0.0.0:9001";
let listener = TcpListener::bind(address).await?;
println!("Server running on ws://{}", address);
let connections: ConnectionMap = Arc::new(Mutex::new(HashMap::new()));
// Shared state initialization using the new helper functions
let connections = connection::new_connection_map();
let matches = connection::new_match_map();
let waiting_queue = connection::new_waiting_queue();
// Event system for communication between components
let event_system = events::EventSystem::new();
// Start matchmaking background task
let matchmaker = matchmaking::MatchmakingSystem::new(
connections.clone(),
matches.clone(),
waiting_queue.clone(),
event_system.clone(),
);
tokio::spawn(async move {
matchmaker.run().await;
});
// Main connection loop
while let Ok((stream, _)) = listener.accept().await {
let connections = connections.clone();
let matches = matches.clone();
let waiting_queue = waiting_queue.clone();
let event_system = event_system.clone();
tokio::spawn(async move {
handle_connection(stream, connections).await;
if let Err(e) = connection::handle_connection(
stream,
connections,
matches,
waiting_queue,
event_system,
)
.await
{
eprintln!("Connection error: {}", e);
}
});
}
Ok(())
}

112
server/src/matchmaking.rs Normal file
View File

@@ -0,0 +1,112 @@
use crate::connection::{ConnectionMap, GameMatch, MatchMap, WaitingQueue};
use crate::events::EventSystem;
use rand::random;
use uuid::Uuid;
pub struct MatchmakingSystem {
connections: ConnectionMap,
matches: MatchMap,
waiting_queue: WaitingQueue,
event_system: EventSystem,
}
impl MatchmakingSystem {
pub fn new(
connections: ConnectionMap,
matches: MatchMap,
waiting_queue: WaitingQueue,
event_system: EventSystem,
) -> Self {
Self {
connections,
matches,
waiting_queue,
event_system,
}
}
pub async fn run(&self) {
loop {
self.try_create_match().await;
tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;
}
}
async fn try_create_match(&self) {
let mut queue = self.waiting_queue.lock().await;
while queue.len() >= 2 {
let player1 = queue.pop_front().unwrap();
let player2 = queue.pop_front().unwrap();
let match_id = Uuid::new_v4();
let (white_player, black_player) = if random::<bool>() {
(player1, player2)
} else {
(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(),
};
// 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);
}
if let Some(player) = conn_map.get_mut(&black_player) {
player.current_match = Some(match_id);
}
}
// Notify players
self.notify_players(white_player, black_player, match_id)
.await;
}
}
async fn notify_players(&self, white: Uuid, black: Uuid, match_id: Uuid) {
let conn_map = self.connections.lock().await;
// Get opponent names
let white_name = conn_map
.get(&black)
.and_then(|c| c.username.as_deref())
.unwrap_or("Opponent");
let black_name = conn_map
.get(&white)
.and_then(|c| c.username.as_deref())
.unwrap_or("Opponent");
// Notify white player
if let Some(_) = conn_map.get(&white) {
let message = format!(
r#"{{"type": "match_found", "match_id": "{}", "opponent": "{}", "color": "white"}}"#,
match_id, black_name
);
let _ =
crate::connection::send_message_to_player(&self.connections, white, &message).await;
}
// Notify black player
if let Some(_) = conn_map.get(&black) {
let message = format!(
r#"{{"type": "match_found", "match_id": "{}", "opponent": "{}", "color": "black"}}"#,
match_id, white_name
);
let _ =
crate::connection::send_message_to_player(&self.connections, black, &message).await;
}
println!("Match created: {} (white) vs {} (black)", white, black);
}
}