Working multiplayer between frontend backend

WIP Need to set the URL and host the backend on the website.
This commit is contained in:
Stephen Seo 2022-04-06 18:43:17 +09:00
parent 8c84aae173
commit 89b9bf860b
5 changed files with 693 additions and 40 deletions

View file

@ -71,21 +71,21 @@ fn handle_check_pairing(root: Value, tx: SyncSender<DBHandlerRequest>) -> Result
})
.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 !exists {
Err("{\"type\":\"pairing_response\", \"status\":\"unknown_id\"}".into())
Err("{\"type\":\"pairing_status\", \"status\":\"unknown_id\"}".into())
} else if is_paired {
Ok(format!(
"{{\"type\":\"pairing_response\", \"status\":\"paired\", \"color\":\"{}\"}}",
"{{\"type\":\"pairing_status\", \"status\":\"paired\", \"color\":\"{}\"}}",
if is_cyan { "cyan" } else { "magenta" }
))
} else {
Ok("{\"type\"\"pairing_response\", \"status\":\"waiting\"}".into())
Ok("{\"type\":\"pairing_status\", \"status\":\"waiting\"}".into())
}
} else {
Err("{\"type\":\"pairing_response\", \"status\":\"internal_error_timeout\"}".into())
Err("{\"type\":\"pairing_status\", \"status\":\"internal_error_timeout\"}".into())
}
}

View file

@ -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"
}
```
```
{
"type": "pairing_response",
"type": "pairing_status",
"status": "paired",
"color": "magenta", // or "cyan"
}

View file

@ -84,6 +84,15 @@ pub fn element_remove_class(document: &Document, id: &str, class: &str) -> Resul
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> {
let mut req_init: RequestInit = RequestInit::new();
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 {
send_json_string.push('"');
send_json_string.push_str(&key);
send_json_string.push_str("\":\"");
send_json_string.push_str(&value);
send_json_string.push_str("\",");
send_json_string.push_str("\":");
if key == "id" || key == "position" {
send_json_string.push_str(&value);
} 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.push('}');

View file

@ -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 {
@ -338,8 +399,10 @@ pub fn board_from_string(board_string: String) -> BoardType {
for (idx, c) in board_string.chars().enumerate() {
match c {
'a' => board[idx].replace(BoardState::Empty),
'b' | 'd' | 'f' => board[idx].replace(BoardState::Cyan),
'c' | 'e' | 'g' => board[idx].replace(BoardState::Magenta),
'b' | 'f' => board[idx].replace(BoardState::Cyan),
'd' => board[idx].replace(BoardState::CyanWin),
'c' | 'g' => board[idx].replace(BoardState::Magenta),
'e' => board[idx].replace(BoardState::MagentaWin),
_ => BoardState::Empty,
};
}
@ -434,6 +497,48 @@ pub struct PairingRequestResponse {
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)]
mod tests {
use super::*;

View file

@ -5,12 +5,14 @@ use crate::constants::{
};
use crate::game_logic::{check_win_draw, WinType};
use crate::html_helper::{
append_to_info_text, create_json_request, element_append_class, element_remove_class,
get_window_document, send_to_backend,
append_to_info_text, create_json_request, element_append_class, element_has_class,
element_remove_class, get_window_document, send_to_backend,
};
use crate::random_helper::get_seeded_random;
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;
@ -19,7 +21,7 @@ use std::rc::Rc;
use js_sys::{Function, Promise};
use wasm_bindgen::JsCast;
use web_sys::Response;
use web_sys::{Document, Response};
use serde_json::Value as SerdeJSONValue;
@ -121,20 +123,24 @@ impl Component for MainMenu {
mainmenu.set_class_name("hidden_menu");
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() {
GameState::SinglePlayer(turn, _) => {
if shared.turn.get() == turn {
info_text_turn.set_inner_html(
"<p><b class=\"cyan\">It is CyanPlayer's (player) Turn</b></p>",
);
append_to_info_text(
&document,
"info_text1",
"<b class=\"cyan\">It is CyanPlayer's (player) Turn</b>",
1,
)
.ok();
} else {
info_text_turn.set_inner_html(
"<p><b class=\"cyan\">It is CyanPlayer's (ai) Turn</b></p>",
);
append_to_info_text(
&document,
"info_text1",
"<b class=\"cyan\">It is CyanPlayer's (ai) Turn</b>",
1,
)
.ok();
// AI player starts first
ctx.link()
.get_parent()
@ -149,6 +155,13 @@ impl Component for MainMenu {
current_side: _,
current_turn: _,
} => {
append_to_info_text(
&document,
"info_text1",
"<b>Waiting to pair with another player...</b>",
1,
)
.ok();
// start the Wrapper Tick loop
ctx.link()
.get_parent()
@ -158,8 +171,13 @@ impl Component for MainMenu {
.send_message(WrapperMsg::BackendTick);
}
_ => {
info_text_turn
.set_inner_html("<p><b class=\"cyan\">It is CyanPlayer's Turn</b></p>");
append_to_info_text(
&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_turn,
} => {
// notify Wrapper with picked slot
if let Some(p) = ctx.link().get_parent() {
p.clone()
.downcast::<Wrapper>()
.send_message(WrapperMsg::BackendRequest {
place: ctx.props().idx,
});
if paired && current_side.is_some() {
if current_side.as_ref().unwrap() == &current_turn {
// notify Wrapper with picked slot
if let Some(p) = ctx.link().get_parent() {
p.clone().downcast::<Wrapper>().send_message(
WrapperMsg::BackendRequest {
place: ctx.props().idx,
},
);
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)]
pub enum BREnum {
Error(String),
GotID(u32, Option<Turn>),
GotPairing(Option<Turn>),
GotStatus(NetworkedGameState, Option<String>),
GotPlaced(PlacedEnum, String),
}
#[derive(Clone, Debug, PartialEq, Eq)]
@ -476,7 +702,21 @@ impl Component for Wrapper {
current_side,
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(_) => (),
}
@ -552,7 +792,7 @@ impl Component for Wrapper {
}
} else {
format!(
"<b class=\"{}\">It is {}'s turn</b>",
"<b class=\"{}\">It is {}'s Turn</b>",
turn.get_color(),
turn
)
@ -585,8 +825,11 @@ impl Component for Wrapper {
} else {
// a player won
let turn = Turn::from(endgame_state);
let text_string =
format!("<b class=\"{}\">{} has won</b>", turn.get_color(), turn);
let text_string = format!(
"<b class=\"{}\">{} has won the game</b>",
turn.get_color(),
turn
);
let text_append_result = append_to_info_text(
&document,
"info_text0",
@ -942,6 +1185,23 @@ impl Component for Wrapper {
WrapperMsg::BackendTick => {
if self.player_id.is_none() {
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
@ -954,11 +1214,284 @@ impl Component for Wrapper {
}
WrapperMsg::BackendResponse(br_enum) => match br_enum {
BREnum::Error(string) => {
// TODO maybe suppress this for release builds
log::warn!("{}", string);
}
BREnum::GotID(id, turn_opt) => {
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)