diff --git a/server/src/main.rs b/server/src/main.rs index 4f103c4..598e120 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -63,22 +63,31 @@ impl Room { #[derive(Clone, Serialize, Debug)] #[serde(tag = "type")] enum RoomEvent { - PlayerJoined(Player), - PlayerLeft(Player), - AIJoined(Difficulty), + PlayerJoined { player: Player }, + PlayerLeft { player: Player }, + AIJoined { difficulty: Difficulty }, AILeft { index: usize }, } +#[derive(Clone, Serialize, Debug)] +#[serde(tag = "type")] +enum GameEvent { + TurnAction { state: ApiState, committed: bool }, + WordAdded { word: String, player: Player }, +} + #[derive(Clone, Serialize, Debug)] #[serde(tag = "type")] enum ServerToClientMessage { RoomChange { event: RoomEvent, info: PartyInfo }, - GameEvent { state: ApiState, committed: bool }, + GameEvent { event: GameEvent }, + WordAdded { word: String }, GameError { error: Error }, Invalid { reason: String }, } #[derive(Deserialize, Debug)] +#[serde(tag = "type")] enum GameMove { Pass, Exchange { @@ -88,6 +97,9 @@ enum GameMove { played_tiles: Vec>, commit_move: bool, }, + AddToDictionary { + word: String, + }, } #[derive(Deserialize, Debug)] @@ -142,7 +154,9 @@ async fn incoming_message_handler( room.party_info.players = new_vec; let event = ServerToClientMessage::RoomChange { - event: RoomEvent::PlayerLeft(player.clone()), + event: RoomEvent::PlayerLeft { + player: player.clone(), + }, info: room.party_info.clone(), }; sender.send(InnerRoomMessage::PassThrough(event)).unwrap(); @@ -208,15 +222,24 @@ async fn incoming_message_handler( played_tiles, commit_move, } => { - let result = game.play(&player.name, played_tiles, commit_move); + let result = + game.play(&player.name, played_tiles, commit_move); if result.is_ok() & !commit_move { - let event = ServerToClientMessage::GameEvent {state: result.unwrap(), committed: false}; + let event = ServerToClientMessage::GameEvent { + event: GameEvent::TurnAction { + state: result.unwrap(), + committed: false, + }, + }; let event = serde_json::to_string(&event).unwrap(); let _ = stream.send(event.into()).await; return false; } result - }, + } + GameMove::AddToDictionary { word } => { + game.add_to_dictionary(&player.name, &word) + } }; match result { Ok(_) => { @@ -235,7 +258,7 @@ async fn incoming_message_handler( room.party_info.ais.push(difficulty.clone()); let event = ServerToClientMessage::RoomChange { - event: RoomEvent::AIJoined(difficulty), + event: RoomEvent::AIJoined { difficulty }, info: room.party_info.clone(), }; sender.send(InnerRoomMessage::PassThrough(event)).unwrap(); @@ -274,7 +297,12 @@ async fn game_load(player: &Player, room: &Arc>, stream: &mut Duple let mut room = room.write().await; let state = room.game.as_mut().unwrap().load(&player.name).unwrap(); - let event = ServerToClientMessage::GameEvent { state, committed: true }; + let event = ServerToClientMessage::GameEvent { + event: GameEvent::TurnAction { + state, + committed: true, + }, + }; let text = serde_json::to_string(&event).unwrap(); let x = stream.send(text.into()).await; @@ -319,7 +347,9 @@ async fn chat( fn make_join_event(room: &Room, player: &Player) -> ServerToClientMessage { ServerToClientMessage::RoomChange { - event: RoomEvent::PlayerJoined(player.clone()), + event: RoomEvent::PlayerJoined { + player: player.clone(), + }, info: room.party_info.clone(), } } diff --git a/ui/src/Game.tsx b/ui/src/Game.tsx index ede9f81..424a412 100644 --- a/ui/src/Game.tsx +++ b/ui/src/Game.tsx @@ -34,7 +34,8 @@ export function Game(props: { const [api_state, setAPIState] = useState(undefined); const [isLoading, setIsLoading] = useState(true); const [confirmedScorePoints, setConfirmedScorePoints] = useState(-1); - const [currentTurnNumber, setCurrentTurnNumber] = useState(0); + const currentTurnNumber = useRef(-1); + const historyProcessedNumber = useRef(0); let isGameOver = false; if (api_state !== undefined) { @@ -42,10 +43,11 @@ export function Game(props: { } - function load() { + function waitForUpdate() { // setAPIState(undefined); // setIsLoading(true); - const result = props.api.load(); + setIsLoading(true); + const result = props.api.load(true); result.then( (state) => { @@ -54,15 +56,11 @@ export function Game(props: { } ) .catch((error) => { - console.log("load() failed") + console.log("waitForUpdate() failed") console.log(error); }); } - useEffect(() => { - load(); - }, []) - const [boardLetters, setBoardLetters] = useState(() => { const newLetterData = [] as HighlightableLetterData[]; for (let i = 0; i < GRID_LENGTH * GRID_LENGTH; i++) { @@ -136,8 +134,9 @@ export function Game(props: { result .then( - (_api_state) => { - load(); + (api_state) => { + setAPIState(api_state); + waitForUpdate(); }) .catch((error) => { console.error({error}); @@ -149,6 +148,15 @@ export function Game(props: { const [gridArrow, gridArrowDispatch] = useReducer(adjustGridArrow, null); 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) { @@ -361,22 +369,28 @@ export function Game(props: { setConfirmedScorePoints(-1); updateBoardLetters(api_state.public_information.board); - for (let i = currentTurnNumber; i < api_state.public_information.history.length; i++) { - if (i > currentTurnNumber) { - logDispatch(

Turn {i + 1}

); - const playerAtTurn = api_state.public_information.players[i % api_state.public_information.players.length].name; - logDispatch(
{playerAtTurn}'s turn
); - } + 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(

TTurn {update.turn_number}

); + const playerAtTurn = api_state.public_information.players[(update.turn_number-1) % api_state.public_information.players.length].name; + logDispatch(
{playerAtTurn}'s turn
); + + } handlePlayerAction(update.type, update.player); } - - setCurrentTurnNumber(api_state.public_information.history.length); + historyProcessedNumber.current = api_state.public_information.history.length; if (!isGameOver) { - logDispatch(

Turn {api_state.public_information.history.length + 1}

); - logDispatch(
{api_state.public_information.current_player}'s turn
); + 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){ + logDispatch(

Turn {api_state.public_information.current_turn_number + 1}

); + logDispatch(
{api_state.public_information.current_player}'s turn
); + currentTurnNumber.current = api_state.public_information.current_turn_number; + } } else { endGame(api_state.public_information.game_state); } @@ -484,7 +498,8 @@ export function Game(props: { setConfirmedScorePoints(play_tiles.result.total); if (committing) { - load(); + setAPIState(api_state); + waitForUpdate(); } } else { @@ -524,8 +539,9 @@ export function Game(props: { result .then( - (_api_state) => { - load(); + (api_state) => { + setAPIState(api_state); + waitForUpdate(); }) .catch((error) => { console.error({error}); @@ -560,7 +576,7 @@ function AddWordButton(props: { word: string, addWordFn: (x: string) => void }) ; } else { return
- {props.word} was added to dictionary. + Adding {props.word} to dictionary.
; } diff --git a/ui/src/Menu.tsx b/ui/src/Menu.tsx index 08613ee..133d642 100644 --- a/ui/src/Menu.tsx +++ b/ui/src/Menu.tsx @@ -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"; +import {GameWasm} from "./wasm_api"; import {AISelection} from "./UI"; export function Menu(props: {settings: Settings, dictionary_text: string}) { diff --git a/ui/src/api.ts b/ui/src/api.ts index cd79956..713c88b 100644 --- a/ui/src/api.ts +++ b/ui/src/api.ts @@ -1,4 +1,3 @@ - export interface Tray { letters: (Letter | undefined)[]; } @@ -58,11 +57,13 @@ export interface PublicInformation { players: Array; remaining_tiles: number; history: Array; + current_turn_number: number; } export interface Update { type: TurnAction, player: string; + turn_number: number; } export interface APIState { @@ -85,6 +86,7 @@ export interface API { pass: () => Promise; play: (tiles: Array, commit: boolean) => Promise; add_to_dictionary: (word: string) => Promise; - load: () => Promise; + load: (wait: boolean) => Promise; + register_log_dispatch: (fn: (x: any) => void) => void; } \ No newline at end of file diff --git a/ui/src/multiplayer.tsx b/ui/src/multiplayer.tsx index 523bfaa..a750841 100644 --- a/ui/src/multiplayer.tsx +++ b/ui/src/multiplayer.tsx @@ -2,21 +2,9 @@ import * as React from "react"; import {useState} from "react"; import {createRoot} from "react-dom/client"; import {AISelection} from "./UI"; +import {PartyInfo, ServerToClientMessage} from "./ws_api"; -interface Player { - name: string - id: string -} -interface AI { - proportion: number - randomness: number -} - -interface PartyInfo { - ais: AI[] - players: Player[] -} const LOGBASE = 10000; function unprocessAIRandomness(processedAIRandomness: number): string { @@ -117,8 +105,10 @@ export function Menu(): React.JSX.Element {