Allow clients to create new rooms to play

This commit is contained in:
2023-02-19 18:03:01 +01:00
parent 831a5003c3
commit 7c8330465f
6 changed files with 302 additions and 5 deletions

129
board-server/src/room.rs Normal file
View File

@@ -0,0 +1,129 @@
use crate::player::Player;
use board_network::protocol::{ClientMessage, ServerMessage};
use futures::channel::mpsc::{UnboundedReceiver, UnboundedSender};
use futures::StreamExt;
use rand::distributions::{Alphanumeric, DistString};
use std::collections::hash_map::Entry;
use std::collections::HashMap;
use std::net::SocketAddr;
use std::sync::{Arc, Mutex};
type TaggedClientMessage = (SocketAddr, ClientMessage);
#[derive(Debug, Default)]
pub struct Room {
pub name: String,
pub connections: HashMap<SocketAddr, usize>,
pub players: Vec<Player>,
}
impl Room {
pub fn add_player(
&mut self,
addr: SocketAddr,
player_name: String,
tx: UnboundedSender<ServerMessage>,
) -> anyhow::Result<()> {
// If the player name matches an existing player, but with a dropped connection,
// then replace this player.
let mut player_index = None;
for (i, p) in self.players.iter().enumerate() {
if p.name == player_name && p.ws.is_none() {
player_index = Some(i);
break;
}
}
if let Some(i) = player_index {
// Reclaim the player's spot
self.broadcast(ServerMessage::PlayerReconnected(i));
self.players[i].ws = Some(tx.clone());
} else {
self.broadcast(ServerMessage::PlayerConnected(player_name.clone()));
player_index = Some(self.players.len());
self.players.push(Player {
name: player_name,
score: 0,
ws: Some(tx.clone()),
});
}
let player_index = player_index.expect("A player index should have been attributed");
self.connections.insert(addr, player_index);
// Send the player the current state of the room
tx.unbounded_send(ServerMessage::JoinedRoom {
room_name: self.name.clone(),
players: self
.players
.iter()
.map(|p| (p.name.clone(), p.score, p.ws.is_some()))
.collect(),
})?;
Ok(())
}
pub fn on_message(&mut self, addr: SocketAddr, msg: ClientMessage) -> bool {
match msg {
ClientMessage::Disconnected => self.on_client_disconnected(addr),
ClientMessage::CreateRoom(_) | ClientMessage::JoinRoom(_, _) => {
eprintln!("[{}] Illegal client message {:?}", self.name, msg);
}
}
!self.connections.is_empty()
}
fn on_client_disconnected(&mut self, addr: SocketAddr) {
if let Some(p) = self.connections.remove(&addr) {
self.players[p].ws = None;
self.broadcast(ServerMessage::PlayerDisconnected(p));
}
}
fn broadcast(&self, s: ServerMessage) {
for c in self.connections.values() {
if let Some(ws) = &self.players[*c].ws {
if let Err(e) = ws.unbounded_send(s.clone()) {
eprintln!(
"[{}] Failed to send broadcast to {}: {}",
self.name, self.players[*c].name, e
);
}
}
}
}
}
type RoomPtr = Arc<Mutex<Room>>;
#[derive(Clone)]
pub struct RoomHandle {
pub write: UnboundedSender<TaggedClientMessage>,
pub room: RoomPtr,
}
impl RoomHandle {
pub async fn run_room(&mut self, mut read: UnboundedReceiver<TaggedClientMessage>) {
while let Some((addr, msg)) = read.next().await {
if !self.room.lock().unwrap().on_message(addr, msg) {
break;
}
}
}
}
pub type Rooms = HashMap<String, RoomHandle>;
pub fn generate_room_name(rooms: &mut Rooms, room: RoomHandle) -> String {
let mut rng = rand::thread_rng();
loop {
let name = Alphanumeric.sample_string(&mut rng, 5);
if let Entry::Vacant(v) = rooms.entry(name.clone()) {
room.room.lock().unwrap().name = name.clone();
v.insert(room);
return name;
}
}
}