Multiplayer #1
13 changed files with 115 additions and 148 deletions
|
@ -13,7 +13,7 @@ use std::collections::HashMap;
|
|||
use std::sync::{Arc, LazyLock, Weak};
|
||||
use tokio::select;
|
||||
use uuid::Uuid;
|
||||
use word_grid::api::{APIGame, ApiState};
|
||||
use word_grid::api::{APIGame, ApiState, Update};
|
||||
use word_grid::dictionary::{Dictionary, DictionaryImpl};
|
||||
use word_grid::game::{Error, Game, PlayedTile};
|
||||
use word_grid::player_interaction::ai::Difficulty;
|
||||
|
@ -73,7 +73,6 @@ enum RoomEvent {
|
|||
#[serde(tag = "type")]
|
||||
enum GameEvent {
|
||||
TurnAction { state: ApiState, committed: bool },
|
||||
WordAdded { word: String, player: Player },
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Debug)]
|
||||
|
@ -81,7 +80,6 @@ enum GameEvent {
|
|||
enum ServerToClientMessage {
|
||||
RoomChange { event: RoomEvent, info: PartyInfo },
|
||||
GameEvent { event: GameEvent },
|
||||
WordAdded { word: String },
|
||||
GameError { error: Error },
|
||||
Invalid { reason: String },
|
||||
}
|
||||
|
@ -115,7 +113,7 @@ enum ClientToServerMessage {
|
|||
#[derive(Clone, Debug)]
|
||||
enum InnerRoomMessage {
|
||||
PassThrough(ServerToClientMessage),
|
||||
GameEvent,
|
||||
GameEvent(Option<Update>),
|
||||
}
|
||||
|
||||
type RoomMap = HashMap<String, Weak<RwLock<Room>>>;
|
||||
|
@ -134,6 +132,10 @@ async fn incoming_message_handler<E: std::fmt::Display>(
|
|||
Some(message) => {
|
||||
match message {
|
||||
Ok(message) => {
|
||||
if let Message::Ping(_) = message {
|
||||
println!("Received ping from player {player:#?}");
|
||||
return false;
|
||||
}
|
||||
let message = message.to_text().unwrap();
|
||||
if message.len() == 0 {
|
||||
println!("Websocket closed");
|
||||
|
@ -173,7 +175,7 @@ async fn incoming_message_handler<E: std::fmt::Display>(
|
|||
println!("Received {message:#?} from client {}", player.id);
|
||||
match message {
|
||||
ClientToServerMessage::Load => {
|
||||
return !game_load(player, room, stream).await
|
||||
return !game_load(player, None, room, stream).await
|
||||
}
|
||||
ClientToServerMessage::StartGame => {
|
||||
let mut room = room.write().await;
|
||||
|
@ -200,7 +202,7 @@ async fn incoming_message_handler<E: std::fmt::Display>(
|
|||
let game = APIGame::new(game);
|
||||
room.game = Some(game);
|
||||
|
||||
sender.send(InnerRoomMessage::GameEvent).unwrap();
|
||||
sender.send(InnerRoomMessage::GameEvent(None)).unwrap();
|
||||
}
|
||||
}
|
||||
ClientToServerMessage::GameMove { r#move } => {
|
||||
|
@ -242,8 +244,10 @@ async fn incoming_message_handler<E: std::fmt::Display>(
|
|||
}
|
||||
};
|
||||
match result {
|
||||
Ok(_) => {
|
||||
sender.send(InnerRoomMessage::GameEvent).unwrap();
|
||||
Ok(event) => {
|
||||
sender
|
||||
.send(InnerRoomMessage::GameEvent(event.update))
|
||||
.unwrap();
|
||||
}
|
||||
Err(error) => {
|
||||
let event = ServerToClientMessage::GameError { error };
|
||||
|
@ -292,11 +296,17 @@ async fn incoming_message_handler<E: std::fmt::Display>(
|
|||
false
|
||||
}
|
||||
|
||||
async fn game_load(player: &Player, room: &Arc<RwLock<Room>>, stream: &mut DuplexStream) -> bool {
|
||||
async fn game_load(
|
||||
player: &Player,
|
||||
update: Option<Update>,
|
||||
room: &Arc<RwLock<Room>>,
|
||||
stream: &mut DuplexStream,
|
||||
) -> bool {
|
||||
// The game object was modified; we need to trigger a load from this player's perspective
|
||||
let mut room = room.write().await;
|
||||
|
||||
let state = room.game.as_mut().unwrap().load(&player.name).unwrap();
|
||||
let mut state = room.game.as_mut().unwrap().load(&player.name).unwrap();
|
||||
state.update = update;
|
||||
let event = ServerToClientMessage::GameEvent {
|
||||
event: GameEvent::TurnAction {
|
||||
state,
|
||||
|
@ -325,7 +335,7 @@ async fn outgoing_message_handler<E: std::fmt::Debug>(
|
|||
let x = stream.send(text.into()).await;
|
||||
x.is_err()
|
||||
}
|
||||
InnerRoomMessage::GameEvent => !game_load(player, room, stream).await,
|
||||
InnerRoomMessage::GameEvent(update) => !game_load(player, update, room, stream).await,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -32,7 +32,6 @@ export function Game(props: {
|
|||
}) {
|
||||
|
||||
const [api_state, setAPIState] = useState<APIState>(undefined);
|
||||
const [isLoading, setIsLoading] = useState<boolean>(true);
|
||||
const [confirmedScorePoints, setConfirmedScorePoints] = useState<number>(-1);
|
||||
const currentTurnNumber = useRef<number>(-1);
|
||||
const historyProcessedNumber = useRef<number>(0);
|
||||
|
@ -44,15 +43,11 @@ export function Game(props: {
|
|||
|
||||
|
||||
function waitForUpdate() {
|
||||
// setAPIState(undefined);
|
||||
// setIsLoading(true);
|
||||
setIsLoading(true);
|
||||
const result = props.api.load(true);
|
||||
|
||||
result.then(
|
||||
(state) => {
|
||||
setAPIState(state);
|
||||
setIsLoading(false);
|
||||
}
|
||||
)
|
||||
.catch((error) => {
|
||||
|
@ -136,7 +131,6 @@ export function Game(props: {
|
|||
.then(
|
||||
(api_state) => {
|
||||
setAPIState(api_state);
|
||||
waitForUpdate();
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error({error});
|
||||
|
@ -149,14 +143,11 @@ export function Game(props: {
|
|||
const [logInfo, logDispatch] = useReducer(addLogInfo, []);
|
||||
|
||||
useEffect(() => {
|
||||
props.api.register_log_dispatch(logDispatch);
|
||||
props.api.load(false)
|
||||
.then((api_state) => {
|
||||
setAPIState(api_state);
|
||||
setIsLoading(false);
|
||||
});
|
||||
}, [])
|
||||
|
||||
}, []);
|
||||
|
||||
function movePlayableLetters(playerLetters: PlayableLetterData[], update: TileDispatchAction) {
|
||||
if (update.action === TileDispatchActionType.RETRIEVE) {
|
||||
|
@ -255,10 +246,12 @@ export function Game(props: {
|
|||
their turn.</div>);
|
||||
} else if (action.type == "Pass") {
|
||||
logDispatch(<div>{playerName} passed.</div>);
|
||||
} else if (action.type == "AddToDictionary") {
|
||||
logDispatch(<div>{playerName} added {action.word} to the dictionary.</div>)
|
||||
} else {
|
||||
console.error("Received unknown turn action: ", action);
|
||||
}
|
||||
|
||||
// Clear any on-screen arrows
|
||||
gridArrowDispatch({action: GridArrowDispatchActionType.CLEAR});
|
||||
}
|
||||
|
||||
function endGame(state: GameState) {
|
||||
|
@ -363,18 +356,24 @@ export function Game(props: {
|
|||
|
||||
useEffect(() => {
|
||||
if (api_state) {
|
||||
console.log("In state: ", api_state.public_information.current_turn_number);
|
||||
console.log("In ref: ", currentTurnNumber.current);
|
||||
console.debug(api_state);
|
||||
if(currentTurnNumber.current < api_state.public_information.current_turn_number){
|
||||
// We only clear everything if there's a chance the board changed
|
||||
// We may have gotten a dictionary update event which doesn't count
|
||||
gridArrowDispatch({action: GridArrowDispatchActionType.CLEAR});
|
||||
trayDispatch({action: TileDispatchActionType.RETRIEVE});
|
||||
setConfirmedScorePoints(-1);
|
||||
updateBoardLetters(api_state.public_information.board);
|
||||
}
|
||||
|
||||
for (let i = historyProcessedNumber.current; i < api_state.public_information.history.length; i++) {
|
||||
const update = api_state.public_information.history[i];
|
||||
if (update.turn_number > currentTurnNumber.current) {
|
||||
currentTurnNumber.current = update.turn_number;
|
||||
logDispatch(<h4>TTurn {update.turn_number}</h4>);
|
||||
const playerAtTurn = api_state.public_information.players[(update.turn_number-1) % api_state.public_information.players.length].name;
|
||||
logDispatch(<h4>Turn {update.turn_number + 1}</h4>);
|
||||
const playerAtTurn = api_state.public_information.players[(update.turn_number) % api_state.public_information.players.length].name;
|
||||
logDispatch(<div>{playerAtTurn}'s turn</div>);
|
||||
|
||||
}
|
||||
|
@ -384,9 +383,7 @@ export function Game(props: {
|
|||
historyProcessedNumber.current = api_state.public_information.history.length;
|
||||
|
||||
if (!isGameOver) {
|
||||
console.log("In state: ", api_state.public_information.current_turn_number);
|
||||
console.log("In ref: ", currentTurnNumber.current);
|
||||
if(api_state.public_information.current_turn_number >= currentTurnNumber.current){
|
||||
if(api_state.public_information.current_turn_number > currentTurnNumber.current){
|
||||
logDispatch(<h4>Turn {api_state.public_information.current_turn_number + 1}</h4>);
|
||||
logDispatch(<div>{api_state.public_information.current_player}'s turn</div>);
|
||||
currentTurnNumber.current = api_state.public_information.current_turn_number;
|
||||
|
@ -395,24 +392,19 @@ export function Game(props: {
|
|||
endGame(api_state.public_information.game_state);
|
||||
}
|
||||
|
||||
|
||||
if(api_state.public_information.current_player != props.settings.playerName) {
|
||||
waitForUpdate();
|
||||
}
|
||||
}
|
||||
}, [api_state]);
|
||||
|
||||
|
||||
if (isLoading) {
|
||||
if(api_state == null){
|
||||
return <div>Still loading</div>;
|
||||
}
|
||||
|
||||
const playerAndScores = api_state.public_information.players;
|
||||
const remainingTiles = api_state.public_information.remaining_tiles;
|
||||
let remainingAITiles = null;
|
||||
for (let player of playerAndScores) {
|
||||
if (player.name == 'AI') {
|
||||
remainingAITiles = player.tray_tiles;
|
||||
break;
|
||||
}
|
||||
}
|
||||
const isPlayersTurn = api_state.public_information.current_player == props.settings.playerName;
|
||||
|
||||
return <>
|
||||
<TileExchangeModal
|
||||
|
@ -449,11 +441,8 @@ export function Game(props: {
|
|||
<div>
|
||||
{remainingTiles} letters remaining
|
||||
</div>
|
||||
<div>
|
||||
{props.settings.aiName} has {remainingAITiles} tiles
|
||||
</div>
|
||||
<button
|
||||
disabled={remainingTiles == 0 || isGameOver}
|
||||
disabled={remainingTiles == 0 || isGameOver || !isPlayersTurn}
|
||||
onClick={() => {
|
||||
trayDispatch({action: TileDispatchActionType.RETURN}); // want all tiles back on tray for tile exchange
|
||||
setIsTileExchangeOpen(true);
|
||||
|
@ -464,7 +453,7 @@ export function Game(props: {
|
|||
<div className="player-controls">
|
||||
<button
|
||||
className="check"
|
||||
disabled={isGameOver}
|
||||
disabled={isGameOver || !isPlayersTurn}
|
||||
onClick={async () => {
|
||||
const playedTiles = playerLetters.map((i) => {
|
||||
if (i === undefined) {
|
||||
|
@ -492,6 +481,8 @@ export function Game(props: {
|
|||
result
|
||||
.then(
|
||||
(api_state) => {
|
||||
console.log("Testing45")
|
||||
console.log({api_state});
|
||||
|
||||
const play_tiles: TurnAction = api_state.update.type;
|
||||
if (play_tiles.type == "PlayTiles") {
|
||||
|
@ -499,7 +490,6 @@ export function Game(props: {
|
|||
|
||||
if (committing) {
|
||||
setAPIState(api_state);
|
||||
waitForUpdate();
|
||||
}
|
||||
|
||||
} else {
|
||||
|
@ -514,7 +504,12 @@ export function Game(props: {
|
|||
const word = error.split(' ')[0];
|
||||
// For whatever reason I can't pass props.api.add_to_dictionary directly
|
||||
logDispatch(<AddWordButton word={word}
|
||||
addWordFn={(word) => props.api.add_to_dictionary(word)}/>);
|
||||
addWordFn={(word) => {
|
||||
props.api.add_to_dictionary(word)
|
||||
.then((api_state) => {
|
||||
setAPIState(api_state);
|
||||
})
|
||||
}}/>);
|
||||
} else {
|
||||
logDispatch(<div>{error}</div>);
|
||||
}
|
||||
|
@ -532,7 +527,7 @@ export function Game(props: {
|
|||
</button>
|
||||
<button
|
||||
className="pass"
|
||||
disabled={isGameOver}
|
||||
disabled={isGameOver || !isPlayersTurn}
|
||||
onClick={() => {
|
||||
if (window.confirm("Are you sure you want to pass?")) {
|
||||
const result = props.api.pass();
|
||||
|
@ -541,7 +536,6 @@ export function Game(props: {
|
|||
.then(
|
||||
(api_state) => {
|
||||
setAPIState(api_state);
|
||||
waitForUpdate();
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error({error});
|
||||
|
|
|
@ -3,7 +3,7 @@ import {useState} from "react";
|
|||
import {Settings} from "./utils";
|
||||
import {Game} from "./Game";
|
||||
import {API, Difficulty} from "./api";
|
||||
import {GameWasm} from "./wasm_api";
|
||||
import {WasmAPI} from "./wasm_api";
|
||||
import {AISelection} from "./UI";
|
||||
|
||||
export function Menu(props: {settings: Settings, dictionary_text: string}) {
|
||||
|
@ -38,7 +38,7 @@ export function Menu(props: {settings: Settings, dictionary_text: string}) {
|
|||
proportion: processedProportionDictionary,
|
||||
randomness: processedAIRandomness,
|
||||
};
|
||||
const game_wasm: API = new GameWasm(BigInt(seed), props.dictionary_text, difficulty);
|
||||
const game_wasm: API = new WasmAPI(BigInt(seed), props.dictionary_text, difficulty);
|
||||
const game = <Game settings={props.settings} api={game_wasm} key={seed} end_game_fn={() => setGame(null)}/>
|
||||
setGame(game);
|
||||
}}>New Game</button>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import * as React from "react";
|
||||
import {ChangeEvent, JSX, useState} from "react";
|
||||
import {ChangeEvent, JSX} from "react";
|
||||
import {
|
||||
cellTypeToDetails,
|
||||
CoordinateData,
|
||||
|
@ -12,9 +12,7 @@ import {
|
|||
TileDispatch,
|
||||
TileDispatchActionType,
|
||||
} from "./utils";
|
||||
import {API, APIPlayer, CellType, Difficulty} from "./api";
|
||||
import {GameWasm} from "./wasm";
|
||||
import {Game} from "./Game";
|
||||
import {APIPlayer, CellType} from "./api";
|
||||
|
||||
|
||||
export function TileSlot(props: {
|
||||
|
@ -182,7 +180,7 @@ export function Grid(props: {
|
|||
const {className, text} = cellTypeToDetails(ct);
|
||||
|
||||
let tileElement: JSX.Element;
|
||||
if (props.boardLetters[i] !== undefined) {
|
||||
if (props.boardLetters[i] != null) {
|
||||
tileElement = <Letter data={props.boardLetters[i]} />;
|
||||
} else {
|
||||
tileElement = <>
|
||||
|
@ -239,7 +237,8 @@ export function Scores(props: {playerScores: Array<APIPlayer>}){
|
|||
let elements = props.playerScores.map((ps) => {
|
||||
return <div key={ps.name}>
|
||||
<h3>{ps.name}</h3>
|
||||
<span>{ps.score}</span>
|
||||
<div>{ps.score}</div>
|
||||
<div>({ps.tray_tiles} tiles remaining)</div>
|
||||
</div>;
|
||||
});
|
||||
|
||||
|
|
|
@ -30,7 +30,7 @@ export interface Letter {
|
|||
}
|
||||
|
||||
|
||||
export type TurnAction = { type: "Pass" } | { type: "ExchangeTiles"; tiles_exchanged: number } | { type: "PlayTiles"; result: ScoreResult; locations: number[] };
|
||||
export type TurnAction = { type: "Pass" } | { type: "ExchangeTiles"; tiles_exchanged: number } | { type: "PlayTiles"; result: ScoreResult; locations: number[] } | {type: "AddToDictionary"; word: string;};
|
||||
|
||||
export enum CellType {
|
||||
Normal = "Normal",
|
||||
|
@ -85,8 +85,7 @@ export interface API {
|
|||
exchange: (selection: Array<boolean>) => Promise<APIState>;
|
||||
pass: () => Promise<APIState>;
|
||||
play: (tiles: Array<PlayedTile>, commit: boolean) => Promise<APIState>;
|
||||
add_to_dictionary: (word: string) => Promise<void>;
|
||||
add_to_dictionary: (word: string) => Promise<APIState>;
|
||||
load: (wait: boolean) => Promise<APIState>;
|
||||
register_log_dispatch: (fn: (x: any) => void) => void;
|
||||
|
||||
}
|
|
@ -2,6 +2,7 @@
|
|||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<link rel="stylesheet" href="style.less" />
|
||||
<title>Word Grid</title>
|
||||
</head>
|
||||
<body>
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
import * as React from "react";
|
||||
import {useState} from "react";
|
||||
import {useRef, useState} from "react";
|
||||
import {createRoot} from "react-dom/client";
|
||||
import {AISelection} from "./UI";
|
||||
import {PartyInfo, ServerToClientMessage} from "./ws_api";
|
||||
import {ClientToServerMessage, WSAPI, PartyInfo, ServerToClientMessage} from "./ws_api";
|
||||
import {Game} from "./Game";
|
||||
import {Settings} from "./utils";
|
||||
|
||||
|
||||
|
||||
|
@ -25,8 +27,19 @@ export function Menu(): React.JSX.Element {
|
|||
|
||||
const [aiRandomness, setAIRandomness] = useState<number>(6);
|
||||
const [proportionDictionary, setProportionDictionary] = useState<number>(7);
|
||||
const [game, setGame] = useState<React.JSX.Element>(null);
|
||||
|
||||
|
||||
let button_or_game = <button onClick={() => {
|
||||
const event: ClientToServerMessage = {
|
||||
type: "StartGame"
|
||||
};
|
||||
socket.send(JSON.stringify(event));
|
||||
}}>Start Game</button>;
|
||||
if(game){
|
||||
button_or_game = game;
|
||||
}
|
||||
|
||||
// Can change log scale to control shape of curve using following equation:
|
||||
// aiRandomness = log(1 + x*(n-1))/log(n) when x, the user input, ranges between 0 and 1
|
||||
const processedAIRandomness = Math.log(1 + (LOGBASE - 1)*aiRandomness/100) / Math.log(LOGBASE);
|
||||
|
@ -53,6 +66,7 @@ export function Menu(): React.JSX.Element {
|
|||
</li>
|
||||
});
|
||||
|
||||
|
||||
return <div>
|
||||
<p>Connected to {roomName}</p>
|
||||
Players: <ol>
|
||||
|
@ -82,7 +96,9 @@ export function Menu(): React.JSX.Element {
|
|||
setSocket(null);
|
||||
setPartyInfo(null);
|
||||
}}>Disconnect</button>
|
||||
{button_or_game}
|
||||
</div>
|
||||
|
||||
} else {
|
||||
return <div>
|
||||
<div>
|
||||
|
@ -108,6 +124,16 @@ export function Menu(): React.JSX.Element {
|
|||
const input: ServerToClientMessage = JSON.parse(event.data);
|
||||
if(input.type == "RoomChange"){
|
||||
setPartyInfo(input.info);
|
||||
} else if(input.type == "GameEvent" && game == null){
|
||||
// start game
|
||||
setGame(<Game api={new WSAPI(socket)} settings={{
|
||||
playerName: playerName,
|
||||
trayLength: 7,
|
||||
}} end_game_fn={function(): void {
|
||||
socket.close();
|
||||
setSocket(null);
|
||||
setGame(null);
|
||||
} } />);
|
||||
}
|
||||
console.log("Message from server ", event.data);
|
||||
});
|
||||
|
|
|
@ -50,8 +50,6 @@ async function run() {
|
|||
root.render(<Menu dictionary_text={dictionary_text} settings={{
|
||||
trayLength: 7,
|
||||
playerName: 'Player',
|
||||
aiName: 'AI',
|
||||
|
||||
}}/>);
|
||||
|
||||
}
|
||||
|
|
|
@ -204,9 +204,7 @@
|
|||
|
||||
.scoring {
|
||||
text-align: center;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
grid-template-rows: none;
|
||||
display: flex;
|
||||
|
||||
span {
|
||||
font-size: 20px;
|
||||
|
@ -215,6 +213,7 @@
|
|||
div {
|
||||
margin-left: 10px;
|
||||
margin-right: 10px;
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,7 +5,6 @@ import {CellType, Letter as LetterData} from "./api";
|
|||
export interface Settings {
|
||||
trayLength: number;
|
||||
playerName: string;
|
||||
aiName: string;
|
||||
}
|
||||
|
||||
export enum LocationType {
|
||||
|
|
|
@ -1,24 +1,22 @@
|
|||
import {API, APIState, Difficulty, PlayedTile, Result, is_ok} from "./api";
|
||||
import {WasmAPI} from 'word_grid';
|
||||
import {WasmAPI as RawAPI} from 'word_grid';
|
||||
|
||||
export class GameWasm implements API{
|
||||
private wasm: WasmAPI;
|
||||
private log_dispatch: (x: any) => void | null;
|
||||
export class WasmAPI implements API{
|
||||
private wasm: RawAPI;
|
||||
|
||||
constructor(seed: bigint, dictionary_text: string, difficulty: Difficulty) {
|
||||
this.wasm = new WasmAPI(seed, dictionary_text, difficulty);
|
||||
this.log_dispatch = null;
|
||||
this.wasm = new RawAPI(seed, dictionary_text, difficulty);
|
||||
}
|
||||
|
||||
add_to_dictionary(word: string): Promise<void> {
|
||||
return new Promise((resolve, _) => {
|
||||
this.wasm.add_to_dictionary(word);
|
||||
if(this.log_dispatch != null) {
|
||||
this.log_dispatch(<div>{word} was added to dictionary</div>);
|
||||
add_to_dictionary(word: string): Promise<APIState> {
|
||||
return new Promise((resolve, reject) => {
|
||||
let api_state: Result<APIState, any> = this.wasm.add_to_dictionary(word);
|
||||
|
||||
if(is_ok(api_state)) {
|
||||
resolve(api_state.Ok);
|
||||
} else {
|
||||
console.error("log_dispatch was unexpectedly null");
|
||||
reject(api_state.Err);
|
||||
}
|
||||
resolve()
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -70,8 +68,4 @@ export class GameWasm implements API{
|
|||
});
|
||||
}
|
||||
|
||||
register_log_dispatch(fn: (x: any) => void): void {
|
||||
this.log_dispatch = fn;
|
||||
}
|
||||
|
||||
}
|
|
@ -2,7 +2,6 @@ import {API, APIState, Difficulty, PlayedTile} from "./api";
|
|||
|
||||
export interface Player {
|
||||
name: string
|
||||
id: string
|
||||
}
|
||||
|
||||
export interface AI {
|
||||
|
@ -30,10 +29,6 @@ export type GameEvent = {
|
|||
type: "TurnAction"
|
||||
state: APIState
|
||||
committed: boolean
|
||||
} | {
|
||||
type: "WordAdded"
|
||||
word: string
|
||||
player: Player
|
||||
}
|
||||
|
||||
export type ServerToClientMessage = {
|
||||
|
@ -50,9 +45,6 @@ export type ServerToClientMessage = {
|
|||
} | {
|
||||
type: "Invalid"
|
||||
reason: string
|
||||
} | {
|
||||
type: "WordAdded"
|
||||
word: string
|
||||
}
|
||||
|
||||
type GameMove = {
|
||||
|
@ -69,7 +61,7 @@ type GameMove = {
|
|||
word: string
|
||||
}
|
||||
|
||||
type ClientToServerMessage = {
|
||||
export type ClientToServerMessage = {
|
||||
type: "Load" | "StartGame"
|
||||
} | {
|
||||
type: "GameMove"
|
||||
|
@ -87,14 +79,12 @@ interface PromiseInput {
|
|||
reject: (error: any) => void
|
||||
}
|
||||
|
||||
export class GameWS implements API{
|
||||
export class WSAPI implements API{
|
||||
private socket: WebSocket;
|
||||
private currentPromiseInput: PromiseInput | null;
|
||||
private log_dispatch: (x: any) => void | null;
|
||||
|
||||
constructor(socket: WebSocket) {
|
||||
this.socket = socket;
|
||||
this.log_dispatch = null;
|
||||
this.currentPromiseInput = null;
|
||||
this.socket.addEventListener("message", (event) => {
|
||||
let data: ServerToClientMessage = JSON.parse(event.data);
|
||||
|
@ -120,14 +110,6 @@ export class GameWS implements API{
|
|||
});
|
||||
}
|
||||
|
||||
private log_new_word(word: string, player: string) {
|
||||
if(this.log_dispatch != null) {
|
||||
this.log_dispatch(<div>Player {player} added {word} to the dictionary</div>);
|
||||
} else {
|
||||
console.error("Unable to log new word ", word, " from player ", player);
|
||||
}
|
||||
}
|
||||
|
||||
private register_promise(resolve: (value: GameEvent) => void, reject: (value: any) => void) {
|
||||
if(this.currentPromiseInput != null) {
|
||||
console.error("We are setting a new promise before the current one has resolved")
|
||||
|
@ -139,7 +121,7 @@ export class GameWS implements API{
|
|||
};
|
||||
}
|
||||
|
||||
add_to_dictionary(word: string): Promise<void> {
|
||||
add_to_dictionary(word: string): Promise<APIState> {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.register_promise(resolve, reject);
|
||||
let event: ClientToServerMessage = {
|
||||
|
@ -152,13 +134,7 @@ export class GameWS implements API{
|
|||
this.socket.send(JSON.stringify(event));
|
||||
|
||||
}).then((game_event: GameEvent) => {
|
||||
if(game_event.type == "WordAdded"){
|
||||
this.log_new_word(game_event.word, game_event.player.name);
|
||||
} else {
|
||||
console.error("We received the wrong kind of response back!")
|
||||
console.error({game_event});
|
||||
return Promise.reject("We received the wrong kind of response back!");
|
||||
}
|
||||
return game_event.state;
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -174,13 +150,7 @@ export class GameWS implements API{
|
|||
};
|
||||
this.socket.send(JSON.stringify(event));
|
||||
}).then((game_event: GameEvent) => {
|
||||
if(game_event.type == "TurnAction") {
|
||||
return game_event.state;
|
||||
} else {
|
||||
console.error("We received the wrong kind of response back!")
|
||||
console.error({game_event});
|
||||
return Promise.reject("We received the wrong kind of response back!");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -194,13 +164,7 @@ export class GameWS implements API{
|
|||
this.socket.send(JSON.stringify(event));
|
||||
}
|
||||
}).then((game_event: GameEvent) => {
|
||||
if(game_event.type == "TurnAction") {
|
||||
return game_event.state;
|
||||
} else {
|
||||
// need to handle this case; we'll deal with it by returning a new promise again
|
||||
this.log_new_word(game_event.word, game_event.player.name);
|
||||
return this.load(wait);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -215,13 +179,7 @@ export class GameWS implements API{
|
|||
};
|
||||
this.socket.send(JSON.stringify(event));
|
||||
}).then((game_event: GameEvent) => {
|
||||
if(game_event.type == "TurnAction") {
|
||||
return game_event.state;
|
||||
} else {
|
||||
console.error("We received the wrong kind of response back!")
|
||||
console.error({game_event});
|
||||
return Promise.reject("We received the wrong kind of response back!");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -238,18 +196,8 @@ export class GameWS implements API{
|
|||
};
|
||||
this.socket.send(JSON.stringify(event));
|
||||
}).then((game_event: GameEvent) => {
|
||||
if(game_event.type == "TurnAction") {
|
||||
return game_event.state;
|
||||
} else {
|
||||
console.error("We received the wrong kind of response back!")
|
||||
console.error({game_event});
|
||||
return Promise.reject("We received the wrong kind of response back!");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
register_log_dispatch(fn: (x: any) => void): void {
|
||||
this.log_dispatch = fn;
|
||||
}
|
||||
|
||||
}
|
|
@ -47,7 +47,7 @@ pub struct PublicInformation {
|
|||
pub struct ApiState {
|
||||
public_information: PublicInformation,
|
||||
tray: Tray,
|
||||
update: Option<Update>,
|
||||
pub update: Option<Update>,
|
||||
}
|
||||
|
||||
pub struct APIGame(pub Game, Vec<Update>);
|
||||
|
|
Loading…
Reference in a new issue