Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add match settings in dev-server #34

Merged
merged 1 commit into from
Aug 20, 2024
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
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