diff --git a/board-frontend/src/remote_view.rs b/board-frontend/src/remote_view.rs index 23eabfd..ee01749 100644 --- a/board-frontend/src/remote_view.rs +++ b/board-frontend/src/remote_view.rs @@ -1,13 +1,17 @@ use crate::app::BoardView; +use crate::hand_view::HandView; use crate::types::SelectedTile; use board_network::protocol::{ClientMessage, ServerMessage}; -use board_shared::game::Game; +use board_shared::board::Board; +use board_shared::game::Hand; +use board_shared::position::Position2d; use futures::stream::{SplitSink, SplitStream}; use futures::{SinkExt, StreamExt}; use gloo_dialogs::alert; use gloo_net::websocket::futures::WebSocket; use gloo_net::websocket::Message; use std::cell::RefCell; +use std::ops::Deref; use std::rc::Rc; use yew::platform::spawn_local; use yew::prelude::*; @@ -56,8 +60,11 @@ pub fn remote_game_view( let selected_tile = use_state(|| SelectedTile::None); let is_started = use_state(|| false); let current_player_turn = use_state(|| 0); - let game = use_state(Game::default); + let board = use_state(Board::default); + let in_hand = use_state(Hand::default); { + let board = board.clone(); + let in_hand = in_hand.clone(); let player_name = player_name.clone(); let room_name = room_name.clone(); let write = write.clone(); @@ -96,6 +103,15 @@ pub fn remote_game_view( current_player_turn.set(player_id); is_started.set(true); } + Ok(ServerMessage::SyncHand(hand)) => { + in_hand + .set(Hand::new(hand.iter().map(|&x| x.into()).collect())); + } + Ok(ServerMessage::TilePlaced(pos, tile)) => { + let mut changed = board.deref().clone(); + changed.set(pos.x, pos.y, tile.into()); + board.set(changed); + } r => { alert(&format!("{r:?}")); } @@ -109,6 +125,28 @@ pub fn remote_game_view( ); } + let on_tile_select = { + let selected_tile = selected_tile.clone(); + Callback::from(move |idx| { + selected_tile.set(SelectedTile::InHand(idx)); + }) + }; + let on_tile_click = { + let selected_tile = selected_tile.clone(); + let write = write.clone(); + Callback::from(move |pos: (usize, usize)| { + let position: Position2d = pos.into(); + match *selected_tile { + SelectedTile::None => {} + SelectedTile::InHand(idx) => { + send_client_message!(write, ClientMessage::TileUse(position.into(), idx)); + } + SelectedTile::Equals => { + send_client_message!(write, ClientMessage::TilePlaceEqual(position.into())) + } + } + }) + }; let on_validate_click = { let write = write.clone(); Callback::from(move |_| { @@ -130,7 +168,8 @@ pub fn remote_game_view( html! {

{"Remote Game"}

- + +
if *is_started { diff --git a/board-network/src/protocol.rs b/board-network/src/protocol.rs index ceac38c..47c88d7 100644 --- a/board-network/src/protocol.rs +++ b/board-network/src/protocol.rs @@ -20,6 +20,8 @@ pub enum ClientMessage { /// /// The server will validate the move and answer with a TilePlaced if the message is valid. TileUse(Position2dRef, usize), + /// Try to place an equal sign on the board. + TilePlaceEqual(Position2dRef), /// Try to remove a tile from the board to add it to the hand. TileTake(Position2dRef), /// Get the server to validate the current player moves. diff --git a/board-network/src/types.rs b/board-network/src/types.rs index f326d02..8ca4ffc 100644 --- a/board-network/src/types.rs +++ b/board-network/src/types.rs @@ -12,21 +12,21 @@ pub struct BoardRef { pub height: usize, } -#[derive(Clone, Debug, Serialize, Deserialize)] +#[derive(Clone, Copy, Debug, Serialize, Deserialize)] pub enum TileRef { Digit(DigitRef), Operator(OperatorRef), Equals, } -#[derive(Clone, Debug, Serialize, Deserialize)] +#[derive(Clone, Copy, Debug, Serialize, Deserialize)] pub struct DigitRef { pub value: i8, pub has_left_parenthesis: bool, pub has_right_parenthesis: bool, } -#[derive(Clone, Debug, Serialize, Deserialize)] +#[derive(Clone, Copy, Debug, Serialize, Deserialize)] pub enum OperatorRef { Add, Subtract, @@ -34,7 +34,7 @@ pub enum OperatorRef { Divide, } -#[derive(Clone, Debug, Serialize, Deserialize)] +#[derive(Clone, Copy, Debug, Serialize, Deserialize)] pub struct Position2dRef { pub x: usize, pub y: usize, @@ -63,6 +63,16 @@ impl From for TileRef { } } +impl From for Tile { + fn from(value: TileRef) -> Self { + match value { + TileRef::Digit(digit) => Tile::Digit(digit.into()), + TileRef::Operator(operator) => Tile::Operator(operator.into()), + TileRef::Equals => Tile::Equals, + } + } +} + impl From for DigitRef { fn from(value: Digit) -> Self { Self { @@ -73,6 +83,16 @@ impl From for DigitRef { } } +impl From for Digit { + fn from(value: DigitRef) -> Self { + Self { + value: value.value, + has_left_parenthesis: value.has_left_parenthesis, + has_right_parenthesis: value.has_right_parenthesis, + } + } +} + impl From for OperatorRef { fn from(value: Operator) -> Self { match value { @@ -84,6 +104,17 @@ impl From for OperatorRef { } } +impl From for Operator { + fn from(value: OperatorRef) -> Self { + match value { + OperatorRef::Add => Operator::Add, + OperatorRef::Subtract => Operator::Subtract, + OperatorRef::Multiply => Operator::Multiply, + OperatorRef::Divide => Operator::Divide, + } + } +} + impl From for Position2dRef { fn from(value: Position2d) -> Self { Self { diff --git a/board-server/src/room.rs b/board-server/src/room.rs index 81de110..8d2f8ba 100644 --- a/board-server/src/room.rs +++ b/board-server/src/room.rs @@ -1,10 +1,12 @@ use crate::player::Player; use board_network::protocol::{ClientMessage, ServerMessage}; +use board_network::types::TileRef; use board_shared::board::Board; use board_shared::deck::RngDeck; use board_shared::expr::is_valid_guess; use board_shared::game::Hand; use board_shared::position::Position2d; +use board_shared::tile::Tile; use futures::channel::mpsc::{UnboundedReceiver, UnboundedSender}; use futures::StreamExt; use rand::distributions::{Alphanumeric, DistString}; @@ -108,6 +110,13 @@ impl Room { } } } + ClientMessage::TilePlaceEqual(pos) => { + if let Some(p) = self.connections.get(&addr) { + if *p == self.active_player { + self.on_tile_place(pos.into(), Tile::Equals); + } + } + } ClientMessage::Validate => { if let Some(p) = self.connections.get(&addr) { if *p == self.active_player { @@ -134,16 +143,31 @@ impl Room { .expect("Not enough tiles in deck"); } + for i in 0..self.players.len() { + self.sync_hand(i); + } + self.broadcast(ServerMessage::PlayerTurn(self.active_player)); } fn on_tile_use(&mut self, pos: Position2d, tile_idx: usize) { let hand = &mut self.players[self.active_player].hand; if let Some(tile) = hand.remove(tile_idx) { - self.board.set(pos.x, pos.y, tile); + self.on_tile_place(pos, tile); + self.sync_hand(self.active_player); + } else { + self.send( + self.active_player, + ServerMessage::TurnRejected("Invalid tile index".to_string()), + ); } } + fn on_tile_place(&mut self, pos: Position2d, tile: Tile) { + self.board.set(pos.x, pos.y, tile); + self.broadcast(ServerMessage::TilePlaced(pos.into(), tile.into())); + } + fn on_validate(&mut self) { let diff = self.board.difference(&self.validated_board); if !Board::has_alignment(&diff) { @@ -180,6 +204,7 @@ impl Room { self.broadcast(ServerMessage::TileRemoved(pos.into())); } self.board = self.validated_board.clone(); + self.sync_hand(self.active_player); } fn on_client_disconnected(&mut self, addr: SocketAddr) { @@ -194,26 +219,39 @@ impl Room { 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 + "[{}] Failed to send broadcast to {}: {e}", + self.name, self.players[*c].name ); } } } } - fn send(&self, i: usize, s: ServerMessage) { - if let Some(p) = self.players[i].ws.as_ref() { + fn send(&self, player_id: usize, s: ServerMessage) { + if let Some(p) = self.players[player_id].ws.as_ref() { if let Err(e) = p.unbounded_send(s) { eprintln!( - "[{}] Failed to send message to {}: {}", - self.name, self.players[i].name, e + "[{}] Failed to send message to {}: {e}", + self.name, self.players[player_id].name ); } } else { eprintln!("[{}] Tried sending message to inactive player", self.name); } } + + fn sync_hand(&mut self, player_id: usize) { + self.send( + player_id, + ServerMessage::SyncHand( + self.players[player_id] + .hand + .iter() + .map(|t| >::into(*t)) + .collect::>(), + ), + ); + } } type RoomPtr = Arc>;