From cbff8c7c608e54bc86487d15d38ed3f491b46399 Mon Sep 17 00:00:00 2001 From: Joshua Higgins Date: Wed, 31 Dec 2025 16:10:32 -0500 Subject: [PATCH 1/3] feat(tournaments & demo mode): demo mode can be changed at runtime --- src/server.rs | 76 ++++++++++++++++++++++++---------- src/tournaments/mod.rs | 1 + src/tournaments/round_robin.rs | 5 +++ src/types.rs | 4 +- 4 files changed, 64 insertions(+), 22 deletions(-) diff --git a/src/server.rs b/src/server.rs index e9e3e62..0593f53 100644 --- a/src/server.rs +++ b/src/server.rs @@ -9,7 +9,7 @@ pub struct Server { pub admin_password: Arc, pub tournament: WrappedTournament, pub waiting_timeout: Arc>, - pub demo_mode: bool, + pub demo_mode: Arc>, pub tournament_type: String, } @@ -24,7 +24,7 @@ impl Server { admin_password: Arc::new(admin_password), tournament: Arc::new(RwLock::new(None)), waiting_timeout: Arc::new(RwLock::new(5000)), - demo_mode, + demo_mode: Arc::new(RwLock::new(demo_mode)), tournament_type, } } @@ -90,12 +90,14 @@ impl Server { client.ready = true; let _ = crate::send(&tx, "READY:ACK"); - if self.demo_mode { + let is_demo_mode = self.demo_mode.read().await.clone(); + if is_demo_mode { let match_id: u32 = crate::gen_match_id(&self.matches).await; let new_match = Arc::new(RwLock::new(Match::new( match_id, addr.to_string().parse()?, addr.to_string().parse()?, + is_demo_mode, ))); self.matches.write().await.insert(match_id, new_match.clone()); client.ready = false; @@ -175,6 +177,7 @@ impl Server { // 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); @@ -182,7 +185,7 @@ impl Server { drop(client); drop(clients_guard); - if self.demo_mode { + if is_demo_mode { self.terminate_match(current_match_id).await; tx.send(Message::Close(None))?; } else { @@ -224,7 +227,7 @@ impl Server { if winner != Color::None { if winner == client.color { let _ = crate::send(&tx, "GAME:WINS"); - if !self.demo_mode { + if !current_match.demo_mode { let _ = crate::send(&opponent_connection, "GAME:LOSS"); } self.broadcast_message( @@ -234,7 +237,7 @@ impl Server { .await; } else { let _ = crate::send(&tx, "GAME:LOSS"); - if !self.demo_mode { + if !current_match.demo_mode { let _ = crate::send(&opponent_connection, "GAME:WINS"); } self.broadcast_message( @@ -245,7 +248,7 @@ impl Server { } } else if filled { let _ = crate::send(&tx, "GAME:DRAW"); - if !self.demo_mode { + if !current_match.demo_mode { let _ = crate::send(&opponent_connection, "GAME:DRAW"); } self.broadcast_message(¤t_match.viewers, "GAME:DRAW").await; @@ -254,6 +257,7 @@ impl Server { // 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); @@ -277,7 +281,7 @@ impl Server { drop(opponent); matches_guard.remove(¤t_match_id).unwrap(); - if !self.demo_mode && matches_guard.is_empty() { + if !is_demo_mode && matches_guard.is_empty() { drop(matches_guard); drop(clients_guard); @@ -292,17 +296,17 @@ impl Server { return Ok(()); } - let connection_to_send = if !self.demo_mode { + let connection_to_send = if !current_match.demo_mode { opponent_connection.clone() } else { tx.clone() }; - let column_to_use = if !self.demo_mode { + let column_to_use = if !current_match.demo_mode { column } else { crate::random_move(¤t_match.board) }; - if self.demo_mode { + if current_match.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::Yellow, column_to_use); @@ -314,7 +318,7 @@ impl Server { let matches_move = self.matches.clone(); let observers_move = self.observers.clone(); let match_id_move = current_match.id; - let demo_mode_move = self.demo_mode; + let demo_mode_move = current_match.demo_mode; current_match.wait_thread = Some(tokio::spawn(async move { tokio::time::sleep(tokio::time::Duration::from_millis(waiting as u64)).await; @@ -418,7 +422,7 @@ impl Server { 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 self.demo_mode { + if the_match.demo_mode { player2 = "demo".to_string(); } let ledger = the_match.ledger.clone(); @@ -497,7 +501,7 @@ impl Server { self.terminate_match(match_id).await; - if !self.demo_mode && self.matches.read().await.is_empty() { + 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; @@ -517,7 +521,7 @@ impl Server { return Err(anyhow::anyhow!("ERROR:INVALID:AUTH")); } - if self.tournament.read().await.is_some() || self.demo_mode { + if self.tournament.read().await.is_some() { return Err(anyhow::anyhow!("ERROR:INVALID:TOURNAMENT")); } @@ -557,7 +561,7 @@ impl Server { return Err(anyhow::anyhow!("ERROR:INVALID:AUTH")); } - if self.tournament.read().await.is_none() || self.demo_mode { + if self.tournament.read().await.is_none() { return Err(anyhow::anyhow!("ERROR:INVALID:TOURNAMENT")); } @@ -598,13 +602,43 @@ impl Server { tx: UnboundedSender, ) -> Result<(), anyhow::Error> { let status = self.tournament.read().await.is_some(); - if self.demo_mode { - let _ = crate::send(&tx, "GET:TOURNAMENT_STATUS:DEMO"); + let mut msg = "GET:TOURNAMENT_STATUS:".to_string(); + if status { + msg += self.tournament.read().await.as_ref().unwrap().read().await.get_type().as_str(); } else { - let mut msg = "GET:TOURNAMENT_STATUS:".to_string(); msg += status.to_string().as_str(); - let _ = crate::send(&tx, &msg); } + let _ = crate::send(&tx, &msg); + Ok(()) + } + + pub async fn handle_get_demo_mode( + &self, + tx: UnboundedSender, + ) -> Result<(), anyhow::Error> { + let demo_mode = *self.demo_mode.read().await; + let mut msg = "GET:DEMO_MODE:".to_string(); + msg += demo_mode.to_string().as_str(); + let _ = crate::send(&tx, &msg); + Ok(()) + } + + pub async fn handle_set_demo_mode( + &self, + tx: UnboundedSender, + addr: SocketAddr, + demo_mode: bool, + ) -> 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")); + } + + *self.demo_mode.write().await = demo_mode; + let _ = crate::send(&tx, "SET:DEMO_MODE:ACK"); Ok(()) } @@ -660,7 +694,7 @@ impl Server { player1.color = Color::None; drop(player1); - if !self.demo_mode { + if !the_match.demo_mode { let mut player2 = clients_guard.get(&the_match.player2).unwrap().write().await; let _ = send(&player2.connection, "GAME:TERMINATED"); player2.current_match = None; diff --git a/src/tournaments/mod.rs b/src/tournaments/mod.rs index 724f149..f956c0a 100644 --- a/src/tournaments/mod.rs +++ b/src/tournaments/mod.rs @@ -16,4 +16,5 @@ pub trait Tournament { async fn start(&mut self, server: &Server); async fn cancel(&mut self, server: &Server); 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 9060874..b037aa5 100644 --- a/src/tournaments/round_robin.rs +++ b/src/tournaments/round_robin.rs @@ -29,6 +29,7 @@ impl RoundRobin { match_id, *player1_addr, *player2_addr, + false, ))); let match_guard = new_match.read().await; @@ -178,4 +179,8 @@ impl Tournament for RoundRobin { 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 8a9686a..b6f33bd 100644 --- a/src/types.rs +++ b/src/types.rs @@ -40,6 +40,7 @@ impl Client { pub struct Match { pub id: u32, + pub demo_mode: bool, pub board: Vec>, pub viewers: Vec, pub ledger: Vec<(Color, usize)>, @@ -50,7 +51,7 @@ pub struct Match { } impl Match { - pub fn new(id: u32, player1: SocketAddr, player2: SocketAddr) -> 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 { @@ -59,6 +60,7 @@ impl Match { Match { id, + demo_mode, board: vec![vec![Color::None; 6]; 7], viewers: Vec::new(), ledger: Vec::new(), From 5e02ccc47beb5be1065d454dbf03c910838bd6df Mon Sep 17 00:00:00 2001 From: Joshua Higgins Date: Wed, 31 Dec 2025 16:15:03 -0500 Subject: [PATCH 2/3] misc: remove unnessacary crate prefixes --- src/server.rs | 62 +++++++++++++++++++++++++-------------------------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/src/server.rs b/src/server.rs index 0593f53..83fc541 100644 --- a/src/server.rs +++ b/src/server.rs @@ -1,4 +1,4 @@ -use crate::{tournaments::Tournament, types::*, *}; +use crate::{tournaments::*, types::*, *}; pub struct Server { pub clients: Clients, @@ -67,7 +67,7 @@ impl Server { ))), ); - let _ = crate::send(&tx, "CONNECT:ACK"); + let _ = send(&tx, "CONNECT:ACK"); Ok(()) } @@ -88,11 +88,11 @@ impl Server { let mut client = clients_guard.get(&addr).unwrap().write().await; client.ready = true; - let _ = crate::send(&tx, "READY:ACK"); + let _ = send(&tx, "READY:ACK"); let is_demo_mode = self.demo_mode.read().await.clone(); if is_demo_mode { - let match_id: u32 = crate::gen_match_id(&self.matches).await; + 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()?, @@ -103,7 +103,7 @@ impl Server { client.ready = false; client.current_match = Some(match_id); client.color = Color::Red; - let _ = crate::send(&tx, "GAME:START:1"); + let _ = send(&tx, "GAME:START:1"); } Ok(()) @@ -152,7 +152,7 @@ impl Server { || (current_match.ledger.last().is_some() && current_match.ledger.last().unwrap().0 == client.color) { - let _ = crate::send(&tx, "ERROR:INVALID:MOVE"); + let _ = send(&tx, "ERROR:INVALID:MOVE"); invalid = true; } @@ -165,12 +165,12 @@ impl Server { // Check if valid move if column >= 7 && !invalid { - let _ = crate::send(&tx, "ERROR:INVALID:MOVE"); + let _ = send(&tx, "ERROR:INVALID:MOVE"); invalid = true; } if current_match.board[column][5] != Color::None && !invalid { - let _ = crate::send(&tx, "ERROR:INVALID:MOVE"); + let _ = send(&tx, "ERROR:INVALID:MOVE"); invalid = true; } @@ -189,8 +189,8 @@ impl Server { self.terminate_match(current_match_id).await; tx.send(Message::Close(None))?; } else { - let _ = crate::send(&tx, "GAME:LOSS"); - let _ = crate::send(&opponent_connection, "GAME:WINS"); + let _ = send(&tx, "GAME:LOSS"); + let _ = send(&opponent_connection, "GAME:WINS"); self.broadcast_message(&viewers, &format!("GAME:WIN:{}", opponent_username)).await; let mut clients_guard = self.clients.write().await; @@ -226,9 +226,9 @@ impl Server { if winner != Color::None { if winner == client.color { - let _ = crate::send(&tx, "GAME:WINS"); + let _ = send(&tx, "GAME:WINS"); if !current_match.demo_mode { - let _ = crate::send(&opponent_connection, "GAME:LOSS"); + let _ = send(&opponent_connection, "GAME:LOSS"); } self.broadcast_message( ¤t_match.viewers, @@ -236,9 +236,9 @@ impl Server { ) .await; } else { - let _ = crate::send(&tx, "GAME:LOSS"); + let _ = send(&tx, "GAME:LOSS"); if !current_match.demo_mode { - let _ = crate::send(&opponent_connection, "GAME:WINS"); + let _ = send(&opponent_connection, "GAME:WINS"); } self.broadcast_message( ¤t_match.viewers, @@ -247,9 +247,9 @@ impl Server { .await; } } else if filled { - let _ = crate::send(&tx, "GAME:DRAW"); + let _ = send(&tx, "GAME:DRAW"); if !current_match.demo_mode { - let _ = crate::send(&opponent_connection, "GAME:DRAW"); + let _ = send(&opponent_connection, "GAME:DRAW"); } self.broadcast_message(¤t_match.viewers, "GAME:DRAW").await; } @@ -304,7 +304,7 @@ impl Server { let column_to_use = if !current_match.demo_mode { column } else { - crate::random_move(¤t_match.board) + random_move(¤t_match.board) }; if current_match.demo_mode { let move_to_dispatch = current_match.move_to_dispatch.clone(); @@ -329,7 +329,7 @@ impl Server { current_match.move_to_dispatch = (Color::None, 0); if demo_mode_move { - crate::broadcast_message( + broadcast_message( &observers_move, ¤t_match.viewers, &format!("GAME:MOVE:{}:{}", "demo", column_to_use), @@ -340,7 +340,7 @@ impl Server { drop(current_match); drop(matches_guard); - let _ = crate::send(&connection_to_send, &format!("OPPONENT:{}", column_to_use)); + let _ = send(&connection_to_send, &format!("OPPONENT:{}", column_to_use)); })); Ok(()) @@ -370,7 +370,7 @@ impl Server { to_send.remove(to_send.len() - 1); } - let _ = crate::send(&tx, to_send.as_str()); + let _ = send(&tx, to_send.as_str()); Ok(()) } @@ -401,7 +401,7 @@ impl Server { to_send.remove(to_send.len() - 1); } - let _ = crate::send(&tx, to_send.as_str()); + let _ = send(&tx, to_send.as_str()); Ok(()) } @@ -443,7 +443,7 @@ impl Server { message.pop(); - let _ = crate::send(&tx, &message); + let _ = send(&tx, &message); Ok(()) } @@ -463,7 +463,7 @@ impl Server { let mut admin_guard = self.admin.write().await; *admin_guard = Some(addr.to_string().parse()?); - let _ = crate::send(&tx, "ADMIN:AUTH:ACK"); + let _ = send(&tx, "ADMIN:AUTH:ACK"); Ok(()) } @@ -540,15 +540,15 @@ impl Server { drop(clients_guard); let mut tourney = match self.tournament_type.as_str() { - "round_robin" => crate::tournaments::round_robin::RoundRobin::new(&ready_players), - &_ => crate::tournaments::round_robin::RoundRobin::new(&ready_players), + "round_robin" => 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))); - let _ = crate::send(&tx, "TOURNAMENT:START:ACK"); + let _ = send(&tx, "TOURNAMENT:START:ACK"); Ok(()) } @@ -570,7 +570,7 @@ impl Server { tourney.write().await.cancel(&self).await; *tournament_guard = None; - let _ = crate::send(&tx, "TOURNAMENT:CANCEL:ACK"); + let _ = send(&tx, "TOURNAMENT:CANCEL:ACK"); Ok(()) } @@ -593,7 +593,7 @@ impl Server { ) -> Result<(), anyhow::Error> { let mut msg = "GET:MOVE_WAIT:".to_string(); msg += &(*self.waiting_timeout.read().await as f64 / 1000f64).to_string(); - let _ = crate::send(&tx, &msg); + let _ = send(&tx, &msg); Ok(()) } @@ -608,7 +608,7 @@ impl Server { } else { msg += status.to_string().as_str(); } - let _ = crate::send(&tx, &msg); + let _ = send(&tx, &msg); Ok(()) } @@ -619,7 +619,7 @@ impl Server { let demo_mode = *self.demo_mode.read().await; let mut msg = "GET:DEMO_MODE:".to_string(); msg += demo_mode.to_string().as_str(); - let _ = crate::send(&tx, &msg); + let _ = send(&tx, &msg); Ok(()) } @@ -638,7 +638,7 @@ impl Server { } *self.demo_mode.write().await = demo_mode; - let _ = crate::send(&tx, "SET:DEMO_MODE:ACK"); + let _ = send(&tx, "SET:DEMO_MODE:ACK"); Ok(()) } From cba7dd66f38c50c2b2c250c3cb21ab7e29c48a97 Mon Sep 17 00:00:00 2001 From: Joshua Higgins Date: Wed, 31 Dec 2025 16:42:51 -0500 Subject: [PATCH 3/3] misc: fix casing to be consistent --- src/server.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/server.rs b/src/server.rs index 83fc541..bb802d0 100644 --- a/src/server.rs +++ b/src/server.rs @@ -540,7 +540,7 @@ impl Server { drop(clients_guard); let mut tourney = match self.tournament_type.as_str() { - "round_robin" => RoundRobin::new(&ready_players), + "RoundRobin" => RoundRobin::new(&ready_players), &_ => RoundRobin::new(&ready_players), }; tourney.start(&self).await;