Skip to content

Commit

Permalink
Add match settings in dev-server
Browse files Browse the repository at this point in the history
Also:
* Rename default -> isDefault in game setting (`default` being a JS
  reserved keyword it was annoying in some places)
* Normalize the game settings in `parseGame`
  • Loading branch information
simlmx committed Aug 19, 2024
1 parent 6a330fe commit 4de5c40
Show file tree
Hide file tree
Showing 11 changed files with 680 additions and 260 deletions.
2 changes: 1 addition & 1 deletion games/game1-v2.3.0/game/src/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { expect, test } from "vitest";

import { MatchTester as _MatchTester, MatchTesterOptions } from "@lefun/game";

import { autoMove, game, RollGame as G, RollGameState as GS } from ".";
import { autoMove, G, game, GS } from ".";

class MatchTester extends _MatchTester<GS, G> {
constructor(options: Omit<MatchTesterOptions<GS, G>, "game" | "autoMove">) {
Expand Down
61 changes: 52 additions & 9 deletions games/game1-v2.3.0/game/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { UserId } from "@lefun/core";
import { GamePlayerSettings, GameSettings, UserId } from "@lefun/core";
import {
AutoMove,
BoardMove,
Expand All @@ -21,9 +21,12 @@ export type Board = {

sum: number;
lastSomeBoardMoveValue?: number;

matchSettings: Record<string, string>;
matchPlayersSettings: Record<UserId, Record<string, string>>;
};

export type RollGameState = GameState<Board>;
export type GS = GameState<Board>;

type MoveWithArgPayload = { someArg: string };
type BoardMoveWithArgPayload = { someArg: number };
Expand All @@ -47,7 +50,7 @@ const playerStats = [
] as const satisfies GameStats;

type PM<Payload = null> = PlayerMove<
RollGameState,
GS,
Payload,
PMT,
BMT,
Expand All @@ -71,7 +74,10 @@ const roll: PM = {
logPlayerStat,
endMatch,
}) {
const diceValue = random.d6();
const diceValue =
board.matchPlayersSettings[userId].dieNumFaces === "6"
? random.d6()
: random.dice(20);
board.players[userId].diceValue = diceValue;
board.players[userId].isRolling = false;
board.sum += diceValue;
Expand Down Expand Up @@ -102,7 +108,7 @@ const roll: PM = {
},
};

type BM<P = null> = BoardMove<RollGameState, P, PMT, BMT>;
type BM<P = null> = BoardMove<GS, P, PMT, BMT>;

const initMove: BM = {
execute({ board, turns }) {
Expand All @@ -122,8 +128,41 @@ const someBoardMoveWithArgs: BM<BoardMoveWithArgPayload> = {
},
};

const gameSettings: GameSettings = [
{
key: "setting1",
options: [{ value: "a" }, { value: "b" }],
},
{ key: "setting2", options: [{ value: "x" }, { value: "y" }] },
];

const gamePlayerSettings: GamePlayerSettings = [
{
key: "color",
type: "color",
exclusive: true,
options: [
{ value: "red", label: "red" },
{ value: "blue", label: "blue" },
{ value: "green", label: "green" },
{ value: "orange", label: "orange" },
{ value: "pink", label: "pink" },
{ value: "brown", label: "brown" },
{ value: "black", label: "black" },
{ value: "darkgreen", label: "darkgreen" },
{ value: "darkred", label: "darkred" },
{ value: "purple", label: "purple" },
],
},
{
key: "dieNumFaces",
type: "string",
options: [{ value: "6" }, { value: "20" }],
},
];

export const game = {
initialBoards({ players }) {
initialBoards({ players, matchSettings, matchPlayersSettings }) {
return {
board: {
sum: 0,
Expand All @@ -132,6 +171,8 @@ export const game = {
),
playerOrder: [...players],
currentPlayerIndex: 0,
matchSettings,
matchPlayersSettings,
},
};
},
Expand All @@ -141,11 +182,13 @@ export const game = {
maxPlayers: 10,
matchStats,
playerStats,
} satisfies Game<RollGameState, PMT, BMT>;
gameSettings,
gamePlayerSettings,
} satisfies Game<GS, PMT, BMT>;

export type RollGame = typeof game;
export type G = typeof game;

export const autoMove: AutoMove<RollGameState, RollGame> = ({ random }) => {
export const autoMove: AutoMove<GS, G> = ({ random }) => {
if (random.d2() === 1) {
return ["moveWithArg", { someArg: "123" }];
}
Expand Down
31 changes: 20 additions & 11 deletions games/game1-v2.3.0/ui/src/Board.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,22 +12,23 @@ import {
useUsername,
} from "@lefun/ui";

import type { RollGame, RollGameState } from "game1-v2.3.0-game";
import type { G, GS } from "game1-v2.3.0-game";

// Dice symbol characters
const DICE = ["", "\u2680", "\u2681", "\u2682", "\u2683", "\u2684", "\u2685"];

const useSelector = makeUseSelector<RollGameState>();
const useSelectorShallow = makeUseSelectorShallow<RollGameState>();
const useMakeMove = makeUseMakeMove<RollGame>();
const useSelector = makeUseSelector<GS>();
const useSelectorShallow = makeUseSelectorShallow<GS>();
const useMakeMove = makeUseMakeMove<G>();

function Player({ userId }: { userId: UserId }) {
const itsMe = useSelector((state) => state.userId === userId);
const username = useUsername(userId);

const color = useSelector(
(state) => state.board.matchPlayersSettings[userId].color,
);

return (
<div className="player">
<span className={classNames(itsMe && "bold")}>{username}</span>
<span className={classNames(itsMe && "bold", color)}>{username}</span>
<Die userId={userId} />
</div>
);
Expand All @@ -42,9 +43,10 @@ function Die({ userId }: { userId: UserId }) {
);

return (
<span className="dice">
{isRolling || !diceValue ? "?" : DICE[diceValue]}
</span>
<div>
Dice Value:{" "}
<span className="dice">{isRolling || !diceValue ? "?" : diceValue}</span>
</div>
);
}

Expand All @@ -54,12 +56,19 @@ function Board() {
Object.keys(state.board.players),
);

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

const isPlayer = useIsPlayer();

return (
<div>
<div>
<Trans>The template game</Trans>
{Object.entries(matchSettings).map(([key, value]) => (
<div key={key}>
<span className="bold">{key}:</span> {value}
</div>
))}
{players.map((userId) => (
<Player key={userId} userId={userId} />
))}
Expand Down
49 changes: 46 additions & 3 deletions games/game1-v2.3.0/ui/src/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,53 @@ button {
}

.dice {
margin: 0 0 0 10px;
font-size: 3rem;
font-weight: bold;
font-size: 1.2rem;
}

.player {
height: 100px;
display: flex;
flex-direction: column;
justify-content: center;
height: 80px;
}

.red {
color: red;
}

.blue {
color: blue;
}

.green {
color: green;
}

.orange {
color: orange;
}

.pink {
color: pink;
}

.brown {
color: brown;
}

.black {
color: black;
}

.darkgreen {
color: darkgreen;
}

.darkred {
color: darkred;
}

.purple {
color: purple;
}
2 changes: 1 addition & 1 deletion games/game1-v2.3.0/ui/src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,5 @@ render({
},
game,
messages: { en, fr },
gameId: "roll",
gameId: "game1-v2.3.0",
});
25 changes: 14 additions & 11 deletions packages/core/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export const LOCALES: Locale[] = ["fr", "en"];
export type GameSettingOption = {
value: string;
// Is it the default option. If it's missing we'll take the first one.
default?: boolean;
isDefault?: boolean;

// Keeping as optional for backward compatibility.
label?: string;
Expand Down Expand Up @@ -51,14 +51,19 @@ export type GameSetting = {

export type GameSettings = GameSetting[];

export type GameSettings_ = {
allIds: string[];
byId: Record<string, GameSetting>;
};

/*
* Fields common to all the player setting options.
*/
export type CommonPlayerSettingOption = {
value: string;
// Is it the default option? If none is the default, we will fallback on the first
// player option as the default.
default?: boolean;
isDefault?: boolean;
};

type ColorPlayerSettingOption = {
Expand All @@ -75,6 +80,7 @@ type StringPlayerSettingOption = {
// Note that some fields are common to all types of game player setting, and some
// depend on the type.
export type GamePlayerSetting = {
key: string;
// Can different players have the same selected option?
// By default we assume *not* exclusive.
exclusive?: boolean;
Expand All @@ -94,15 +100,12 @@ export type GamePlayerSetting = {
| { type: "string"; options: StringPlayerSettingOption[] }
);

// FIXME This should be a list, to get an order, like the game options. This will be a problem when we have
// more than one option (which is not the case yet!).
// But at the same time being able to query the option using a string is useful, and
// it's missing in the Game Options. Ideally find a way to have the best of both worlds,
// for both the (regular) options and the player options... without making it to
// cumbersome for the game developer! We probably want to internally build a allIds/byId
// scheme from the list of options, and split the "game player options DEF" with the
// "gamePlayerSettings" that we store.
export type GamePlayerSettings = Record<string, GamePlayerSetting>;
export type GamePlayerSettings = GamePlayerSetting[];

export type GamePlayerSettings_ = {
allIds: string[];
byId: Record<string, GamePlayerSetting>;
};

export type GameStatType =
| "integer"
Expand Down
Loading

0 comments on commit 4de5c40

Please sign in to comment.