Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
.PHONY: all
all: fix check test

.PHONY: install
install:
pnpm install
Expand Down
6 changes: 6 additions & 0 deletions games/game1-v2.7.0/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
</head>
<body>
<script>
if (window.parent !== window) {
window.__REACT_DEVTOOLS_GLOBAL_HOOK__ =
window.parent.__REACT_DEVTOOLS_GLOBAL_HOOK__;
}
</script>
<div id="home"></div>
<script type="module" src="src/main.tsx"></script>
</body>
Expand Down
52 changes: 30 additions & 22 deletions games/game1-v2.7.0/src/Board.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -87,26 +87,47 @@ const EndMatchCountDown = () => {
return <CountDown ts={endsAt} />;
};

function Board() {
const Sum = () => {
const sum = useSelector((state) => state.board.sum);
return <div>Sum: {sum}</div>;
};

const Buttons = () => {
const makeMove = useMakeMove();
const itsMyTurn = useSelector(
(state) => getCurrentPlayer(state.board) === state.userId,
);
return (
<>
<button
className={classNames(!itsMyTurn && "disabled")}
onClick={() => makeMove("roll")}
>
<Trans>Roll</Trans>
</button>
<button
className={classNames(!itsMyTurn && "disabled")}
onClick={() => makeMove("pass")}
>
<Trans>Pass</Trans>
</button>
</>
);
};

function Board() {
const players = useSelectorShallow((state) =>
Object.keys(state.board.players),
);

const matchSettings = useSelector((state) => state.board.matchSettings);

const sum = useSelector((state) => state.board.sum);

const itsMyTurn = useSelector(
(state) => getCurrentPlayer(state.board) === state.userId,
);

return (
<div className="outer">
<div className="inner">
<div>
<Trans>The template game</Trans>
<div>Sum: {sum}</div>
<Sum />
<EndMatchCountDown />
{Object.entries(matchSettings).map(([key, value]) => (
<div key={key}>
Expand All @@ -118,20 +139,7 @@ function Board() {
))}
</div>

<>
<button
className={classNames(!itsMyTurn && "disabled")}
onClick={() => makeMove("roll")}
>
<Trans>Roll</Trans>
</button>
<button
className={classNames(!itsMyTurn && "disabled")}
onClick={() => makeMove("pass")}
>
<Trans>Pass</Trans>
</button>
</>
<Buttons />
</div>
</div>
);
Expand Down
5 changes: 4 additions & 1 deletion games/game1-v2.7.0/src/game.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,10 @@ const goToNextPlayer = ({
};

const pass: PM = {
executeNow({ board, turns }) {
executeNow({ board, turns, userId }) {
if (getCurrentPlayer(board) !== userId) {
throw new Error("not your turn!");
}
goToNextPlayer({ board, turns });
},
};
Expand Down
1 change: 0 additions & 1 deletion packages/dev-server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,6 @@
"classnames": "^2.5.1",
"immer": "^10.1.1",
"json-edit-react": "^1.13.3",
"valtio": "^2.1.5",
"zustand": "^4.5.4"
},
"postcss": {
Expand Down
85 changes: 37 additions & 48 deletions packages/dev-server/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,16 @@ import classNames from "classnames";
import { enablePatches, Patch } from "immer";
import { JsonEditor } from "json-edit-react";
import { ReactNode, RefObject, useEffect, useRef, useState } from "react";
import { proxy, snapshot, useSnapshot } from "valtio";
import { useStore as useStoreZustand } from "zustand";
import { shallow } from "zustand/shallow";
import { useStoreWithEqualityFn } from "zustand/traditional";

import {
GameId,
GamePlayerSettings_,
GameSetting,
GameSettings_,
Locale,
Meta,
UserId,
} from "@lefun/core";
import {
Expand All @@ -42,7 +43,7 @@ import { OptimisticBoards } from "./moves";
import { useStore } from "./store";
import { generateId } from "./utils";

const LATENCY = 100;
const LATENCY = 500;

enablePatches();

Expand Down Expand Up @@ -80,22 +81,20 @@ const BoardForPlayer = ({
}, [locale, messages]);

const [loading, setLoading] = useState(true);
const { store: mainStore } = backend;
const { users } = mainStore;

// Here we use `valtio` as a test to see how well it works as a local state
// management solution. So far it's pretty good, perhaps we should also use it for the dev-server settings!
const optimisticBoards = useRef(
proxy(
new OptimisticBoards({
board: backend.store.board,
playerboard: backend.store.playerboards[userId] || null,
meta: backend.store.meta,
}),
),
new OptimisticBoards({
board: backend.store.board,
playerboard: backend.store.playerboards[userId] || null,
meta: backend.store.meta,
userId,
users,
}),
);

useEffect(() => {
const { store: mainStore } = backend;

backend.addEventListener(patchesForUserEvent(userId), (event: any) => {
if (!event) {
return;
Expand Down Expand Up @@ -127,23 +126,23 @@ const BoardForPlayer = ({

let result: MoveExecutionOutput | null = null;

const { board, playerboard, meta } =
optimisticBoards.current.store.getState();

try {
result = executePlayerMove({
name,
payload,
game: backend.game,
userId,
board: optimisticBoards.current.board,
playerboards: { [userId]: optimisticBoards.current.playerboard },
board,
playerboards: { [userId]: playerboard },
secretboard: null,
now: new Date().getTime(),
random: backend.random,
onlyExecuteNow: true,
// Note that technically we should not use anything from
// `match.store` as this represents the DB.
matchData: backend.store.matchData,
gameData: backend.store.gameData,
meta: backend.store.meta,
meta,
isExpiration: false,
});
} catch (e) {
Expand All @@ -164,50 +163,40 @@ const BoardForPlayer = ({
backend.makeMove({ userId, name, payload, moveId, isExpiration: false });
});

const { users } = mainStore;

const _useSelector = (): UseSelector<GameStateBase> => {
// We wrap it to respect the rules of hooks.
const useSelector = <GS extends GameStateBase, T>(
selector: Selector<GS, T>,
): T => {
const snapshot = useSnapshot(optimisticBoards.current);
const board = snapshot.board;
const playerboard = snapshot.playerboard;
const meta = snapshot.meta as Meta;
return selector({
board,
playerboard,
meta,
userId,
users,
timeDelta: 0,
timeLatency: 0,
});
return useStoreZustand(optimisticBoards.current.store, selector);
};
return useSelector;
};

setUseSelector(_useSelector);

setUseStore(() => {
const playerboard = optimisticBoards.current.playerboard;
return {
board: snapshot(optimisticBoards.current.board),
playerboard: playerboard === null ? null : snapshot(playerboard),
meta: snapshot(optimisticBoards.current.meta) as Meta,
userId,
users,
timeDelta: 0,
timeLatency: 0,
};
return optimisticBoards.current.store.getState();
});

// As far as I know, valtio does not support shallow selectors, but if I understand correctly it's not a concern with Valtio.
setUseSelectorShallow(_useSelector);
const _useSelectorShallow = (): UseSelector<GameStateBase> => {
// We wrap it to respect the rules of hooks.
const useSelector = <GS extends GameStateBase, T>(
selector: Selector<GS, T>,
): T => {
return useStoreWithEqualityFn(
optimisticBoards.current.store,
selector,
shallow,
);
};
return useSelector;
};

setUseSelectorShallow(_useSelectorShallow);

setLoading(false);
}, [userId, backend, gameId]);
}, [userId, backend, gameId, users]);

if (loading) {
return <div>Loading player...</div>;
Expand Down
21 changes: 5 additions & 16 deletions packages/dev-server/src/backend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import {
Game_,
MoveExecutionOutput,
Random,
updateMetaWithTurnInfo,
} from "@lefun/game";
import { User } from "@lefun/ui";

Expand Down Expand Up @@ -338,23 +337,13 @@ class Backend extends EventTarget {

// Update the store.
{
const { board, playerboards, secretboard } = result;
const { board, playerboards, secretboard, meta } = result;
store.board = board;
store.playerboards = playerboards;
store.secretboard = secretboard;
store.meta = meta;
}

// Update meta
const { beginTurn, endTurn } = result;
const { meta: newMeta, patches: metaPatches } = updateMetaWithTurnInfo({
meta,
beginTurn,
endTurn,
now,
});

store.meta = newMeta;

// Send out patches to users.
{
const userIds = store.meta.players.allIds;
Expand All @@ -366,12 +355,12 @@ class Backend extends EventTarget {
const { patches } = result;

separatePatchesByUser({
patches: [...patches, ...metaPatches],
patches,
userIds,
patchesOut: patchesByUserId,
});

// Add the `meta` patches to everyone.
// Add the patches to everyone.

for (const [userId, patches] of Object.entries(patchesByUserId)) {
if (patches.length === 0) {
Expand Down Expand Up @@ -459,7 +448,7 @@ class Backend extends EventTarget {
try {
this._makeMove({ name, payload, userId, moveId, isExpiration });
} catch (e) {
console.error("There was an error executing move", name, e);
console.warn("BACKEND: There was an error executing move", name, e);
this.dispatchEvent(
new CustomEvent(REVERT_MOVE_EVENT, { detail: { moveId } }),
);
Expand Down
Loading