Multiplayer #1

Merged
joel merged 19 commits from multiplayer into main 2024-12-26 18:38:24 +00:00
4 changed files with 83 additions and 27 deletions
Showing only changes of commit 8c2714b0dd - Show all commits

View file

@ -15,7 +15,7 @@ use tokio::select;
use uuid::Uuid; use uuid::Uuid;
use word_grid::api::{APIGame, ApiState}; use word_grid::api::{APIGame, ApiState};
use word_grid::dictionary::{Dictionary, DictionaryImpl}; use word_grid::dictionary::{Dictionary, DictionaryImpl};
use word_grid::game::Game; use word_grid::game::{Error, Game, PlayedTile};
use word_grid::player_interaction::ai::Difficulty; use word_grid::player_interaction::ai::Difficulty;
use ws::stream::DuplexStream; use ws::stream::DuplexStream;
use ws::Message; use ws::Message;
@ -73,17 +73,29 @@ enum RoomEvent {
#[serde(tag = "type")] #[serde(tag = "type")]
enum ServerToClientMessage { enum ServerToClientMessage {
RoomChange { event: RoomEvent, info: PartyInfo }, RoomChange { event: RoomEvent, info: PartyInfo },
GameEvent { state: ApiState }, GameEvent { state: ApiState, committed: bool },
GameError { error: Error },
Invalid { reason: String }, Invalid { reason: String },
} }
#[derive(Deserialize, Debug)]
enum GameMove {
Pass,
Exchange {
tiles: Vec<bool>,
},
Play {
played_tiles: Vec<Option<PlayedTile>>,
commit_move: bool,
},
}
#[derive(Deserialize, Debug)] #[derive(Deserialize, Debug)]
#[serde(tag = "type")] #[serde(tag = "type")]
enum ClientToServerMessage { enum ClientToServerMessage {
RoomChange,
Load, Load,
StartGame, StartGame,
GameMove, GameMove { r#move: GameMove },
AddAI { difficulty: Difficulty }, AddAI { difficulty: Difficulty },
RemoveAI { index: usize }, RemoveAI { index: usize },
} }
@ -146,8 +158,9 @@ async fn incoming_message_handler<E: std::fmt::Display>(
// TODO // TODO
println!("Received {message:#?} from client {}", player.id); println!("Received {message:#?} from client {}", player.id);
match message { match message {
ClientToServerMessage::RoomChange => {} ClientToServerMessage::Load => {
ClientToServerMessage::Load => {} return !game_load(player, room, stream).await
}
ClientToServerMessage::StartGame => { ClientToServerMessage::StartGame => {
let mut room = room.write().await; let mut room = room.write().await;
if room.game.is_some() { if room.game.is_some() {
@ -176,7 +189,47 @@ async fn incoming_message_handler<E: std::fmt::Display>(
sender.send(InnerRoomMessage::GameEvent).unwrap(); sender.send(InnerRoomMessage::GameEvent).unwrap();
} }
} }
ClientToServerMessage::GameMove => {} ClientToServerMessage::GameMove { r#move } => {
let mut room = room.write().await;
if room.game.is_none() {
let event = ServerToClientMessage::Invalid {
reason: format!("Game hasn't been started yet"),
};
let event = serde_json::to_string(&event).unwrap();
let _ = stream.send(event.into()).await;
} else {
let game = room.game.as_mut().unwrap();
let result = match r#move {
GameMove::Pass => game.pass(&player.name),
GameMove::Exchange { tiles } => {
game.exchange(&player.name, tiles)
}
GameMove::Play {
played_tiles,
commit_move,
} => {
let result = game.play(&player.name, played_tiles, commit_move);
if result.is_ok() & !commit_move {
let event = ServerToClientMessage::GameEvent {state: result.unwrap(), committed: false};
let event = serde_json::to_string(&event).unwrap();
let _ = stream.send(event.into()).await;
return false;
}
result
},
};
match result {
Ok(_) => {
sender.send(InnerRoomMessage::GameEvent).unwrap();
}
Err(error) => {
let event = ServerToClientMessage::GameError { error };
let event = serde_json::to_string(&event).unwrap();
let _ = stream.send(event.into()).await;
}
}
}
}
ClientToServerMessage::AddAI { difficulty } => { ClientToServerMessage::AddAI { difficulty } => {
let mut room = room.write().await; let mut room = room.write().await;
room.party_info.ais.push(difficulty.clone()); room.party_info.ais.push(difficulty.clone());
@ -216,6 +269,19 @@ async fn incoming_message_handler<E: std::fmt::Display>(
false false
} }
async fn game_load(player: &Player, room: &Arc<RwLock<Room>>, stream: &mut DuplexStream) -> bool {
// The game object was modified; we need to trigger a load from this player's perspective
let mut room = room.write().await;
let state = room.game.as_mut().unwrap().load(&player.name).unwrap();
let event = ServerToClientMessage::GameEvent { state, committed: true };
let text = serde_json::to_string(&event).unwrap();
let x = stream.send(text.into()).await;
x.is_ok()
}
async fn outgoing_message_handler<E: std::fmt::Debug>( async fn outgoing_message_handler<E: std::fmt::Debug>(
message: Result<InnerRoomMessage, E>, message: Result<InnerRoomMessage, E>,
_sender: &Sender<InnerRoomMessage>, _sender: &Sender<InnerRoomMessage>,
@ -225,22 +291,14 @@ async fn outgoing_message_handler<E: std::fmt::Debug>(
) -> bool { ) -> bool {
let message = message.unwrap(); let message = message.unwrap();
println!("Inner room message - {:#?}", message); println!("Inner room message - {:#?}", message);
let message = match message { return match message {
InnerRoomMessage::PassThrough(event) => serde_json::to_string(&event).unwrap(), InnerRoomMessage::PassThrough(event) => {
InnerRoomMessage::GameEvent => { let text = serde_json::to_string(&event).unwrap();
// The game object was modified; we need to trigger a load from this player's perspective let x = stream.send(text.into()).await;
let mut room = room.write().await; x.is_err()
let state = room.game.as_mut().unwrap().load(&player.name).unwrap();
let event = ServerToClientMessage::GameEvent { state };
serde_json::to_string(&event).unwrap()
} }
InnerRoomMessage::GameEvent => !game_load(player, room, stream).await,
}; };
let _ = stream.send(message.into()).await;
false
} }
#[get("/room/<id>?<player_name>")] #[get("/room/<id>?<player_name>")]

View file

@ -46,9 +46,7 @@ impl WasmAPI {
let tray_tile_locations: Vec<Option<PlayedTile>> = let tray_tile_locations: Vec<Option<PlayedTile>> =
serde_wasm_bindgen::from_value(tray_tile_locations).unwrap(); serde_wasm_bindgen::from_value(tray_tile_locations).unwrap();
let result = self let result = self.0.play("Player", tray_tile_locations, commit_move);
.0
.play("Player".to_string(), tray_tile_locations, commit_move);
serde_wasm_bindgen::to_value(&result).unwrap() serde_wasm_bindgen::to_value(&result).unwrap()
} }

View file

@ -180,7 +180,7 @@ impl APIGame {
pub fn play( pub fn play(
&mut self, &mut self,
player: String, player: &str,
tray_tile_locations: Vec<Option<PlayedTile>>, tray_tile_locations: Vec<Option<PlayedTile>>,
commit_move: bool, commit_move: bool,
) -> Result<ApiState, Error> { ) -> Result<ApiState, Error> {
@ -190,7 +190,7 @@ impl APIGame {
let tray = self.0.player_states.get_tray(&player).unwrap().clone(); let tray = self.0.player_states.get_tray(&player).unwrap().clone();
let update = Update { let update = Update {
r#type: turn_action, r#type: turn_action,
player, player: player.to_string(),
}; };
if commit_move { if commit_move {
self.1.push(update.clone()) self.1.push(update.clone())

View file

@ -20,7 +20,7 @@ pub enum Player {
}, },
} }
#[derive(Debug)] #[derive(Debug, Clone)]
pub enum Error { pub enum Error {
InvalidPlayer(String), InvalidPlayer(String),
WrongTurn(String), WrongTurn(String),