feat: knockout brackets
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
const WebSocket = require("ws");
|
const WebSocket = require("ws");
|
||||||
const readline = require("readline");
|
const readline = require("readline");
|
||||||
|
|
||||||
const DEFAULT_URL = "wss://connect4.abunchofknowitalls.com";
|
const DEFAULT_URL = "ws://localhost:8080";
|
||||||
|
|
||||||
let ws;
|
let ws;
|
||||||
let pingInterval;
|
let pingInterval;
|
||||||
|
|||||||
147
src/server.rs
147
src/server.rs
@@ -429,6 +429,9 @@ impl Server {
|
|||||||
invalid = true;
|
invalid = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let player1 = current_match.player1.clone();
|
||||||
|
let player2 = current_match.player2.clone();
|
||||||
|
|
||||||
// Terminate games if a player makes an invalid move
|
// Terminate games if a player makes an invalid move
|
||||||
if invalid {
|
if invalid {
|
||||||
let current_match_id = current_match.id;
|
let current_match_id = current_match.id;
|
||||||
@@ -468,7 +471,16 @@ impl Server {
|
|||||||
|
|
||||||
let mut tournament_guard = self.tournament.write().await;
|
let mut tournament_guard = self.tournament.write().await;
|
||||||
let tourney = tournament_guard.as_mut().unwrap();
|
let tourney = tournament_guard.as_mut().unwrap();
|
||||||
tourney.write().await.inform_winner(opponent_username, false);
|
tourney
|
||||||
|
.write()
|
||||||
|
.await
|
||||||
|
.inform_winner(
|
||||||
|
opponent_username,
|
||||||
|
current_match_id,
|
||||||
|
player1.clone(),
|
||||||
|
player2.clone(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
drop(tournament_guard);
|
drop(tournament_guard);
|
||||||
|
|
||||||
self.matches.write().await.remove(¤t_match_id).unwrap();
|
self.matches.write().await.remove(¤t_match_id).unwrap();
|
||||||
@@ -539,32 +551,55 @@ impl Server {
|
|||||||
|
|
||||||
matches_guard.remove(¤t_match_id).unwrap();
|
matches_guard.remove(¤t_match_id).unwrap();
|
||||||
|
|
||||||
if self.tournament.read().await.is_some() && matches_guard.is_empty() {
|
if self.tournament.read().await.is_some() {
|
||||||
drop(matches_guard);
|
|
||||||
drop(clients_guard);
|
|
||||||
|
|
||||||
let mut tournament_guard = self.tournament.write().await;
|
let mut tournament_guard = self.tournament.write().await;
|
||||||
let tourney = tournament_guard.as_mut().unwrap();
|
let tourney = tournament_guard.as_mut().unwrap();
|
||||||
tourney.write().await.inform_winner(username.clone(), filled);
|
let winner = if filled {
|
||||||
tourney.write().await.next(&self).await;
|
String::new()
|
||||||
if tourney.read().await.is_completed() {
|
} else {
|
||||||
*tournament_guard = None;
|
username.clone()
|
||||||
}
|
};
|
||||||
} else if self.tournament.read().await.is_none() {
|
|
||||||
let _ = send(&tx, "TOURNAMENT:END");
|
tourney
|
||||||
if !is_demo_mode {
|
.write()
|
||||||
let opponent = opponent.clone().unwrap();
|
.await
|
||||||
let opponent = opponent.read().await;
|
.inform_winner(winner, current_match_id, player1.clone(), player2.clone())
|
||||||
let _ = send(&opponent.connection, "TOURNAMENT:END");
|
.await;
|
||||||
|
|
||||||
|
if matches_guard.is_empty() {
|
||||||
|
drop(matches_guard);
|
||||||
|
drop(clients_guard);
|
||||||
|
|
||||||
|
tourney.write().await.next(&self).await;
|
||||||
|
if tourney.read().await.is_completed() {
|
||||||
|
let tournament_players = tourney.read().await.get_players();
|
||||||
|
let clients_guard = self.clients.read().await;
|
||||||
|
|
||||||
|
for player in tournament_players {
|
||||||
|
let player = clients_guard.get(&player);
|
||||||
|
|
||||||
|
if player.is_none() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let player = player.unwrap().read().await;
|
||||||
|
let _ = send(&player.connection, "TOURNAMENT:END");
|
||||||
|
}
|
||||||
|
|
||||||
|
*tournament_guard = None;
|
||||||
|
|
||||||
|
self.broadcast("TOURNAMENT:END").await;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
let default_waiting_time = *self.waiting_timeout.read().await;
|
let set_waiting_time = *self.waiting_timeout.read().await;
|
||||||
let mut adjusted_waiting =
|
let mut variance = rand::rng().random_range(0..=(set_waiting_time / 200)) as i64;
|
||||||
default_waiting_time as i64 + (rand::rng().random_range(0..=50) - 25);
|
variance *= rand::rng().random_range(0..=2) - 1;
|
||||||
|
let mut adjusted_waiting = set_waiting_time as i64 + variance;
|
||||||
let current_move_time = Instant::now();
|
let current_move_time = Instant::now();
|
||||||
|
|
||||||
if current_match.ledger.is_empty() {
|
if current_match.ledger.is_empty() {
|
||||||
@@ -609,7 +644,7 @@ impl Server {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if demo_mode && no_winner {
|
if demo_mode && no_winner {
|
||||||
tokio::time::sleep(tokio::time::Duration::from_millis(default_waiting_time)).await;
|
tokio::time::sleep(tokio::time::Duration::from_millis(set_waiting_time)).await;
|
||||||
let _ = send(&client_tx, &format!("OPPONENT:{}", demo_move));
|
let _ = send(&client_tx, &format!("OPPONENT:{}", demo_move));
|
||||||
let observers_guard = observers.read().await;
|
let observers_guard = observers.read().await;
|
||||||
let msg = format!(
|
let msg = format!(
|
||||||
@@ -640,7 +675,8 @@ impl Server {
|
|||||||
tokio::time::sleep(tokio::time::Duration::from_millis(max_timeout as u64)).await;
|
tokio::time::sleep(tokio::time::Duration::from_millis(max_timeout as u64)).await;
|
||||||
|
|
||||||
let matches_guard = matches.read().await;
|
let matches_guard = matches.read().await;
|
||||||
let the_match = matches_guard.get(&match_id);
|
let the_match = matches_guard.get(&match_id).cloned();
|
||||||
|
drop(matches_guard);
|
||||||
if let Some(the_match) = the_match {
|
if let Some(the_match) = the_match {
|
||||||
let the_match = the_match.read().await;
|
let the_match = the_match.read().await;
|
||||||
if the_match.ledger.len() == ledger_size {
|
if the_match.ledger.len() == ledger_size {
|
||||||
@@ -670,7 +706,7 @@ impl Server {
|
|||||||
|
|
||||||
let mut tournament_guard = tournament.write().await;
|
let mut tournament_guard = tournament.write().await;
|
||||||
let tourney = tournament_guard.as_mut().unwrap();
|
let tourney = tournament_guard.as_mut().unwrap();
|
||||||
tourney.write().await.inform_winner(client_username, false);
|
tourney.write().await.inform_winner(client_username, match_id, player1, player2).await;
|
||||||
drop(tournament_guard);
|
drop(tournament_guard);
|
||||||
|
|
||||||
matches.write().await.remove(&match_id).unwrap();
|
matches.write().await.remove(&match_id).unwrap();
|
||||||
@@ -820,7 +856,23 @@ impl Server {
|
|||||||
let tourney = tournament_guard.as_mut().unwrap();
|
let tourney = tournament_guard.as_mut().unwrap();
|
||||||
tourney.write().await.next(&self).await;
|
tourney.write().await.next(&self).await;
|
||||||
if tourney.read().await.is_completed() {
|
if tourney.read().await.is_completed() {
|
||||||
|
let tournament_players = tourney.read().await.get_players();
|
||||||
|
let clients_guard = self.clients.read().await;
|
||||||
|
|
||||||
|
for player in tournament_players {
|
||||||
|
let player = clients_guard.get(&player);
|
||||||
|
|
||||||
|
if player.is_none() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let player = player.unwrap().read().await;
|
||||||
|
let _ = send(&player.connection, "TOURNAMENT:END");
|
||||||
|
}
|
||||||
|
|
||||||
*tournament_guard = None;
|
*tournament_guard = None;
|
||||||
|
|
||||||
|
self.broadcast("TOURNAMENT:END").await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -926,11 +978,36 @@ impl Server {
|
|||||||
if self.tournament.read().await.is_some() {
|
if self.tournament.read().await.is_some() {
|
||||||
let mut tournament_guard = self.tournament.write().await;
|
let mut tournament_guard = self.tournament.write().await;
|
||||||
let tourney = tournament_guard.as_mut().unwrap();
|
let tourney = tournament_guard.as_mut().unwrap();
|
||||||
tourney.write().await.inform_winner(winner_username, false);
|
tourney
|
||||||
|
.write()
|
||||||
|
.await
|
||||||
|
.inform_winner(
|
||||||
|
winner_username,
|
||||||
|
match_id,
|
||||||
|
the_match.player1.clone(),
|
||||||
|
the_match.player2.clone(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
if self.matches.read().await.is_empty() {
|
if self.matches.read().await.is_empty() {
|
||||||
tourney.write().await.next(&self).await;
|
tourney.write().await.next(&self).await;
|
||||||
if tourney.read().await.is_completed() {
|
if tourney.read().await.is_completed() {
|
||||||
|
let tournament_players = tourney.read().await.get_players();
|
||||||
|
let clients_guard = self.clients.read().await;
|
||||||
|
|
||||||
|
for player in tournament_players {
|
||||||
|
let player = clients_guard.get(&player);
|
||||||
|
|
||||||
|
if player.is_none() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let player = player.unwrap().read().await;
|
||||||
|
let _ = send(&player.connection, "TOURNAMENT:END");
|
||||||
|
}
|
||||||
|
|
||||||
*tournament_guard = None;
|
*tournament_guard = None;
|
||||||
|
|
||||||
|
self.broadcast("TOURNAMENT:END").await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -968,14 +1045,26 @@ impl Server {
|
|||||||
|
|
||||||
drop(clients_guard);
|
drop(clients_guard);
|
||||||
|
|
||||||
let mut tourney = match tournament_type.as_str() {
|
let tourney: Option<Arc<RwLock<dyn Tournament + Send + Sync + 'static>>> =
|
||||||
"RoundRobin" => RoundRobin::new(&ready_players),
|
match tournament_type.as_str() {
|
||||||
&_ => RoundRobin::new(&ready_players),
|
"RoundRobin" => Some(Arc::new(RwLock::new(
|
||||||
};
|
RoundRobin::new(&ready_players, &self).await,
|
||||||
tourney.start(&self).await;
|
))),
|
||||||
|
"KnockoutBracket" => Some(Arc::new(RwLock::new(
|
||||||
|
KnockoutBracket::new(&ready_players, &self).await,
|
||||||
|
))),
|
||||||
|
&_ => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
if tourney.is_none() {
|
||||||
|
return Err(anyhow::anyhow!("ERROR:INVALID:TOURNAMENT"));
|
||||||
|
}
|
||||||
|
|
||||||
|
let tourney = tourney.unwrap();
|
||||||
|
tourney.write().await.start(&self).await;
|
||||||
|
|
||||||
let mut tournament_guard = self.tournament.write().await;
|
let mut tournament_guard = self.tournament.write().await;
|
||||||
*tournament_guard = Some(Arc::new(RwLock::new(tourney)));
|
*tournament_guard = Some(tourney);
|
||||||
|
|
||||||
// Clear any pending reservations when a tournament starts
|
// Clear any pending reservations when a tournament starts
|
||||||
self.reservations.write().await.clear();
|
self.reservations.write().await.clear();
|
||||||
|
|||||||
292
src/tournaments/knockout_bracket.rs
Normal file
292
src/tournaments/knockout_bracket.rs
Normal file
@@ -0,0 +1,292 @@
|
|||||||
|
use async_trait::async_trait;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
server::*,
|
||||||
|
tournaments::{RoundRobin, Tournament},
|
||||||
|
*,
|
||||||
|
};
|
||||||
|
|
||||||
|
type Score = u32;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct KnockoutBracket {
|
||||||
|
pub blitz_round_robin: RoundRobin,
|
||||||
|
pub players: Vec<(String, Score, bool)>,
|
||||||
|
pub pairings: Vec<String>,
|
||||||
|
pub current_matches: Vec<u32>,
|
||||||
|
pub previous_wait: u64,
|
||||||
|
pub completed: bool,
|
||||||
|
pub started: bool,
|
||||||
|
pub clients: Clients,
|
||||||
|
pub matches: Matches,
|
||||||
|
pub usernames: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl KnockoutBracket {
|
||||||
|
async fn create_matches(&mut self) {
|
||||||
|
let clients_guard = self.clients.read().await;
|
||||||
|
|
||||||
|
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 match_id: u32 = gen_match_id(&self.matches).await;
|
||||||
|
self.current_matches.push(match_id);
|
||||||
|
let new_match = Arc::new(RwLock::new(Match::new(
|
||||||
|
match_id,
|
||||||
|
player1_username.clone(),
|
||||||
|
player2_username.clone(),
|
||||||
|
false,
|
||||||
|
)));
|
||||||
|
|
||||||
|
let match_guard = new_match.read().await;
|
||||||
|
let mut player1 = clients_guard.get(&player1_username).unwrap().write().await;
|
||||||
|
|
||||||
|
player1.current_match = Some(match_id);
|
||||||
|
player1.ready = false;
|
||||||
|
|
||||||
|
if match_guard.player1 == player1_username {
|
||||||
|
player1.color = Color::Red;
|
||||||
|
let _ = send(&player1.connection, "GAME:START:1");
|
||||||
|
} else {
|
||||||
|
player1.color = Color::Yellow;
|
||||||
|
let _ = send(&player1.connection, "GAME:START:0");
|
||||||
|
}
|
||||||
|
|
||||||
|
drop(player1);
|
||||||
|
|
||||||
|
let mut player2 = clients_guard.get(&player2_username).unwrap().write().await;
|
||||||
|
|
||||||
|
player2.current_match = Some(match_id);
|
||||||
|
player2.ready = false;
|
||||||
|
|
||||||
|
if match_guard.player1 == player2_username {
|
||||||
|
player2.color = Color::Red;
|
||||||
|
let _ = send(&player2.connection, "GAME:START:1");
|
||||||
|
} else {
|
||||||
|
player2.color = Color::Yellow;
|
||||||
|
let _ = send(&player2.connection, "GAME:START:0");
|
||||||
|
}
|
||||||
|
|
||||||
|
drop(player2);
|
||||||
|
|
||||||
|
self.matches.write().await.insert(match_id, new_match.clone());
|
||||||
|
|
||||||
|
i += 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl Tournament for KnockoutBracket {
|
||||||
|
async fn new(ready_players: &[String], server: &Server) -> KnockoutBracket {
|
||||||
|
let previous_wait = server.waiting_timeout.read().await.clone();
|
||||||
|
|
||||||
|
*server.waiting_timeout.write().await = 5;
|
||||||
|
|
||||||
|
KnockoutBracket {
|
||||||
|
blitz_round_robin: RoundRobin::new(ready_players, server).await,
|
||||||
|
players: Vec::new(),
|
||||||
|
pairings: Vec::new(),
|
||||||
|
current_matches: Vec::new(),
|
||||||
|
previous_wait,
|
||||||
|
completed: false,
|
||||||
|
started: false,
|
||||||
|
clients: server.clients.clone(),
|
||||||
|
matches: server.matches.clone(),
|
||||||
|
usernames: ready_players.to_vec(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn next(&mut self, server: &Server) {
|
||||||
|
if self.completed {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if !self.started {
|
||||||
|
self.blitz_round_robin.next(server).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.blitz_round_robin.completed && !self.started {
|
||||||
|
*server.waiting_timeout.write().await = self.previous_wait;
|
||||||
|
|
||||||
|
let mut players = Vec::new();
|
||||||
|
for player in self.blitz_round_robin.players.values() {
|
||||||
|
players.push((player.0.clone(), player.1, false));
|
||||||
|
}
|
||||||
|
|
||||||
|
players.sort_by(|a, b| b.1.cmp(&a.1));
|
||||||
|
self.players = players;
|
||||||
|
|
||||||
|
for player in &self.players {
|
||||||
|
self.pairings.push(player.0.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
self.create_matches().await;
|
||||||
|
|
||||||
|
self.started = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.pairings.len() == 1 {
|
||||||
|
self.completed = true;
|
||||||
|
} else {
|
||||||
|
self.pairings.retain(|p| !p.is_empty());
|
||||||
|
self.create_matches().await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn start(&mut self, server: &Server) {
|
||||||
|
self.blitz_round_robin.start(server).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn cancel(&mut self, server: &Server) {
|
||||||
|
if !self.started {
|
||||||
|
self.blitz_round_robin.cancel(server).await;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for match_id in &self.current_matches {
|
||||||
|
server.terminate_match(*match_id).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
let clients_guard = server.clients.read().await;
|
||||||
|
for username in &self.players {
|
||||||
|
let client = clients_guard.get(&username.0).cloned();
|
||||||
|
if client.is_none() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let client = client.unwrap();
|
||||||
|
let client = client.read().await;
|
||||||
|
|
||||||
|
let _ = send(&client.connection, "TOURNAMENT:END");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn inform_winner(
|
||||||
|
&mut self,
|
||||||
|
winner: String,
|
||||||
|
match_id: u32,
|
||||||
|
player1: String,
|
||||||
|
player2: String,
|
||||||
|
) {
|
||||||
|
if !self.started {
|
||||||
|
self.blitz_round_robin.inform_winner(winner, match_id, player1, player2).await;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut winner = winner;
|
||||||
|
|
||||||
|
// there's a tie
|
||||||
|
if winner.is_empty() {
|
||||||
|
let mut player1_track = (String::new(), 0, false);
|
||||||
|
let mut player2_track = (String::new(), 0, false);
|
||||||
|
|
||||||
|
for player in self.players.iter_mut() {
|
||||||
|
if player.0 == player1 {
|
||||||
|
player1_track = player.clone();
|
||||||
|
} else if player.0 == player2 {
|
||||||
|
player2_track = player.clone();
|
||||||
|
}
|
||||||
|
|
||||||
|
if !player1_track.0.is_empty() && !player2_track.0.is_empty() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if player1_track.2 {
|
||||||
|
if player1_track.1 < player2_track.1 {
|
||||||
|
winner = player2_track.0.clone();
|
||||||
|
} else {
|
||||||
|
winner = player1_track.0.clone();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let match_id: u32 = gen_match_id(&self.matches).await;
|
||||||
|
self.current_matches.push(match_id);
|
||||||
|
let new_match = Arc::new(RwLock::new(Match::new(
|
||||||
|
match_id,
|
||||||
|
player1.clone(),
|
||||||
|
player2.clone(),
|
||||||
|
false,
|
||||||
|
)));
|
||||||
|
|
||||||
|
let match_guard = new_match.read().await;
|
||||||
|
let clients_guard = self.clients.read().await;
|
||||||
|
let mut player1 = clients_guard.get(&player1).unwrap().write().await;
|
||||||
|
|
||||||
|
player1.current_match = Some(match_id);
|
||||||
|
player1.ready = false;
|
||||||
|
|
||||||
|
if match_guard.player1 == player1.username {
|
||||||
|
player1.color = Color::Red;
|
||||||
|
let _ = send(&player1.connection, "GAME:START:1");
|
||||||
|
} else {
|
||||||
|
player1.color = Color::Yellow;
|
||||||
|
let _ = send(&player1.connection, "GAME:START:0");
|
||||||
|
}
|
||||||
|
|
||||||
|
drop(player1);
|
||||||
|
|
||||||
|
let mut player2 = clients_guard.get(&player2).unwrap().write().await;
|
||||||
|
|
||||||
|
player2.current_match = Some(match_id);
|
||||||
|
player2.ready = false;
|
||||||
|
|
||||||
|
if match_guard.player1 == player2.username {
|
||||||
|
player2.color = Color::Red;
|
||||||
|
let _ = send(&player2.connection, "GAME:START:1");
|
||||||
|
} else {
|
||||||
|
player2.color = Color::Yellow;
|
||||||
|
let _ = send(&player2.connection, "GAME:START:0");
|
||||||
|
}
|
||||||
|
|
||||||
|
drop(player2);
|
||||||
|
|
||||||
|
self.current_matches.push(match_id);
|
||||||
|
self.matches.write().await.insert(match_id, new_match.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut loser = String::new();
|
||||||
|
for i in 0..self.pairings.len() {
|
||||||
|
if self.pairings[i] == winner {
|
||||||
|
if i % 2 == 0 {
|
||||||
|
loser = self.pairings[i + 1].clone();
|
||||||
|
self.pairings[i + 1].clear();
|
||||||
|
} else {
|
||||||
|
loser = self.pairings[i - 1].clone();
|
||||||
|
self.pairings[i - 1].clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset tie tracking
|
||||||
|
for player in self.players.iter_mut() {
|
||||||
|
if player.0 == winner || player.0 == loser {
|
||||||
|
player.2 = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.current_matches.retain(|v| *v != match_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn contains_player(&self, username: String) -> bool {
|
||||||
|
self.usernames.contains(&username)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_completed(&self) -> bool {
|
||||||
|
self.completed
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_players(&self) -> Vec<String> {
|
||||||
|
self.usernames.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_type(&self) -> String {
|
||||||
|
"KnockoutBracket".to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,17 +4,26 @@ use crate::server::Server;
|
|||||||
|
|
||||||
pub mod round_robin;
|
pub mod round_robin;
|
||||||
pub use round_robin::RoundRobin;
|
pub use round_robin::RoundRobin;
|
||||||
|
pub mod knockout_bracket;
|
||||||
|
pub use knockout_bracket::KnockoutBracket;
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
pub trait Tournament {
|
pub trait Tournament {
|
||||||
fn new(ready_players: &[String]) -> Self
|
async fn new(ready_players: &[String], server: &Server) -> Self
|
||||||
where
|
where
|
||||||
Self: Sized;
|
Self: Sized;
|
||||||
async fn next(&mut self, server: &Server);
|
async fn next(&mut self, server: &Server);
|
||||||
async fn start(&mut self, server: &Server);
|
async fn start(&mut self, server: &Server);
|
||||||
async fn cancel(&mut self, server: &Server);
|
async fn cancel(&mut self, server: &Server);
|
||||||
fn inform_winner(&mut self, winner: String, is_tie: bool);
|
async fn inform_winner(
|
||||||
|
&mut self,
|
||||||
|
winner: String,
|
||||||
|
match_id: u32,
|
||||||
|
player1: String,
|
||||||
|
player2: String,
|
||||||
|
);
|
||||||
fn contains_player(&self, username: String) -> bool;
|
fn contains_player(&self, username: String) -> bool;
|
||||||
fn is_completed(&self) -> bool;
|
fn is_completed(&self) -> bool;
|
||||||
|
fn get_players(&self) -> Vec<String>;
|
||||||
fn get_type(&self) -> String;
|
fn get_type(&self) -> String;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
|
use tracing::info;
|
||||||
|
|
||||||
use crate::{server::Server, *};
|
use crate::{server::Server, *};
|
||||||
|
|
||||||
@@ -12,11 +13,13 @@ pub struct RoundRobin {
|
|||||||
pub players: HashMap<ID, (String, Score)>,
|
pub players: HashMap<ID, (String, Score)>,
|
||||||
pub top_half: Vec<ID>,
|
pub top_half: Vec<ID>,
|
||||||
pub bottom_half: Vec<ID>,
|
pub bottom_half: Vec<ID>,
|
||||||
pub is_completed: bool,
|
pub completed: bool,
|
||||||
|
pub current_matches: Vec<ID>,
|
||||||
|
pub usernames: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RoundRobin {
|
impl RoundRobin {
|
||||||
async fn create_matches(&self, clients: &Clients, matches: &Matches) {
|
async fn create_matches(&mut self, clients: &Clients, matches: &Matches) {
|
||||||
let clients_guard = clients.read().await;
|
let clients_guard = clients.read().await;
|
||||||
for (i, id) in self.top_half.iter().enumerate() {
|
for (i, id) in self.top_half.iter().enumerate() {
|
||||||
let player1_username = self.players.get(id).unwrap();
|
let player1_username = self.players.get(id).unwrap();
|
||||||
@@ -35,6 +38,8 @@ impl RoundRobin {
|
|||||||
false,
|
false,
|
||||||
)));
|
)));
|
||||||
|
|
||||||
|
self.current_matches.push(match_id.clone());
|
||||||
|
|
||||||
let match_guard = new_match.read().await;
|
let match_guard = new_match.read().await;
|
||||||
let mut player1 = clients_guard.get(&player1_username.0).unwrap().write().await;
|
let mut player1 = clients_guard.get(&player1_username.0).unwrap().write().await;
|
||||||
|
|
||||||
@@ -73,12 +78,14 @@ impl RoundRobin {
|
|||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl Tournament for RoundRobin {
|
impl Tournament for RoundRobin {
|
||||||
fn new(ready_players: &[String]) -> RoundRobin {
|
async fn new(ready_players: &[String], _: &Server) -> RoundRobin {
|
||||||
let mut result = RoundRobin {
|
let mut result = RoundRobin {
|
||||||
players: HashMap::new(),
|
players: HashMap::new(),
|
||||||
top_half: Vec::new(),
|
top_half: Vec::new(),
|
||||||
bottom_half: Vec::new(),
|
bottom_half: Vec::new(),
|
||||||
is_completed: false,
|
completed: false,
|
||||||
|
current_matches: Vec::new(),
|
||||||
|
usernames: ready_players.to_vec(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let size = ready_players.len();
|
let size = ready_players.len();
|
||||||
@@ -98,8 +105,10 @@ impl Tournament for RoundRobin {
|
|||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
fn inform_winner(&mut self, winner: String, is_tie: bool) {
|
async fn inform_winner(&mut self, winner: String, match_id: u32, _: String, _: String) {
|
||||||
if is_tie {
|
info!("RoundRobin: told winner was \"{}\"", winner);
|
||||||
|
|
||||||
|
if winner.is_empty() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,24 +118,17 @@ impl Tournament for RoundRobin {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
fn contains_player(&self, username: String) -> bool {
|
self.current_matches.retain(|id| !(*id == match_id));
|
||||||
for (_, (player_username, _)) in self.players.iter() {
|
|
||||||
if *player_username == username {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn next(&mut self, server: &Server) {
|
async fn next(&mut self, server: &Server) {
|
||||||
if self.is_completed {
|
if self.completed {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.top_half.len() <= 1 || self.bottom_half.is_empty() {
|
if self.top_half.len() <= 1 || self.bottom_half.is_empty() {
|
||||||
self.is_completed = true;
|
self.completed = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -138,20 +140,20 @@ impl Tournament for RoundRobin {
|
|||||||
|
|
||||||
let expected_bottom_start = self.top_half.len() as u32;
|
let expected_bottom_start = self.top_half.len() as u32;
|
||||||
if self.top_half[1] == 1 && self.bottom_half[0] == expected_bottom_start {
|
if self.top_half[1] == 1 && self.bottom_half[0] == expected_bottom_start {
|
||||||
self.is_completed = true;
|
self.completed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
let clients_guard = server.clients.read().await;
|
let clients_guard = server.clients.read().await;
|
||||||
let mut player_scores: Vec<(String, u32)> = Vec::new();
|
let mut player_scores: Vec<(String, u32)> = Vec::new();
|
||||||
for (_, player_addr) in self.players.iter() {
|
for (_, player_addr) in self.players.iter() {
|
||||||
let player = clients_guard.get(&player_addr.0).unwrap().read().await;
|
let player = clients_guard.get(&player_addr.0).unwrap().read().await;
|
||||||
let _ = send(&player.connection.clone(), "TOURNAMENT:END");
|
|
||||||
player_scores.push((player.username.clone(), player_addr.1));
|
player_scores.push((player.username.clone(), player_addr.1));
|
||||||
}
|
}
|
||||||
drop(clients_guard);
|
drop(clients_guard);
|
||||||
|
|
||||||
player_scores.sort_by(|a, b| b.1.cmp(&a.1));
|
player_scores.sort_by(|a, b| b.1.cmp(&a.1));
|
||||||
|
|
||||||
|
// Send scores
|
||||||
let mut message = "TOURNAMENT:SCORES:".to_string();
|
let mut message = "TOURNAMENT:SCORES:".to_string();
|
||||||
for (player, score) in player_scores.iter() {
|
for (player, score) in player_scores.iter() {
|
||||||
message.push_str(&format!("{},{}|", player, score))
|
message.push_str(&format!("{},{}|", player, score))
|
||||||
@@ -160,15 +162,7 @@ impl Tournament for RoundRobin {
|
|||||||
|
|
||||||
server.broadcast(&message).await;
|
server.broadcast(&message).await;
|
||||||
|
|
||||||
if self.is_completed() {
|
if !self.is_completed() {
|
||||||
// Send scores
|
|
||||||
let clients_guard = server.clients.read().await;
|
|
||||||
for (_, player_addr) in self.players.iter() {
|
|
||||||
let player = clients_guard.get(&player_addr.0).unwrap().read().await;
|
|
||||||
let _ = send(&player.connection.clone(), "TOURNAMENT:END");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Create next matches
|
|
||||||
self.create_matches(&server.clients, &server.matches).await;
|
self.create_matches(&server.clients, &server.matches).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -178,36 +172,32 @@ impl Tournament for RoundRobin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn cancel(&mut self, server: &Server) {
|
async fn cancel(&mut self, server: &Server) {
|
||||||
for (_, addr) in self.players.iter() {
|
for match_id in &self.current_matches {
|
||||||
let clients_guard = server.clients.read().await;
|
server.terminate_match(*match_id).await;
|
||||||
|
}
|
||||||
|
|
||||||
let client = clients_guard.get(&addr.0);
|
let clients_guard = server.clients.read().await;
|
||||||
|
for (_, (username, _)) in self.players.iter() {
|
||||||
|
let client = clients_guard.get(username);
|
||||||
if client.is_none() {
|
if client.is_none() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let client = client.unwrap().read().await;
|
let client = client.unwrap().read().await;
|
||||||
let client_connection = client.connection.clone();
|
let _ = send(&client.connection, "TOURNAMENT:END");
|
||||||
let client_ready = client.ready;
|
|
||||||
|
|
||||||
let match_id = client.current_match;
|
|
||||||
if match_id.is_none() {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
let match_id = match_id.unwrap();
|
|
||||||
|
|
||||||
drop(client);
|
|
||||||
drop(clients_guard);
|
|
||||||
|
|
||||||
server.terminate_match(match_id).await;
|
|
||||||
|
|
||||||
if !client_ready {
|
|
||||||
let _ = send(&client_connection, "TOURNAMENT:END");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn contains_player(&self, username: String) -> bool {
|
||||||
|
self.usernames.contains(&username)
|
||||||
|
}
|
||||||
|
|
||||||
fn is_completed(&self) -> bool {
|
fn is_completed(&self) -> bool {
|
||||||
self.is_completed
|
self.completed
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_players(&self) -> Vec<String> {
|
||||||
|
self.usernames.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_type(&self) -> String {
|
fn get_type(&self) -> String {
|
||||||
|
|||||||
Reference in New Issue
Block a user