Working multiplayer between frontend backend
WIP Need to set the URL and host the backend on the website.
This commit is contained in:
parent
8c84aae173
commit
89b9bf860b
5 changed files with 693 additions and 40 deletions
|
@ -71,21 +71,21 @@ fn handle_check_pairing(root: Value, tx: SyncSender<DBHandlerRequest>) -> Result
|
||||||
})
|
})
|
||||||
.is_err()
|
.is_err()
|
||||||
{
|
{
|
||||||
return Err("{\"type\":\"pairing_response\", \"status\":\"internal_error\"}".into());
|
return Err("{\"type\":\"pairing_status\", \"status\":\"internal_error\"}".into());
|
||||||
}
|
}
|
||||||
if let Ok((exists, is_paired, is_cyan)) = request_rx.recv_timeout(DB_REQUEST_TIMEOUT) {
|
if let Ok((exists, is_paired, is_cyan)) = request_rx.recv_timeout(DB_REQUEST_TIMEOUT) {
|
||||||
if !exists {
|
if !exists {
|
||||||
Err("{\"type\":\"pairing_response\", \"status\":\"unknown_id\"}".into())
|
Err("{\"type\":\"pairing_status\", \"status\":\"unknown_id\"}".into())
|
||||||
} else if is_paired {
|
} else if is_paired {
|
||||||
Ok(format!(
|
Ok(format!(
|
||||||
"{{\"type\":\"pairing_response\", \"status\":\"paired\", \"color\":\"{}\"}}",
|
"{{\"type\":\"pairing_status\", \"status\":\"paired\", \"color\":\"{}\"}}",
|
||||||
if is_cyan { "cyan" } else { "magenta" }
|
if is_cyan { "cyan" } else { "magenta" }
|
||||||
))
|
))
|
||||||
} else {
|
} else {
|
||||||
Ok("{\"type\"\"pairing_response\", \"status\":\"waiting\"}".into())
|
Ok("{\"type\":\"pairing_status\", \"status\":\"waiting\"}".into())
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Err("{\"type\":\"pairing_response\", \"status\":\"internal_error_timeout\"}".into())
|
Err("{\"type\":\"pairing_status\", \"status\":\"internal_error_timeout\"}".into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -85,14 +85,14 @@ then the back-end will respond with "too\_many\_players".
|
||||||
|
|
||||||
```
|
```
|
||||||
{
|
{
|
||||||
"type": "pairing_response",
|
"type": "pairing_status",
|
||||||
"status": "waiting", // or "unknown_id"
|
"status": "waiting", // or "unknown_id"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
```
|
```
|
||||||
{
|
{
|
||||||
"type": "pairing_response",
|
"type": "pairing_status",
|
||||||
"status": "paired",
|
"status": "paired",
|
||||||
"color": "magenta", // or "cyan"
|
"color": "magenta", // or "cyan"
|
||||||
}
|
}
|
||||||
|
|
|
@ -84,6 +84,15 @@ pub fn element_remove_class(document: &Document, id: &str, class: &str) -> Resul
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn element_has_class(document: &Document, id: &str, class: &str) -> Result<bool, String> {
|
||||||
|
let element = document
|
||||||
|
.get_element_by_id(id)
|
||||||
|
.ok_or_else(|| format!("Failed to get element with id \"{}\"", id))?;
|
||||||
|
let element_class: String = element.class_name();
|
||||||
|
|
||||||
|
Ok(element_class.contains(class))
|
||||||
|
}
|
||||||
|
|
||||||
pub fn create_json_request(target_url: &str, json_body: &str) -> Result<Request, String> {
|
pub fn create_json_request(target_url: &str, json_body: &str) -> Result<Request, String> {
|
||||||
let mut req_init: RequestInit = RequestInit::new();
|
let mut req_init: RequestInit = RequestInit::new();
|
||||||
req_init.body(Some(&JsValue::from_str(json_body)));
|
req_init.body(Some(&JsValue::from_str(json_body)));
|
||||||
|
@ -117,9 +126,15 @@ pub async fn send_to_backend(entries: HashMap<String, String>) -> Result<String,
|
||||||
for (key, value) in entries {
|
for (key, value) in entries {
|
||||||
send_json_string.push('"');
|
send_json_string.push('"');
|
||||||
send_json_string.push_str(&key);
|
send_json_string.push_str(&key);
|
||||||
send_json_string.push_str("\":\"");
|
send_json_string.push_str("\":");
|
||||||
|
if key == "id" || key == "position" {
|
||||||
send_json_string.push_str(&value);
|
send_json_string.push_str(&value);
|
||||||
send_json_string.push_str("\",");
|
} else {
|
||||||
|
send_json_string.push('"');
|
||||||
|
send_json_string.push_str(&value);
|
||||||
|
send_json_string.push('"');
|
||||||
|
}
|
||||||
|
send_json_string.push(',');
|
||||||
}
|
}
|
||||||
send_json_string.truncate(send_json_string.len() - 1);
|
send_json_string.truncate(send_json_string.len() - 1);
|
||||||
send_json_string.push('}');
|
send_json_string.push('}');
|
||||||
|
|
|
@ -33,6 +33,67 @@ impl GameState {
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_networked_paired(&mut self) {
|
||||||
|
if let GameState::NetworkedMultiplayer {
|
||||||
|
ref mut paired,
|
||||||
|
current_side: _,
|
||||||
|
current_turn: _,
|
||||||
|
} = self
|
||||||
|
{
|
||||||
|
*paired = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_networked_current_side(&self) -> Option<Turn> {
|
||||||
|
if let GameState::NetworkedMultiplayer {
|
||||||
|
paired,
|
||||||
|
current_side,
|
||||||
|
current_turn,
|
||||||
|
} = *self
|
||||||
|
{
|
||||||
|
current_side
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_networked_current_side(&mut self, side: Option<Turn>) {
|
||||||
|
if let GameState::NetworkedMultiplayer {
|
||||||
|
paired,
|
||||||
|
ref mut current_side,
|
||||||
|
current_turn,
|
||||||
|
} = self
|
||||||
|
{
|
||||||
|
*current_side = side;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_current_turn(&self) -> Turn {
|
||||||
|
if let GameState::SinglePlayer(turn, _) = *self {
|
||||||
|
turn
|
||||||
|
} else if let GameState::NetworkedMultiplayer {
|
||||||
|
paired: _,
|
||||||
|
current_side: _,
|
||||||
|
current_turn,
|
||||||
|
} = *self
|
||||||
|
{
|
||||||
|
current_turn
|
||||||
|
} else {
|
||||||
|
Turn::CyanPlayer
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_networked_current_turn(&mut self, turn: Turn) {
|
||||||
|
if let GameState::NetworkedMultiplayer {
|
||||||
|
paired: _,
|
||||||
|
current_side: _,
|
||||||
|
ref mut current_turn,
|
||||||
|
} = self
|
||||||
|
{
|
||||||
|
*current_turn = turn;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for GameState {
|
impl Default for GameState {
|
||||||
|
@ -338,8 +399,10 @@ pub fn board_from_string(board_string: String) -> BoardType {
|
||||||
for (idx, c) in board_string.chars().enumerate() {
|
for (idx, c) in board_string.chars().enumerate() {
|
||||||
match c {
|
match c {
|
||||||
'a' => board[idx].replace(BoardState::Empty),
|
'a' => board[idx].replace(BoardState::Empty),
|
||||||
'b' | 'd' | 'f' => board[idx].replace(BoardState::Cyan),
|
'b' | 'f' => board[idx].replace(BoardState::Cyan),
|
||||||
'c' | 'e' | 'g' => board[idx].replace(BoardState::Magenta),
|
'd' => board[idx].replace(BoardState::CyanWin),
|
||||||
|
'c' | 'g' => board[idx].replace(BoardState::Magenta),
|
||||||
|
'e' => board[idx].replace(BoardState::MagentaWin),
|
||||||
_ => BoardState::Empty,
|
_ => BoardState::Empty,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -434,6 +497,48 @@ pub struct PairingRequestResponse {
|
||||||
pub color: Option<String>,
|
pub color: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
pub struct PairingStatusResponse {
|
||||||
|
pub r#type: String,
|
||||||
|
pub status: String,
|
||||||
|
pub color: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
pub struct GameStateResponse {
|
||||||
|
pub r#type: String,
|
||||||
|
pub status: String,
|
||||||
|
pub board: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
pub struct PlaceTokenResponse {
|
||||||
|
pub r#type: String,
|
||||||
|
pub status: String,
|
||||||
|
pub board: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub enum NetworkedGameState {
|
||||||
|
CyanTurn,
|
||||||
|
MagentaTurn,
|
||||||
|
CyanWon,
|
||||||
|
MagentaWon,
|
||||||
|
Draw,
|
||||||
|
Disconnected,
|
||||||
|
InternalError,
|
||||||
|
NotPaired,
|
||||||
|
UnknownID,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub enum PlacedEnum {
|
||||||
|
Accepted,
|
||||||
|
Illegal,
|
||||||
|
NotYourTurn,
|
||||||
|
Other(NetworkedGameState),
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
|
@ -5,12 +5,14 @@ use crate::constants::{
|
||||||
};
|
};
|
||||||
use crate::game_logic::{check_win_draw, WinType};
|
use crate::game_logic::{check_win_draw, WinType};
|
||||||
use crate::html_helper::{
|
use crate::html_helper::{
|
||||||
append_to_info_text, create_json_request, element_append_class, element_remove_class,
|
append_to_info_text, create_json_request, element_append_class, element_has_class,
|
||||||
get_window_document, send_to_backend,
|
element_remove_class, get_window_document, send_to_backend,
|
||||||
};
|
};
|
||||||
use crate::random_helper::get_seeded_random;
|
use crate::random_helper::get_seeded_random;
|
||||||
use crate::state::{
|
use crate::state::{
|
||||||
BoardState, GameState, MainMenuMessage, PairingRequestResponse, SharedState, Turn,
|
board_from_string, BoardState, BoardType, GameState, GameStateResponse, MainMenuMessage,
|
||||||
|
NetworkedGameState, PairingRequestResponse, PairingStatusResponse, PlaceTokenResponse,
|
||||||
|
PlacedEnum, SharedState, Turn,
|
||||||
};
|
};
|
||||||
|
|
||||||
use std::cell::Cell;
|
use std::cell::Cell;
|
||||||
|
@ -19,7 +21,7 @@ use std::rc::Rc;
|
||||||
|
|
||||||
use js_sys::{Function, Promise};
|
use js_sys::{Function, Promise};
|
||||||
use wasm_bindgen::JsCast;
|
use wasm_bindgen::JsCast;
|
||||||
use web_sys::Response;
|
use web_sys::{Document, Response};
|
||||||
|
|
||||||
use serde_json::Value as SerdeJSONValue;
|
use serde_json::Value as SerdeJSONValue;
|
||||||
|
|
||||||
|
@ -121,20 +123,24 @@ impl Component for MainMenu {
|
||||||
mainmenu.set_class_name("hidden_menu");
|
mainmenu.set_class_name("hidden_menu");
|
||||||
mainmenu.set_inner_html("");
|
mainmenu.set_inner_html("");
|
||||||
|
|
||||||
let info_text_turn = document
|
|
||||||
.get_element_by_id("info_text1")
|
|
||||||
.expect("info_text1 should exist");
|
|
||||||
|
|
||||||
match shared.game_state.get() {
|
match shared.game_state.get() {
|
||||||
GameState::SinglePlayer(turn, _) => {
|
GameState::SinglePlayer(turn, _) => {
|
||||||
if shared.turn.get() == turn {
|
if shared.turn.get() == turn {
|
||||||
info_text_turn.set_inner_html(
|
append_to_info_text(
|
||||||
"<p><b class=\"cyan\">It is CyanPlayer's (player) Turn</b></p>",
|
&document,
|
||||||
);
|
"info_text1",
|
||||||
|
"<b class=\"cyan\">It is CyanPlayer's (player) Turn</b>",
|
||||||
|
1,
|
||||||
|
)
|
||||||
|
.ok();
|
||||||
} else {
|
} else {
|
||||||
info_text_turn.set_inner_html(
|
append_to_info_text(
|
||||||
"<p><b class=\"cyan\">It is CyanPlayer's (ai) Turn</b></p>",
|
&document,
|
||||||
);
|
"info_text1",
|
||||||
|
"<b class=\"cyan\">It is CyanPlayer's (ai) Turn</b>",
|
||||||
|
1,
|
||||||
|
)
|
||||||
|
.ok();
|
||||||
// AI player starts first
|
// AI player starts first
|
||||||
ctx.link()
|
ctx.link()
|
||||||
.get_parent()
|
.get_parent()
|
||||||
|
@ -149,6 +155,13 @@ impl Component for MainMenu {
|
||||||
current_side: _,
|
current_side: _,
|
||||||
current_turn: _,
|
current_turn: _,
|
||||||
} => {
|
} => {
|
||||||
|
append_to_info_text(
|
||||||
|
&document,
|
||||||
|
"info_text1",
|
||||||
|
"<b>Waiting to pair with another player...</b>",
|
||||||
|
1,
|
||||||
|
)
|
||||||
|
.ok();
|
||||||
// start the Wrapper Tick loop
|
// start the Wrapper Tick loop
|
||||||
ctx.link()
|
ctx.link()
|
||||||
.get_parent()
|
.get_parent()
|
||||||
|
@ -158,8 +171,13 @@ impl Component for MainMenu {
|
||||||
.send_message(WrapperMsg::BackendTick);
|
.send_message(WrapperMsg::BackendTick);
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
info_text_turn
|
append_to_info_text(
|
||||||
.set_inner_html("<p><b class=\"cyan\">It is CyanPlayer's Turn</b></p>");
|
&document,
|
||||||
|
"info_text1",
|
||||||
|
"<b class=\"cyan\">It is CyanPlayer's Turn</b>",
|
||||||
|
1,
|
||||||
|
)
|
||||||
|
.ok();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -222,13 +240,18 @@ impl Component for Slot {
|
||||||
current_side,
|
current_side,
|
||||||
current_turn,
|
current_turn,
|
||||||
} => {
|
} => {
|
||||||
|
if paired && current_side.is_some() {
|
||||||
|
if current_side.as_ref().unwrap() == ¤t_turn {
|
||||||
// notify Wrapper with picked slot
|
// notify Wrapper with picked slot
|
||||||
if let Some(p) = ctx.link().get_parent() {
|
if let Some(p) = ctx.link().get_parent() {
|
||||||
p.clone()
|
p.clone().downcast::<Wrapper>().send_message(
|
||||||
.downcast::<Wrapper>()
|
WrapperMsg::BackendRequest {
|
||||||
.send_message(WrapperMsg::BackendRequest {
|
|
||||||
place: ctx.props().idx,
|
place: ctx.props().idx,
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
GameState::PostGameResults(_) => return false,
|
GameState::PostGameResults(_) => return false,
|
||||||
|
@ -336,12 +359,215 @@ impl Wrapper {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_networked_player_type(&mut self, ctx: &Context<Self>) {
|
||||||
|
// make a request to get the pairing status
|
||||||
|
if self.player_id.is_none() {
|
||||||
|
log::warn!("Cannot request pairing status if ID is unknown");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let player_id: u32 = self.player_id.unwrap();
|
||||||
|
ctx.link().send_future(async move {
|
||||||
|
let mut json_entries = HashMap::new();
|
||||||
|
json_entries.insert("type".into(), "check_pairing".into());
|
||||||
|
json_entries.insert("id".into(), format!("{}", player_id));
|
||||||
|
|
||||||
|
let send_to_backend_result = send_to_backend(json_entries).await;
|
||||||
|
if let Err(e) = send_to_backend_result {
|
||||||
|
return WrapperMsg::BackendResponse(BREnum::Error(format!("{:?}", e)));
|
||||||
|
}
|
||||||
|
|
||||||
|
let request_result: Result<PairingStatusResponse, _> =
|
||||||
|
serde_json::from_str(&send_to_backend_result.unwrap());
|
||||||
|
if let Err(e) = request_result {
|
||||||
|
return WrapperMsg::BackendResponse(BREnum::Error(format!("{:?}", e)));
|
||||||
|
}
|
||||||
|
let response = request_result.unwrap();
|
||||||
|
|
||||||
|
if response.r#type != "pairing_status" {
|
||||||
|
return WrapperMsg::BackendResponse(BREnum::Error(
|
||||||
|
"Invalid response type when check_pairing".into(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
if response.status == "paired" && response.color.is_some() {
|
||||||
|
WrapperMsg::BackendResponse(BREnum::GotPairing(response.color.map(|string| {
|
||||||
|
if string == "cyan" {
|
||||||
|
Turn::CyanPlayer
|
||||||
|
} else {
|
||||||
|
Turn::MagentaPlayer
|
||||||
|
}
|
||||||
|
})))
|
||||||
|
} else {
|
||||||
|
WrapperMsg::BackendResponse(BREnum::Error("Not paired".into()))
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_game_status(&mut self, ctx: &Context<Self>) {
|
||||||
|
if self.player_id.is_none() {
|
||||||
|
log::warn!("Cannot request pairing status if ID is unknown");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let player_id: u32 = self.player_id.unwrap();
|
||||||
|
ctx.link().send_future(async move {
|
||||||
|
let mut json_entries = HashMap::new();
|
||||||
|
json_entries.insert("id".into(), format!("{}", player_id));
|
||||||
|
json_entries.insert("type".into(), "game_state".into());
|
||||||
|
|
||||||
|
let send_to_backend_result = send_to_backend(json_entries).await;
|
||||||
|
if let Err(e) = send_to_backend_result {
|
||||||
|
return WrapperMsg::BackendResponse(BREnum::Error(format!("{:?}", e)));
|
||||||
|
}
|
||||||
|
|
||||||
|
let response_result: Result<GameStateResponse, _> =
|
||||||
|
serde_json::from_str(&send_to_backend_result.unwrap());
|
||||||
|
if let Err(e) = response_result {
|
||||||
|
return WrapperMsg::BackendResponse(BREnum::Error(format!("{:?}", e)));
|
||||||
|
}
|
||||||
|
let response = response_result.unwrap();
|
||||||
|
|
||||||
|
if response.r#type != "game_state" {
|
||||||
|
return WrapperMsg::BackendResponse(BREnum::Error(
|
||||||
|
"Invalid state when checking game_state".into(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let networked_game_state = match response.status.as_str() {
|
||||||
|
"not_paired" => NetworkedGameState::NotPaired,
|
||||||
|
"unknown_id" => NetworkedGameState::UnknownID,
|
||||||
|
"cyan_turn" => NetworkedGameState::CyanTurn,
|
||||||
|
"magenta_turn" => NetworkedGameState::MagentaTurn,
|
||||||
|
"cyan_won" => NetworkedGameState::CyanWon,
|
||||||
|
"magenta_won" => NetworkedGameState::MagentaWon,
|
||||||
|
"draw" => NetworkedGameState::Draw,
|
||||||
|
"opponent_disconnected" => NetworkedGameState::Disconnected,
|
||||||
|
_ => NetworkedGameState::InternalError,
|
||||||
|
};
|
||||||
|
|
||||||
|
WrapperMsg::BackendResponse(BREnum::GotStatus(networked_game_state, response.board))
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn send_place_request(&mut self, ctx: &Context<Self>, placement: u8) {
|
||||||
|
if self.player_id.is_none() {
|
||||||
|
log::warn!("Cannot request pairing status if ID is unknown");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let player_id: u32 = self.player_id.unwrap();
|
||||||
|
ctx.link().send_future(async move {
|
||||||
|
let mut json_entries = HashMap::new();
|
||||||
|
json_entries.insert("id".into(), format!("{}", player_id));
|
||||||
|
json_entries.insert("position".into(), format!("{}", placement));
|
||||||
|
json_entries.insert("type".into(), "place_token".into());
|
||||||
|
|
||||||
|
let send_to_backend_result = send_to_backend(json_entries).await;
|
||||||
|
if let Err(e) = send_to_backend_result {
|
||||||
|
return WrapperMsg::BackendResponse(BREnum::Error(format!("{:?}", e)));
|
||||||
|
}
|
||||||
|
|
||||||
|
let response_result: Result<PlaceTokenResponse, _> =
|
||||||
|
serde_json::from_str(&send_to_backend_result.unwrap());
|
||||||
|
if let Err(e) = response_result {
|
||||||
|
return WrapperMsg::BackendResponse(BREnum::Error(format!("{:?}", e)));
|
||||||
|
}
|
||||||
|
let response = response_result.unwrap();
|
||||||
|
|
||||||
|
if response.r#type != "place_token" {
|
||||||
|
return WrapperMsg::BackendResponse(BREnum::Error(
|
||||||
|
"Invalid state when place_token".into(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let placed_enum = match response.status.as_str() {
|
||||||
|
"accepted" => PlacedEnum::Accepted,
|
||||||
|
"illegal" => PlacedEnum::Illegal,
|
||||||
|
"not_your_turn" => PlacedEnum::NotYourTurn,
|
||||||
|
"game_ended_draw" => PlacedEnum::Other(NetworkedGameState::Draw),
|
||||||
|
"game_ended_cyan_won" => PlacedEnum::Other(NetworkedGameState::CyanWon),
|
||||||
|
"game_ended_magenta_won" => PlacedEnum::Other(NetworkedGameState::MagentaWon),
|
||||||
|
"unknown_id" => PlacedEnum::Other(NetworkedGameState::UnknownID),
|
||||||
|
"not_paired_yet" => PlacedEnum::Other(NetworkedGameState::NotPaired),
|
||||||
|
_ => PlacedEnum::Other(NetworkedGameState::InternalError),
|
||||||
|
};
|
||||||
|
|
||||||
|
WrapperMsg::BackendResponse(BREnum::GotPlaced(placed_enum, response.board))
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_board_from_string(
|
||||||
|
&mut self,
|
||||||
|
shared: &SharedState,
|
||||||
|
document: &Document,
|
||||||
|
board_string: String,
|
||||||
|
) {
|
||||||
|
let board = board_from_string(board_string.clone());
|
||||||
|
for (idx, slot) in board.iter().enumerate() {
|
||||||
|
let was_open =
|
||||||
|
element_has_class(&document, &format!("slot{}", idx), "open").unwrap_or(false);
|
||||||
|
element_remove_class(&document, &format!("slot{}", idx), "open").ok();
|
||||||
|
element_remove_class(&document, &format!("slot{}", idx), "placed").ok();
|
||||||
|
element_remove_class(&document, &format!("slot{}", idx), "win").ok();
|
||||||
|
element_remove_class(&document, &format!("slot{}", idx), "cyan").ok();
|
||||||
|
element_remove_class(&document, &format!("slot{}", idx), "magenta").ok();
|
||||||
|
match slot.get() {
|
||||||
|
BoardState::Empty => {
|
||||||
|
element_append_class(&document, &format!("slot{}", idx), "open").ok();
|
||||||
|
}
|
||||||
|
BoardState::Cyan => {
|
||||||
|
element_append_class(&document, &format!("slot{}", idx), "cyan").ok();
|
||||||
|
}
|
||||||
|
BoardState::CyanWin => {
|
||||||
|
element_append_class(&document, &format!("slot{}", idx), "cyan").ok();
|
||||||
|
element_append_class(&document, &format!("slot{}", idx), "win").ok();
|
||||||
|
}
|
||||||
|
BoardState::Magenta => {
|
||||||
|
element_append_class(&document, &format!("slot{}", idx), "magenta").ok();
|
||||||
|
}
|
||||||
|
BoardState::MagentaWin => {
|
||||||
|
element_append_class(&document, &format!("slot{}", idx), "magenta").ok();
|
||||||
|
element_append_class(&document, &format!("slot{}", idx), "win").ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let char_at_idx = board_string
|
||||||
|
.chars()
|
||||||
|
.nth(idx)
|
||||||
|
.expect("idx into board_string should be in range");
|
||||||
|
if char_at_idx == 'f' {
|
||||||
|
element_append_class(&document, &format!("slot{}", idx), "placed").ok();
|
||||||
|
if was_open {
|
||||||
|
append_to_info_text(
|
||||||
|
&document,
|
||||||
|
"info_text0",
|
||||||
|
&format!("<b class=\"cyan\">CyanPlayer placed at {}</b>", idx),
|
||||||
|
INFO_TEXT_MAX_ITEMS,
|
||||||
|
)
|
||||||
|
.ok();
|
||||||
|
}
|
||||||
|
} else if char_at_idx == 'g' {
|
||||||
|
element_append_class(&document, &format!("slot{}", idx), "placed").ok();
|
||||||
|
if was_open {
|
||||||
|
append_to_info_text(
|
||||||
|
&document,
|
||||||
|
"info_text0",
|
||||||
|
&format!("<b class=\"magenta\">MagentaPlayer placed at {}</b>", idx),
|
||||||
|
INFO_TEXT_MAX_ITEMS,
|
||||||
|
)
|
||||||
|
.ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
shared.board[idx].set(slot.get());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
pub enum BREnum {
|
pub enum BREnum {
|
||||||
Error(String),
|
Error(String),
|
||||||
GotID(u32, Option<Turn>),
|
GotID(u32, Option<Turn>),
|
||||||
|
GotPairing(Option<Turn>),
|
||||||
|
GotStatus(NetworkedGameState, Option<String>),
|
||||||
|
GotPlaced(PlacedEnum, String),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
|
@ -476,7 +702,21 @@ impl Component for Wrapper {
|
||||||
current_side,
|
current_side,
|
||||||
current_turn,
|
current_turn,
|
||||||
} => {
|
} => {
|
||||||
// TODO
|
log::warn!(
|
||||||
|
"paired is {}, current_side is {:?}, current_turn is {:?}",
|
||||||
|
paired,
|
||||||
|
current_side,
|
||||||
|
current_turn
|
||||||
|
);
|
||||||
|
if paired {
|
||||||
|
if let Some(current_side) = current_side {
|
||||||
|
if current_side == current_turn {
|
||||||
|
self.place_request.replace(idx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log::warn!("Set place request to {:?}", self.place_request);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
GameState::PostGameResults(_) => (),
|
GameState::PostGameResults(_) => (),
|
||||||
}
|
}
|
||||||
|
@ -552,7 +792,7 @@ impl Component for Wrapper {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
format!(
|
format!(
|
||||||
"<b class=\"{}\">It is {}'s turn</b>",
|
"<b class=\"{}\">It is {}'s Turn</b>",
|
||||||
turn.get_color(),
|
turn.get_color(),
|
||||||
turn
|
turn
|
||||||
)
|
)
|
||||||
|
@ -585,8 +825,11 @@ impl Component for Wrapper {
|
||||||
} else {
|
} else {
|
||||||
// a player won
|
// a player won
|
||||||
let turn = Turn::from(endgame_state);
|
let turn = Turn::from(endgame_state);
|
||||||
let text_string =
|
let text_string = format!(
|
||||||
format!("<b class=\"{}\">{} has won</b>", turn.get_color(), turn);
|
"<b class=\"{}\">{} has won the game</b>",
|
||||||
|
turn.get_color(),
|
||||||
|
turn
|
||||||
|
);
|
||||||
let text_append_result = append_to_info_text(
|
let text_append_result = append_to_info_text(
|
||||||
&document,
|
&document,
|
||||||
"info_text0",
|
"info_text0",
|
||||||
|
@ -942,6 +1185,23 @@ impl Component for Wrapper {
|
||||||
WrapperMsg::BackendTick => {
|
WrapperMsg::BackendTick => {
|
||||||
if self.player_id.is_none() {
|
if self.player_id.is_none() {
|
||||||
self.get_networked_player_id(ctx);
|
self.get_networked_player_id(ctx);
|
||||||
|
} else if shared
|
||||||
|
.game_state
|
||||||
|
.get()
|
||||||
|
.get_networked_current_side()
|
||||||
|
.is_none()
|
||||||
|
{
|
||||||
|
self.get_networked_player_type(ctx);
|
||||||
|
} else if !matches!(shared.game_state.get(), GameState::PostGameResults(_)) {
|
||||||
|
if self.place_request.is_some() {
|
||||||
|
let placement = self.place_request.take().unwrap();
|
||||||
|
self.send_place_request(ctx, placement);
|
||||||
|
} else {
|
||||||
|
self.get_game_status(ctx);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.do_backend_tick = false;
|
||||||
|
log::warn!("Ended backend tick");
|
||||||
}
|
}
|
||||||
|
|
||||||
// repeat BackendTick handling while "connected" to backend
|
// repeat BackendTick handling while "connected" to backend
|
||||||
|
@ -954,11 +1214,284 @@ impl Component for Wrapper {
|
||||||
}
|
}
|
||||||
WrapperMsg::BackendResponse(br_enum) => match br_enum {
|
WrapperMsg::BackendResponse(br_enum) => match br_enum {
|
||||||
BREnum::Error(string) => {
|
BREnum::Error(string) => {
|
||||||
|
// TODO maybe suppress this for release builds
|
||||||
log::warn!("{}", string);
|
log::warn!("{}", string);
|
||||||
}
|
}
|
||||||
BREnum::GotID(id, turn_opt) => {
|
BREnum::GotID(id, turn_opt) => {
|
||||||
self.player_id = Some(id);
|
self.player_id = Some(id);
|
||||||
log::warn!("Got player id {}", id);
|
let mut game_state = shared.game_state.get();
|
||||||
|
game_state.set_networked_paired();
|
||||||
|
game_state.set_networked_current_side(turn_opt);
|
||||||
|
shared.game_state.set(game_state);
|
||||||
|
if let Some(turn_type) = turn_opt {
|
||||||
|
append_to_info_text(
|
||||||
|
&document,
|
||||||
|
"info_text0",
|
||||||
|
&format!(
|
||||||
|
"<b class=\"{}\">Paired with player, you are the {}</b>",
|
||||||
|
turn_type.get_color(),
|
||||||
|
turn_type
|
||||||
|
),
|
||||||
|
INFO_TEXT_MAX_ITEMS,
|
||||||
|
)
|
||||||
|
.ok();
|
||||||
|
append_to_info_text(
|
||||||
|
&document,
|
||||||
|
"info_text1",
|
||||||
|
"<b class=\"cyan\">It is CyanPlayer's Turn</b>",
|
||||||
|
1,
|
||||||
|
)
|
||||||
|
.ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
BREnum::GotPairing(turn_opt) => {
|
||||||
|
let mut game_state = shared.game_state.get();
|
||||||
|
game_state.set_networked_current_side(turn_opt);
|
||||||
|
shared.game_state.set(game_state);
|
||||||
|
if let Some(turn_type) = turn_opt {
|
||||||
|
append_to_info_text(
|
||||||
|
&document,
|
||||||
|
"info_text0",
|
||||||
|
&format!(
|
||||||
|
"<b class=\"{}\">Paired with player, you are the {}</b>",
|
||||||
|
turn_type.get_color(),
|
||||||
|
turn_type
|
||||||
|
),
|
||||||
|
INFO_TEXT_MAX_ITEMS,
|
||||||
|
)
|
||||||
|
.ok();
|
||||||
|
append_to_info_text(
|
||||||
|
&document,
|
||||||
|
"info_text1",
|
||||||
|
"<b class=\"cyan\">It is CyanPlayer's Turn</b>",
|
||||||
|
1,
|
||||||
|
)
|
||||||
|
.ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
BREnum::GotStatus(networked_game_state, board_opt) => {
|
||||||
|
if let Some(board_string) = board_opt {
|
||||||
|
self.update_board_from_string(&shared, &document, board_string);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut current_game_state = shared.game_state.get();
|
||||||
|
match networked_game_state {
|
||||||
|
NetworkedGameState::CyanTurn => {
|
||||||
|
if current_game_state.get_current_turn() != Turn::CyanPlayer {
|
||||||
|
current_game_state.set_networked_current_turn(Turn::CyanPlayer);
|
||||||
|
shared.game_state.set(current_game_state);
|
||||||
|
append_to_info_text(
|
||||||
|
&document,
|
||||||
|
"info_text1",
|
||||||
|
"<b class=\"cyan\">It is CyanPlayer's Turn</b>",
|
||||||
|
1,
|
||||||
|
)
|
||||||
|
.ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
NetworkedGameState::MagentaTurn => {
|
||||||
|
if current_game_state.get_current_turn() != Turn::MagentaPlayer {
|
||||||
|
current_game_state.set_networked_current_turn(Turn::MagentaPlayer);
|
||||||
|
shared.game_state.set(current_game_state);
|
||||||
|
append_to_info_text(
|
||||||
|
&document,
|
||||||
|
"info_text1",
|
||||||
|
"<b class=\"magenta\">It is MagentaPlayer's Turn</b>",
|
||||||
|
1,
|
||||||
|
)
|
||||||
|
.ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
NetworkedGameState::CyanWon => {
|
||||||
|
append_to_info_text(
|
||||||
|
&document,
|
||||||
|
"info_text1",
|
||||||
|
"<b class=\"cyan\">CyanPlayer won the game</b>",
|
||||||
|
1,
|
||||||
|
)
|
||||||
|
.ok();
|
||||||
|
shared
|
||||||
|
.game_state
|
||||||
|
.set(GameState::PostGameResults(BoardState::CyanWin));
|
||||||
|
self.do_backend_tick = false;
|
||||||
|
}
|
||||||
|
NetworkedGameState::MagentaWon => {
|
||||||
|
append_to_info_text(
|
||||||
|
&document,
|
||||||
|
"info_text1",
|
||||||
|
"<b class=\"magenta\">MagentaPlayer won the game</b>",
|
||||||
|
1,
|
||||||
|
)
|
||||||
|
.ok();
|
||||||
|
shared
|
||||||
|
.game_state
|
||||||
|
.set(GameState::PostGameResults(BoardState::MagentaWin));
|
||||||
|
self.do_backend_tick = false;
|
||||||
|
}
|
||||||
|
NetworkedGameState::Draw => {
|
||||||
|
append_to_info_text(
|
||||||
|
&document,
|
||||||
|
"info_text1",
|
||||||
|
"<b>The game ended in a draw</b>",
|
||||||
|
1,
|
||||||
|
)
|
||||||
|
.ok();
|
||||||
|
shared
|
||||||
|
.game_state
|
||||||
|
.set(GameState::PostGameResults(BoardState::Empty));
|
||||||
|
self.do_backend_tick = false;
|
||||||
|
}
|
||||||
|
NetworkedGameState::Disconnected => {
|
||||||
|
append_to_info_text(
|
||||||
|
&document,
|
||||||
|
"info_text1",
|
||||||
|
"<b>The opponent disconnected</b>",
|
||||||
|
1,
|
||||||
|
)
|
||||||
|
.ok();
|
||||||
|
shared
|
||||||
|
.game_state
|
||||||
|
.set(GameState::PostGameResults(BoardState::Empty));
|
||||||
|
self.do_backend_tick = false;
|
||||||
|
}
|
||||||
|
NetworkedGameState::InternalError => {
|
||||||
|
append_to_info_text(
|
||||||
|
&document,
|
||||||
|
"info_text1",
|
||||||
|
"<b>There was an internal error</b>",
|
||||||
|
1,
|
||||||
|
)
|
||||||
|
.ok();
|
||||||
|
shared
|
||||||
|
.game_state
|
||||||
|
.set(GameState::PostGameResults(BoardState::Empty));
|
||||||
|
self.do_backend_tick = false;
|
||||||
|
}
|
||||||
|
NetworkedGameState::NotPaired => (),
|
||||||
|
NetworkedGameState::UnknownID => {
|
||||||
|
append_to_info_text(
|
||||||
|
&document,
|
||||||
|
"info_text1",
|
||||||
|
"<b>The game has ended (disconnected?)</b>",
|
||||||
|
1,
|
||||||
|
)
|
||||||
|
.ok();
|
||||||
|
shared
|
||||||
|
.game_state
|
||||||
|
.set(GameState::PostGameResults(BoardState::Empty));
|
||||||
|
self.do_backend_tick = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
BREnum::GotPlaced(placed_status, board_string) => {
|
||||||
|
self.update_board_from_string(&shared, &document, board_string);
|
||||||
|
|
||||||
|
match placed_status {
|
||||||
|
PlacedEnum::Accepted => {
|
||||||
|
// noop, handled by update_board_from_string
|
||||||
|
}
|
||||||
|
PlacedEnum::Illegal => {
|
||||||
|
append_to_info_text(
|
||||||
|
&document,
|
||||||
|
"info_text0",
|
||||||
|
"<b>Cannot place a token there</b>",
|
||||||
|
INFO_TEXT_MAX_ITEMS,
|
||||||
|
)
|
||||||
|
.ok();
|
||||||
|
}
|
||||||
|
PlacedEnum::NotYourTurn => {
|
||||||
|
append_to_info_text(
|
||||||
|
&document,
|
||||||
|
"info_text0",
|
||||||
|
"<b>Cannot place a token, not your turn</b>",
|
||||||
|
INFO_TEXT_MAX_ITEMS,
|
||||||
|
)
|
||||||
|
.ok();
|
||||||
|
}
|
||||||
|
PlacedEnum::Other(networked_game_state) => match networked_game_state {
|
||||||
|
NetworkedGameState::CyanTurn => (),
|
||||||
|
NetworkedGameState::MagentaTurn => (),
|
||||||
|
NetworkedGameState::CyanWon => {
|
||||||
|
append_to_info_text(
|
||||||
|
&document,
|
||||||
|
"info_text1",
|
||||||
|
"<b class=\"cyan\">CyanPlayer has won the game</b>",
|
||||||
|
1,
|
||||||
|
)
|
||||||
|
.ok();
|
||||||
|
shared
|
||||||
|
.game_state
|
||||||
|
.set(GameState::PostGameResults(BoardState::CyanWin));
|
||||||
|
self.do_backend_tick = false;
|
||||||
|
}
|
||||||
|
NetworkedGameState::MagentaWon => {
|
||||||
|
append_to_info_text(
|
||||||
|
&document,
|
||||||
|
"info_text1",
|
||||||
|
"<b class=\"magenta\">MagentaPlayer has won the game</b>",
|
||||||
|
1,
|
||||||
|
)
|
||||||
|
.ok();
|
||||||
|
shared
|
||||||
|
.game_state
|
||||||
|
.set(GameState::PostGameResults(BoardState::MagentaWin));
|
||||||
|
self.do_backend_tick = false;
|
||||||
|
}
|
||||||
|
NetworkedGameState::Draw => {
|
||||||
|
append_to_info_text(
|
||||||
|
&document,
|
||||||
|
"info_text1",
|
||||||
|
"<b>The game ended in a draw</b>",
|
||||||
|
1,
|
||||||
|
)
|
||||||
|
.ok();
|
||||||
|
shared
|
||||||
|
.game_state
|
||||||
|
.set(GameState::PostGameResults(BoardState::Empty));
|
||||||
|
self.do_backend_tick = false;
|
||||||
|
}
|
||||||
|
NetworkedGameState::Disconnected => {
|
||||||
|
append_to_info_text(
|
||||||
|
&document,
|
||||||
|
"info_text1",
|
||||||
|
"<b>The opponent disconnected</b>",
|
||||||
|
1,
|
||||||
|
)
|
||||||
|
.ok();
|
||||||
|
shared
|
||||||
|
.game_state
|
||||||
|
.set(GameState::PostGameResults(BoardState::Empty));
|
||||||
|
self.do_backend_tick = false;
|
||||||
|
}
|
||||||
|
NetworkedGameState::InternalError => {
|
||||||
|
append_to_info_text(
|
||||||
|
&document,
|
||||||
|
"info_text1",
|
||||||
|
"<b>There was an internal error</b>",
|
||||||
|
1,
|
||||||
|
)
|
||||||
|
.ok();
|
||||||
|
shared
|
||||||
|
.game_state
|
||||||
|
.set(GameState::PostGameResults(BoardState::Empty));
|
||||||
|
self.do_backend_tick = false;
|
||||||
|
}
|
||||||
|
NetworkedGameState::NotPaired => (),
|
||||||
|
NetworkedGameState::UnknownID => {
|
||||||
|
append_to_info_text(
|
||||||
|
&document,
|
||||||
|
"info_text1",
|
||||||
|
"<b>The game has ended (disconnected?)</b>",
|
||||||
|
1,
|
||||||
|
)
|
||||||
|
.ok();
|
||||||
|
shared
|
||||||
|
.game_state
|
||||||
|
.set(GameState::PostGameResults(BoardState::Empty));
|
||||||
|
self.do_backend_tick = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
} // match (msg)
|
} // match (msg)
|
||||||
|
|
Loading…
Reference in a new issue