From 279671ca7f35132760fd0e569c0545104660c9bb Mon Sep 17 00:00:00 2001 From: Joshua Higgins Date: Wed, 15 Apr 2026 12:24:03 -0400 Subject: [PATCH] feat: custom bracket seeding --- .gitignore | 4 +- src/server.rs | 12 +++++ src/tournaments/knockout_bracket.rs | 77 +++++++++++++++++++++++++---- src/tournaments/round_robin.rs | 6 +-- 4 files changed, 85 insertions(+), 14 deletions(-) diff --git a/.gitignore b/.gitignore index aa9c373..7031d8e 100644 --- a/.gitignore +++ b/.gitignore @@ -27,4 +27,6 @@ target .env -node_modules/ \ No newline at end of file +node_modules/ + +bracket_pairings.txt \ No newline at end of file diff --git a/src/server.rs b/src/server.rs index 1164b92..66cba04 100644 --- a/src/server.rs +++ b/src/server.rs @@ -1160,6 +1160,10 @@ impl Server { } } } + "BRACKET_PAIRINGS" => { + let file = std::fs::read_to_string("bracket_pairings.txt").unwrap_or_default(); + msg += file.replace("\n", ",").as_str(); + } "MOVE_WAIT" => { let wait_time = *self.waiting_timeout.read().await as f64 / 1000f64; msg += wait_time.to_string().as_str(); @@ -1212,6 +1216,14 @@ impl Server { } *self.max_timeout.write().await = (max_time.unwrap() * 1000.0) as u64; } + "BRACKET_PAIRINGS" => { + let file = data_value.replace(",", "\n"); + let result = std::fs::write("bracket_pairings.txt", file); + match result { + Ok(_) => (), + Err(_) => return Err(anyhow::anyhow!("ERROR:INVALID:SET")), + } + } &_ => return Err(anyhow::anyhow!("ERROR:INVALID:SET")), } diff --git a/src/tournaments/knockout_bracket.rs b/src/tournaments/knockout_bracket.rs index 6eaebb8..8aaa8fd 100644 --- a/src/tournaments/knockout_bracket.rs +++ b/src/tournaments/knockout_bracket.rs @@ -17,6 +17,7 @@ pub struct KnockoutBracket { pub previous_wait: u64, pub completed: bool, pub started: bool, + pub skip_round_robin: bool, pub clients: Clients, pub matches: Matches, pub observers: Observers, @@ -31,7 +32,13 @@ impl KnockoutBracket { let mut i = 0; while i < self.pairings.len() { let player1_username = self.pairings[i].clone(); - let player2_username = self.pairings[i + 1].clone(); + let player2_username = self.pairings.get(i + 1); + + if player2_username.is_none() { + break; + } + + let player2_username = player2_username.unwrap().clone(); let match_id: u32 = gen_match_id(&self.matches).await; self.current_matches.push(match_id); @@ -98,8 +105,27 @@ impl KnockoutBracket { impl Tournament for KnockoutBracket { async fn new(ready_players: &[String], server: &Server) -> KnockoutBracket { let previous_wait = server.waiting_timeout.read().await.clone(); + let bracket_file = std::fs::read_to_string("bracket_pairings.txt").unwrap_or_default(); + let bracket_players = bracket_file.split('\n').collect::>(); + let mut skip_round_robin = + !bracket_players.is_empty() && bracket_players.len() == ready_players.len(); - *server.waiting_timeout.write().await = 5; + if skip_round_robin { + for player in bracket_players { + let mut player_match = false; + for ready_player in ready_players { + if player == ready_player { + player_match = true; + break; + } + } + + if !player_match { + skip_round_robin = false; + break; + } + } + } KnockoutBracket { blitz_round_robin: RoundRobin::new(ready_players, server).await, @@ -109,6 +135,7 @@ impl Tournament for KnockoutBracket { previous_wait, completed: false, started: false, + skip_round_robin, clients: server.clients.clone(), matches: server.matches.clone(), observers: server.observers.clone(), @@ -144,7 +171,11 @@ impl Tournament for KnockoutBracket { self.data.push(self.pairings.clone()); self.create_matches().await; - broadcast_message(&self.observers, &format!("GET:TOURNAMENT_DATA:{}", self.get_data().unwrap())).await; + broadcast_message( + &self.observers, + &format!("GET:TOURNAMENT_DATA:{}", self.get_data().unwrap()), + ) + .await; return; } @@ -155,13 +186,39 @@ impl Tournament for KnockoutBracket { } else { self.data.push(self.pairings.clone()); self.create_matches().await; - broadcast_message(&self.observers, &format!("GET:TOURNAMENT_DATA:{}", self.get_data().unwrap())).await; + broadcast_message( + &self.observers, + &format!("GET:TOURNAMENT_DATA:{}", self.get_data().unwrap()), + ) + .await; } - } + } } async fn start(&mut self, server: &Server) { - self.blitz_round_robin.start(server).await; + if self.skip_round_robin { + let bracket_file = std::fs::read_to_string("bracket_pairings.txt").unwrap_or_default(); + self.blitz_round_robin.completed = true; + self.started = true; + + let mut i = 0; + bracket_file.split('\n').into_iter().for_each(|line| { + self.players.push((line.to_string(), i, false)); + self.pairings.push(line.to_string()); + i += 1; + }); + + self.data.push(self.pairings.clone()); + self.create_matches().await; + broadcast_message( + &self.observers, + &format!("GET:TOURNAMENT_DATA:{}", self.get_data().unwrap()), + ) + .await; + } else { + *server.waiting_timeout.write().await = 5; + self.blitz_round_robin.start(server).await; + } } async fn cancel(&mut self, server: &Server) { @@ -329,7 +386,7 @@ impl Tournament for KnockoutBracket { fn get_players(&self) -> Vec { self.usernames.clone() } - + fn get_winner(&self) -> Option { if self.completed { return Some(self.pairings[0].clone()); @@ -337,12 +394,12 @@ impl Tournament for KnockoutBracket { None } - + fn get_data(&self) -> Option { if !self.started { return None; } - + let mut message = String::new(); for round in self.data.iter() { for player in round.iter() { @@ -352,7 +409,7 @@ impl Tournament for KnockoutBracket { message.pop(); message.push('|'); } - + if self.data.len() > 0 { message.pop(); } diff --git a/src/tournaments/round_robin.rs b/src/tournaments/round_robin.rs index 7e8f275..539a80f 100644 --- a/src/tournaments/round_robin.rs +++ b/src/tournaments/round_robin.rs @@ -40,7 +40,7 @@ impl RoundRobin { self.current_matches.push(match_id.clone()); let match_guard = new_match.read().await; - + let mut player1 = clients_guard.get(&player1_username.0).unwrap().write().await; player1.current_match = Some(match_id); player1.ready = false; @@ -212,7 +212,7 @@ impl Tournament for RoundRobin { fn get_players(&self) -> Vec { self.usernames.clone() } - + fn get_winner(&self) -> Option { if !self.is_completed() { return None; @@ -230,7 +230,7 @@ impl Tournament for RoundRobin { winner } - + fn get_data(&self) -> Option { None }