WIP - fix multiplayer bugs
This commit is contained in:
parent
f0552fee32
commit
6c5c1d7cb8
5 changed files with 138 additions and 67 deletions
|
@ -12,21 +12,20 @@ use serde::{Deserialize, Serialize};
|
|||
use std::collections::HashMap;
|
||||
use std::sync::{Arc, LazyLock, Weak};
|
||||
use tokio::select;
|
||||
use uuid::Uuid;
|
||||
use word_grid::api::{APIGame, ApiState, Update};
|
||||
use word_grid::dictionary::{Dictionary, DictionaryImpl};
|
||||
use word_grid::game::{Error, Game, PlayedTile};
|
||||
use word_grid::player_interaction::ai::Difficulty;
|
||||
use ws::frame::{CloseCode, CloseFrame};
|
||||
use ws::stream::DuplexStream;
|
||||
use ws::Message;
|
||||
|
||||
static DICTIONARY: LazyLock<DictionaryImpl> =
|
||||
LazyLock::new(|| DictionaryImpl::create_from_path("../resources/dictionary.csv"));
|
||||
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
#[derive(Clone, Debug, Serialize, PartialEq)]
|
||||
struct Player {
|
||||
name: String,
|
||||
id: Uuid,
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Debug)]
|
||||
|
@ -47,7 +46,7 @@ impl PartyInfo {
|
|||
struct Room {
|
||||
party_info: PartyInfo,
|
||||
game: Option<APIGame>,
|
||||
sender: Sender<InnerRoomMessage>,
|
||||
sender: Sender<(Option<Player>, InnerRoomMessage)>,
|
||||
}
|
||||
|
||||
impl Room {
|
||||
|
@ -120,7 +119,7 @@ type RoomMap = HashMap<String, Weak<RwLock<Room>>>;
|
|||
|
||||
async fn incoming_message_handler<E: std::fmt::Display>(
|
||||
message: Option<Result<Message, E>>,
|
||||
sender: &Sender<InnerRoomMessage>,
|
||||
sender: &Sender<(Option<Player>, InnerRoomMessage)>,
|
||||
player: &Player,
|
||||
room: &Arc<RwLock<Room>>,
|
||||
stream: &mut DuplexStream,
|
||||
|
@ -139,18 +138,14 @@ async fn incoming_message_handler<E: std::fmt::Display>(
|
|||
let message = message.to_text().unwrap();
|
||||
if message.len() == 0 {
|
||||
println!("Websocket closed");
|
||||
// TODO need to handle updating Players, etc.
|
||||
println!("Player {player:#?} is leaving");
|
||||
let mut room = room.write().await;
|
||||
if room.game.is_some() {
|
||||
unimplemented!("Need to handle mid-game someone leaving")
|
||||
}
|
||||
|
||||
let new_vec = room
|
||||
.party_info
|
||||
.players
|
||||
.iter()
|
||||
.filter(|p| !p.id.eq(&player.id))
|
||||
.filter(|p| !p.name.eq(&player.name))
|
||||
.map(|p| p.clone())
|
||||
.collect_vec();
|
||||
room.party_info.players = new_vec;
|
||||
|
@ -161,7 +156,9 @@ async fn incoming_message_handler<E: std::fmt::Display>(
|
|||
},
|
||||
info: room.party_info.clone(),
|
||||
};
|
||||
sender.send(InnerRoomMessage::PassThrough(event)).unwrap();
|
||||
sender
|
||||
.send((None, InnerRoomMessage::PassThrough(event)))
|
||||
.unwrap();
|
||||
|
||||
// TODO - handle case where there are no players left
|
||||
|
||||
|
@ -172,7 +169,7 @@ async fn incoming_message_handler<E: std::fmt::Display>(
|
|||
let message: ClientToServerMessage = serde_json::from_str(message).unwrap();
|
||||
|
||||
// TODO
|
||||
println!("Received {message:#?} from client {}", player.id);
|
||||
println!("Received {message:#?} from client {}", player.name);
|
||||
match message {
|
||||
ClientToServerMessage::Load => {
|
||||
return !game_load(player, None, room, stream).await
|
||||
|
@ -202,7 +199,9 @@ async fn incoming_message_handler<E: std::fmt::Display>(
|
|||
let game = APIGame::new(game);
|
||||
room.game = Some(game);
|
||||
|
||||
sender.send(InnerRoomMessage::GameEvent(None)).unwrap();
|
||||
sender
|
||||
.send((None, InnerRoomMessage::GameEvent(None)))
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
ClientToServerMessage::GameMove { r#move } => {
|
||||
|
@ -246,7 +245,7 @@ async fn incoming_message_handler<E: std::fmt::Display>(
|
|||
match result {
|
||||
Ok(event) => {
|
||||
sender
|
||||
.send(InnerRoomMessage::GameEvent(event.update))
|
||||
.send((None, InnerRoomMessage::GameEvent(event.update)))
|
||||
.unwrap();
|
||||
}
|
||||
Err(error) => {
|
||||
|
@ -265,7 +264,9 @@ async fn incoming_message_handler<E: std::fmt::Display>(
|
|||
event: RoomEvent::AIJoined { difficulty },
|
||||
info: room.party_info.clone(),
|
||||
};
|
||||
sender.send(InnerRoomMessage::PassThrough(event)).unwrap();
|
||||
sender
|
||||
.send((None, InnerRoomMessage::PassThrough(event)))
|
||||
.unwrap();
|
||||
}
|
||||
ClientToServerMessage::RemoveAI { index } => {
|
||||
let mut room = room.write().await;
|
||||
|
@ -275,7 +276,9 @@ async fn incoming_message_handler<E: std::fmt::Display>(
|
|||
event: RoomEvent::AILeft { index },
|
||||
info: room.party_info.clone(),
|
||||
};
|
||||
sender.send(InnerRoomMessage::PassThrough(event)).unwrap();
|
||||
sender
|
||||
.send((None, InnerRoomMessage::PassThrough(event)))
|
||||
.unwrap();
|
||||
} else {
|
||||
let event = ServerToClientMessage::Invalid {
|
||||
reason: format!("{index} is out of bounds"),
|
||||
|
@ -321,26 +324,48 @@ async fn game_load(
|
|||
}
|
||||
|
||||
async fn outgoing_message_handler<E: std::fmt::Debug>(
|
||||
message: Result<InnerRoomMessage, E>,
|
||||
_sender: &Sender<InnerRoomMessage>,
|
||||
message: Result<(Option<Player>, InnerRoomMessage), E>,
|
||||
_sender: &Sender<(Option<Player>, InnerRoomMessage)>,
|
||||
player: &Player,
|
||||
room: &Arc<RwLock<Room>>,
|
||||
stream: &mut DuplexStream,
|
||||
) -> bool {
|
||||
let message = message.unwrap();
|
||||
let (target, message) = message.unwrap();
|
||||
println!("Inner room message - {:#?}", message);
|
||||
return match message {
|
||||
InnerRoomMessage::PassThrough(event) => {
|
||||
let text = serde_json::to_string(&event).unwrap();
|
||||
let x = stream.send(text.into()).await;
|
||||
x.is_err()
|
||||
if target.is_none() || target.unwrap() == *player {
|
||||
match message {
|
||||
InnerRoomMessage::PassThrough(event) => {
|
||||
let text = serde_json::to_string(&event).unwrap();
|
||||
let x = stream.send(text.into()).await;
|
||||
x.is_err()
|
||||
}
|
||||
InnerRoomMessage::GameEvent(update) => !game_load(player, update, room, stream).await,
|
||||
}
|
||||
InnerRoomMessage::GameEvent(update) => !game_load(player, update, room, stream).await,
|
||||
};
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fn reject_websocket_with_reason(
|
||||
ws: ws::WebSocket,
|
||||
close_code: CloseCode,
|
||||
reason: String,
|
||||
) -> ws::Channel<'static> {
|
||||
ws.channel(move |mut stream| {
|
||||
Box::pin(async move {
|
||||
let closeframe = CloseFrame {
|
||||
code: close_code,
|
||||
reason: reason.into(),
|
||||
};
|
||||
let _ = stream.close(Some(closeframe)).await;
|
||||
|
||||
Ok(())
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
#[get("/room/<id>?<player_name>")]
|
||||
async fn chat(
|
||||
async fn room(
|
||||
id: &str,
|
||||
player_name: &str,
|
||||
ws: ws::WebSocket,
|
||||
|
@ -349,10 +374,8 @@ async fn chat(
|
|||
let mut rooms = rooms.lock().await;
|
||||
let room = rooms.get(id);
|
||||
|
||||
// TODO extract from cookies
|
||||
let player = Player {
|
||||
name: player_name.to_string(),
|
||||
id: Uuid::new_v4(),
|
||||
};
|
||||
|
||||
fn make_join_event(room: &Room, player: &Player) -> ServerToClientMessage {
|
||||
|
@ -364,40 +387,80 @@ async fn chat(
|
|||
}
|
||||
}
|
||||
|
||||
let (room, mut receiver, sender) = if room.is_none_or(|x| x.strong_count() == 0) {
|
||||
println!("Creating new room");
|
||||
let room = Room::new(player.clone());
|
||||
let event = make_join_event(&room, &player);
|
||||
|
||||
let sender = room.sender.clone();
|
||||
let receiver = sender.subscribe();
|
||||
|
||||
let arc = Arc::new(RwLock::new(room));
|
||||
|
||||
rooms.insert(id.to_string(), Arc::downgrade(&arc));
|
||||
sender.send(InnerRoomMessage::PassThrough(event)).unwrap();
|
||||
|
||||
(arc, receiver, sender)
|
||||
} else {
|
||||
let a = room.unwrap();
|
||||
let b = a.clone();
|
||||
let c = b.upgrade();
|
||||
let d = c.unwrap();
|
||||
|
||||
// need to add player to group
|
||||
let (sender, event) = {
|
||||
let mut room = d.write().await;
|
||||
room.party_info.players.push(player.clone());
|
||||
let sender = room.sender.clone();
|
||||
let (room, mut receiver, sender, trigger_game_load) =
|
||||
if room.is_none_or(|x| x.strong_count() == 0) {
|
||||
println!("Creating new room");
|
||||
let room = Room::new(player.clone());
|
||||
let event = make_join_event(&room, &player);
|
||||
|
||||
(sender, event)
|
||||
};
|
||||
let receiver = sender.subscribe();
|
||||
sender.send(InnerRoomMessage::PassThrough(event)).unwrap();
|
||||
let sender = room.sender.clone();
|
||||
let receiver = sender.subscribe();
|
||||
|
||||
(d, receiver, sender)
|
||||
};
|
||||
let arc = Arc::new(RwLock::new(room));
|
||||
|
||||
rooms.insert(id.to_string(), Arc::downgrade(&arc));
|
||||
sender
|
||||
.send((None, InnerRoomMessage::PassThrough(event)))
|
||||
.unwrap();
|
||||
|
||||
(arc, receiver, sender, false)
|
||||
} else {
|
||||
let a = room.unwrap();
|
||||
let b = a.clone();
|
||||
let c = b.upgrade();
|
||||
let d = c.unwrap();
|
||||
|
||||
let mut trigger_game_load = false;
|
||||
|
||||
// need to add player to group
|
||||
let (sender, event) = {
|
||||
let mut room = d.write().await;
|
||||
|
||||
// check if player is already in the room. If they are, don't allow the new connection
|
||||
if room.party_info.players.contains(&player) {
|
||||
return reject_websocket_with_reason(
|
||||
ws,
|
||||
CloseCode::Protocol,
|
||||
format!("{} is already in the room", player.name),
|
||||
);
|
||||
}
|
||||
|
||||
if let Some(game) = &room.game {
|
||||
if game
|
||||
.0
|
||||
.player_states
|
||||
.get_player_state(&player.name)
|
||||
.is_none()
|
||||
{
|
||||
// Game is in progress and our new player isn't a member
|
||||
return reject_websocket_with_reason(
|
||||
ws,
|
||||
CloseCode::Protocol,
|
||||
"The game is already in-progress".to_string(),
|
||||
);
|
||||
}
|
||||
trigger_game_load = true;
|
||||
}
|
||||
|
||||
room.party_info.players.push(player.clone());
|
||||
let sender = room.sender.clone();
|
||||
let event = make_join_event(&room, &player);
|
||||
|
||||
(sender, event)
|
||||
};
|
||||
let receiver = sender.subscribe();
|
||||
sender
|
||||
.send((None, InnerRoomMessage::PassThrough(event)))
|
||||
.unwrap();
|
||||
|
||||
(d, receiver, sender, trigger_game_load)
|
||||
};
|
||||
|
||||
if trigger_game_load {
|
||||
sender
|
||||
.send((Some(player.clone()), InnerRoomMessage::GameEvent(None)))
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
ws.channel(move |mut stream| {
|
||||
Box::pin(async move {
|
||||
|
@ -429,5 +492,5 @@ async fn chat(
|
|||
fn rocket() -> _ {
|
||||
rocket::build()
|
||||
.manage(Mutex::new(RoomMap::new()))
|
||||
.mount("/", routes![chat])
|
||||
.mount("/", routes![room])
|
||||
}
|
||||
|
|
|
@ -269,7 +269,7 @@ export function Game(props: {
|
|||
continue
|
||||
}
|
||||
|
||||
const letters = state.remaining_tiles.get(name);
|
||||
const letters = state.remaining_tiles[name];
|
||||
if (letters.length == 0) {
|
||||
logDispatch(<div>{name} has no remaining tiles.</div>);
|
||||
} else {
|
||||
|
|
|
@ -41,7 +41,7 @@ export enum CellType {
|
|||
Start = "Start",
|
||||
}
|
||||
|
||||
export type GameState = { type: "InProgress" } | { type: "Ended"; finisher: string | undefined; remaining_tiles: Map<string, Letter[]> };
|
||||
export type GameState = { type: "InProgress" } | { type: "Ended"; finisher: string | undefined; remaining_tiles: {[id: string]: Letter[]} };
|
||||
|
||||
export interface APIPlayer {
|
||||
name: string;
|
||||
|
|
|
@ -49,7 +49,7 @@ export function Menu(): React.JSX.Element {
|
|||
return <div><p>Connecting to {roomName}</p></div>
|
||||
} else if (partyInfo != null) {
|
||||
const players = partyInfo.players.map((x) => {
|
||||
return <li key={x.id}>{x.name}</li>;
|
||||
return <li key={x.name}>{x.name}</li>;
|
||||
});
|
||||
const ais = partyInfo.ais.map((x, i) => {
|
||||
return <li key={i}>
|
||||
|
@ -137,6 +137,14 @@ export function Menu(): React.JSX.Element {
|
|||
}
|
||||
console.log("Message from server ", event.data);
|
||||
});
|
||||
socket.addEventListener("close", (event) => {
|
||||
console.log({event});
|
||||
setSocket(null);
|
||||
setGame(null);
|
||||
if(event.reason != null && event.reason.length > 0) {
|
||||
alert(`Disconnected with reason "${event.reason} & code ${event.code}"`);
|
||||
}
|
||||
});
|
||||
setSocket(socket);
|
||||
}}>Connect
|
||||
</button>
|
||||
|
|
|
@ -119,7 +119,7 @@ export function mergeTrays(existing: PlayableLetterData[], newer: (LetterData |
|
|||
}
|
||||
|
||||
existing.filter((x) => {
|
||||
return x !== undefined && x !== null;
|
||||
return x != null;
|
||||
}).forEach((x) => {
|
||||
if (x.location === LocationType.TRAY) {
|
||||
freeSpots[x.index] = null;
|
||||
|
@ -138,9 +138,9 @@ export function mergeTrays(existing: PlayableLetterData[], newer: (LetterData |
|
|||
}
|
||||
|
||||
return newer.map((ld, i) => {
|
||||
if (ld !== undefined) {
|
||||
if (ld != null) {
|
||||
|
||||
if (existing[i] !== undefined && existing[i] !== null && existing[i].location === LocationType.TRAY) {
|
||||
if (existing[i] != null && existing[i].location === LocationType.TRAY) {
|
||||
ld["index"] = existing[i].index;
|
||||
} else {
|
||||
ld["index"] = firstNotNull();
|
||||
|
|
Loading…
Reference in a new issue