From 095bc8509c740d8bb91d036add59cf212ec433a1 Mon Sep 17 00:00:00 2001 From: clfreville2 Date: Sat, 25 Feb 2023 14:24:41 +0100 Subject: [PATCH] Integrate a remote playtesting front-end --- board-frontend/src/app.rs | 108 +++++++++++++++++++--- board-frontend/src/main.rs | 2 + board-frontend/src/remote_view.rs | 149 ++++++++++++++++++++++++++++++ board-frontend/src/types.rs | 5 + board-network/src/protocol.rs | 1 + board-server/src/room.rs | 1 + 6 files changed, 254 insertions(+), 12 deletions(-) create mode 100644 board-frontend/src/remote_view.rs create mode 100644 board-frontend/src/types.rs diff --git a/board-frontend/src/app.rs b/board-frontend/src/app.rs index b7af009..30cc4d6 100644 --- a/board-frontend/src/app.rs +++ b/board-frontend/src/app.rs @@ -1,28 +1,29 @@ use crate::hand_view::HandView; +use crate::remote_view::RemoteGameView; use crate::tile_view::PlacedTileView; +use crate::types::SelectedTile; use board_shared::board::Board; use board_shared::deck::RngDeck; use board_shared::expr::is_valid_guess; use board_shared::game::Game; use board_shared::position::Grid2d; use board_shared::tile::Tile; +use futures::StreamExt; use gloo_dialogs::alert; +use gloo_net::websocket::futures::WebSocket; +use std::cell::RefCell; +use std::rc::Rc; +use web_sys::HtmlInputElement; use yew::prelude::*; -enum SelectedTile { - InHand(usize), - Equals, - None, -} - #[derive(Properties, PartialEq)] -struct BoardViewProps { - board: Board, - on_click: Callback<(usize, usize)>, +pub struct BoardViewProps { + pub board: Board, + pub on_click: Callback<(usize, usize)>, } #[function_component(BoardView)] -fn board_view(BoardViewProps { board, on_click }: &BoardViewProps) -> Html { +pub fn board_view(BoardViewProps { board, on_click }: &BoardViewProps) -> Html { html! { { (0..board.height()).map(|y| html! { @@ -36,8 +37,8 @@ fn board_view(BoardViewProps { board, on_click }: &BoardViewProps) -> Html { } } -#[function_component(App)] -pub fn app() -> Html { +#[function_component(GameView)] +pub fn game_view() -> Html { let game = use_state(Game::default); let current_game = use_state(Game::default); @@ -128,3 +129,86 @@ pub fn app() -> Html { } } + +#[function_component(App)] +pub fn app() -> Html { + let remote = use_state(|| "ws://localhost:8081/ws".to_string()); + let player_name = use_state(|| None); + let room_name = use_state(|| None); + + let player_name_ref = use_node_ref(); + let room_name_ref = use_node_ref(); + let on_create_click = { + let player_name = player_name.clone(); + let player_name_ref = player_name_ref.clone(); + Callback::from(move |_| { + player_name.set(Some( + player_name_ref + .cast::() + .expect("player_name_ref is not attached to a input element") + .value(), + )); + }) + }; + let on_join_click = { + let player_name = player_name.clone(); + let player_name_ref = player_name_ref.clone(); + let room_name = room_name.clone(); + let room_name_ref = room_name_ref.clone(); + Callback::from(move |_| { + player_name.set(Some( + player_name_ref + .cast::() + .expect("player_name_ref is not attached to a input element") + .value(), + )); + room_name.set(Some( + room_name_ref + .cast::() + .expect("room_name_ref is not attached to a input element") + .value(), + )); + }) + }; + + if let Some(player_name) = (*player_name).as_ref() { + let ws = WebSocket::open(remote.as_ref()).unwrap(); + let (write, read) = ws.split(); + + return html! { +
+ +
+ }; + } + + html! { +
+
+ + +
+
+ + +
+ +
+
+ +
+
+
+ + +
+ +
+
+
+ } +} diff --git a/board-frontend/src/main.rs b/board-frontend/src/main.rs index 8a3496e..6414221 100644 --- a/board-frontend/src/main.rs +++ b/board-frontend/src/main.rs @@ -1,6 +1,8 @@ mod app; mod hand_view; +mod remote_view; mod tile_view; +mod types; use app::App; diff --git a/board-frontend/src/remote_view.rs b/board-frontend/src/remote_view.rs new file mode 100644 index 0000000..dd9c297 --- /dev/null +++ b/board-frontend/src/remote_view.rs @@ -0,0 +1,149 @@ +use crate::app::BoardView; +use crate::types::SelectedTile; +use board_network::protocol::{ClientMessage, ServerMessage}; +use board_shared::game::Game; +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::rc::Rc; +use yew::platform::spawn_local; +use yew::prelude::*; + +#[derive(Properties)] +pub struct RemoteGameViewProps { + pub write: Rc>>, + pub read: Rc>>, + pub player_name: String, + pub room_name: Option, +} + +impl PartialEq for RemoteGameViewProps { + fn eq(&self, other: &Self) -> bool { + Rc::ptr_eq(&self.write, &other.write) + && Rc::ptr_eq(&self.read, &other.read) + && self.player_name == other.player_name + && self.room_name == other.room_name + } +} + +#[function_component(RemoteGameView)] +pub fn remote_game_view( + RemoteGameViewProps { + write, + read, + player_name, + room_name, + }: &RemoteGameViewProps, +) -> Html { + macro_rules! send_client_message { + ($write:expr, $message:expr) => {{ + let write = $write.clone(); + spawn_local(async move { + write + .borrow_mut() + .send(Message::Text( + serde_json::to_string(&$message).expect("Cannot serialize"), + )) + .await + .unwrap(); + }); + }}; + } + + 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 player_name = player_name.clone(); + let room_name = room_name.clone(); + let write = write.clone(); + use_effect_with_deps( + move |_| { + send_client_message!( + write, + if let Some(room_name) = room_name { + ClientMessage::JoinRoom(room_name, player_name) + } else { + ClientMessage::CreateRoom(player_name) + } + ); + }, + (), + ); + + let is_started = is_started.clone(); + let current_player_turn = current_player_turn.clone(); + let read = read.clone(); + use_effect_with_deps( + move |_| { + spawn_local(async move { + while let Some(event) = read.borrow_mut().next().await { + if let Message::Text(msg) = event.unwrap() { + match serde_json::from_str::(&msg) { + Ok(ServerMessage::JoinedRoom { + room_name, + has_started, + .. + }) => { + alert(&format!("Joined room {}", room_name)); + is_started.set(has_started); + } + Ok(ServerMessage::PlayerTurn(player_id)) => { + current_player_turn.set(player_id); + is_started.set(true); + } + r => { + alert(&format!("{:?}", r)); + } + }; + } + } + }); + || {} + }, + (), + ); + } + + let on_validate_click = { + let write = write.clone(); + Callback::from(move |_| { + send_client_message!(write, ClientMessage::Validate); + }) + }; + let on_start_game_click = { + let write = write.clone(); + Callback::from(move |_| { + send_client_message!(write, ClientMessage::StartGame); + }) + }; + let on_equals_select = { + Callback::from(move |_| { + selected_tile.set(SelectedTile::Equals); + }) + }; + + html! { +
+

{"Remote Game"}

+ +
+ + if *is_started { + + } else { + + } +
+ if *is_started { +
+

{format!("Player {}'s turn", *current_player_turn)}

+
+ } +
+ } +} diff --git a/board-frontend/src/types.rs b/board-frontend/src/types.rs new file mode 100644 index 0000000..010adb1 --- /dev/null +++ b/board-frontend/src/types.rs @@ -0,0 +1,5 @@ +pub enum SelectedTile { + InHand(usize), + Equals, + None, +} diff --git a/board-network/src/protocol.rs b/board-network/src/protocol.rs index 5ce1e62..7ebc34f 100644 --- a/board-network/src/protocol.rs +++ b/board-network/src/protocol.rs @@ -35,6 +35,7 @@ pub enum ServerMessage { room_name: String, players: Vec<(String, u32, bool)>, active_player: usize, + has_started: bool, }, /// Notify that the room cannot be joined. JoinFailed(String), diff --git a/board-server/src/room.rs b/board-server/src/room.rs index ffbe549..569947d 100644 --- a/board-server/src/room.rs +++ b/board-server/src/room.rs @@ -72,6 +72,7 @@ impl Room { .map(|p| (p.name.clone(), p.score, p.ws.is_some())) .collect(), active_player: self.active_player, + has_started: self.has_started, })?; Ok(())