Sync tile placements for all players
This commit is contained in:
@@ -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>>;
|
||||||
|
Reference in New Issue
Block a user