Sync tile placements for all players

main
Clément FRÉVILLE 2 years ago
parent 4fbe817f71
commit 126d5026cd

@ -1,13 +1,17 @@
use crate::app::BoardView; use crate::app::BoardView;
use crate::hand_view::HandView;
use crate::types::SelectedTile; use crate::types::SelectedTile;
use board_network::protocol::{ClientMessage, ServerMessage}; 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::stream::{SplitSink, SplitStream};
use futures::{SinkExt, StreamExt}; use futures::{SinkExt, StreamExt};
use gloo_dialogs::alert; use gloo_dialogs::alert;
use gloo_net::websocket::futures::WebSocket; use gloo_net::websocket::futures::WebSocket;
use gloo_net::websocket::Message; use gloo_net::websocket::Message;
use std::cell::RefCell; use std::cell::RefCell;
use std::ops::Deref;
use std::rc::Rc; use std::rc::Rc;
use yew::platform::spawn_local; use yew::platform::spawn_local;
use yew::prelude::*; use yew::prelude::*;
@ -56,8 +60,11 @@ pub fn remote_game_view(
let selected_tile = use_state(|| SelectedTile::None); let selected_tile = use_state(|| SelectedTile::None);
let is_started = use_state(|| false); let is_started = use_state(|| false);
let current_player_turn = use_state(|| 0); 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 player_name = player_name.clone();
let room_name = room_name.clone(); let room_name = room_name.clone();
let write = write.clone(); let write = write.clone();
@ -96,6 +103,15 @@ pub fn remote_game_view(
current_player_turn.set(player_id); current_player_turn.set(player_id);
is_started.set(true); 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 => { r => {
alert(&format!("{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 on_validate_click = {
let write = write.clone(); let write = write.clone();
Callback::from(move |_| { Callback::from(move |_| {
@ -130,7 +168,8 @@ pub fn remote_game_view(
html! { html! {
<main> <main>
<h1>{"Remote Game"}</h1> <h1>{"Remote Game"}</h1>
<BoardView board={game.board.clone()} on_click={Callback::from(|_| {})} /> <BoardView board={board.deref().clone()} on_click={on_tile_click} />
<HandView hand={in_hand.deref().clone()} on_select={on_tile_select} />
<div class="row"> <div class="row">
<button onclick={on_equals_select} class="button">{"="}</button> <button onclick={on_equals_select} class="button">{"="}</button>
if *is_started { if *is_started {

@ -20,6 +20,8 @@ pub enum ClientMessage {
/// ///
/// The server will validate the move and answer with a TilePlaced if the message is valid. /// The server will validate the move and answer with a TilePlaced if the message is valid.
TileUse(Position2dRef, usize), 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. /// Try to remove a tile from the board to add it to the hand.
TileTake(Position2dRef), TileTake(Position2dRef),
/// Get the server to validate the current player moves. /// Get the server to validate the current player moves.

@ -12,21 +12,21 @@ pub struct BoardRef {
pub height: usize, pub height: usize,
} }
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Copy, Debug, Serialize, Deserialize)]
pub enum TileRef { pub enum TileRef {
Digit(DigitRef), Digit(DigitRef),
Operator(OperatorRef), Operator(OperatorRef),
Equals, Equals,
} }
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Copy, Debug, Serialize, Deserialize)]
pub struct DigitRef { pub struct DigitRef {
pub value: i8, pub value: i8,
pub has_left_parenthesis: bool, pub has_left_parenthesis: bool,
pub has_right_parenthesis: bool, pub has_right_parenthesis: bool,
} }
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Copy, Debug, Serialize, Deserialize)]
pub enum OperatorRef { pub enum OperatorRef {
Add, Add,
Subtract, Subtract,
@ -34,7 +34,7 @@ pub enum OperatorRef {
Divide, Divide,
} }
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Copy, Debug, Serialize, Deserialize)]
pub struct Position2dRef { pub struct Position2dRef {
pub x: usize, pub x: usize,
pub y: usize, pub y: usize,
@ -63,6 +63,16 @@ impl From<Tile> for TileRef {
} }
} }
impl From<TileRef> 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<Digit> for DigitRef { impl From<Digit> for DigitRef {
fn from(value: Digit) -> Self { fn from(value: Digit) -> Self {
Self { Self {
@ -73,6 +83,16 @@ impl From<Digit> for DigitRef {
} }
} }
impl From<DigitRef> 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<Operator> for OperatorRef { impl From<Operator> for OperatorRef {
fn from(value: Operator) -> Self { fn from(value: Operator) -> Self {
match value { match value {
@ -84,6 +104,17 @@ impl From<Operator> for OperatorRef {
} }
} }
impl From<OperatorRef> 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<Position2d> for Position2dRef { impl From<Position2d> for Position2dRef {
fn from(value: Position2d) -> Self { fn from(value: Position2d) -> Self {
Self { Self {

@ -1,10 +1,12 @@
use crate::player::Player; use crate::player::Player;
use board_network::protocol::{ClientMessage, ServerMessage}; use board_network::protocol::{ClientMessage, ServerMessage};
use board_network::types::TileRef;
use board_shared::board::Board; use board_shared::board::Board;
use board_shared::deck::RngDeck; use board_shared::deck::RngDeck;
use board_shared::expr::is_valid_guess; use board_shared::expr::is_valid_guess;
use board_shared::game::Hand; use board_shared::game::Hand;
use board_shared::position::Position2d; use board_shared::position::Position2d;
use board_shared::tile::Tile;
use futures::channel::mpsc::{UnboundedReceiver, UnboundedSender}; use futures::channel::mpsc::{UnboundedReceiver, UnboundedSender};
use futures::StreamExt; use futures::StreamExt;
use rand::distributions::{Alphanumeric, DistString}; 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 => { ClientMessage::Validate => {
if let Some(p) = self.connections.get(&addr) { if let Some(p) = self.connections.get(&addr) {
if *p == self.active_player { if *p == self.active_player {
@ -134,16 +143,31 @@ impl Room {
.expect("Not enough tiles in deck"); .expect("Not enough tiles in deck");
} }
for i in 0..self.players.len() {
self.sync_hand(i);
}
self.broadcast(ServerMessage::PlayerTurn(self.active_player)); self.broadcast(ServerMessage::PlayerTurn(self.active_player));
} }
fn on_tile_use(&mut self, pos: Position2d, tile_idx: usize) { fn on_tile_use(&mut self, pos: Position2d, tile_idx: usize) {
let hand = &mut self.players[self.active_player].hand; let hand = &mut self.players[self.active_player].hand;
if let Some(tile) = hand.remove(tile_idx) { 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) { fn on_validate(&mut self) {
let diff = self.board.difference(&self.validated_board); let diff = self.board.difference(&self.validated_board);
if !Board::has_alignment(&diff) { if !Board::has_alignment(&diff) {
@ -180,6 +204,7 @@ impl Room {
self.broadcast(ServerMessage::TileRemoved(pos.into())); self.broadcast(ServerMessage::TileRemoved(pos.into()));
} }
self.board = self.validated_board.clone(); self.board = self.validated_board.clone();
self.sync_hand(self.active_player);
} }
fn on_client_disconnected(&mut self, addr: SocketAddr) { fn on_client_disconnected(&mut self, addr: SocketAddr) {
@ -194,26 +219,39 @@ impl Room {
if let Some(ws) = &self.players[*c].ws { if let Some(ws) = &self.players[*c].ws {
if let Err(e) = ws.unbounded_send(s.clone()) { if let Err(e) = ws.unbounded_send(s.clone()) {
eprintln!( eprintln!(
"[{}] Failed to send broadcast to {}: {}", "[{}] Failed to send broadcast to {}: {e}",
self.name, self.players[*c].name, e self.name, self.players[*c].name
); );
} }
} }
} }
} }
fn send(&self, i: usize, s: ServerMessage) { fn send(&self, player_id: usize, s: ServerMessage) {
if let Some(p) = self.players[i].ws.as_ref() { if let Some(p) = self.players[player_id].ws.as_ref() {
if let Err(e) = p.unbounded_send(s) { if let Err(e) = p.unbounded_send(s) {
eprintln!( eprintln!(
"[{}] Failed to send message to {}: {}", "[{}] Failed to send message to {}: {e}",
self.name, self.players[i].name, e self.name, self.players[player_id].name
); );
} }
} else { } else {
eprintln!("[{}] Tried sending message to inactive player", self.name); 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| <Tile as Into<TileRef>>::into(*t))
.collect::<Vec<_>>(),
),
);
}
} }
type RoomPtr = Arc<Mutex<Room>>; type RoomPtr = Arc<Mutex<Room>>;

Loading…
Cancel
Save