Unified WinnerSchema (#856)

## Description:

Refactor various types across the project to use WinnerSchema.

## Please complete the following:

- [x] I have added screenshots for all UI updates
- [x] I confirm I have thoroughly tested these changes and take full
responsibility for any bugs introduced
- [x] I understand that submitting code with bugs that could have been
caught through manual testing blocks releases and new features for all
contributors

Co-authored-by: Scott Anderson <662325+scottanderson@users.noreply.github.com>
This commit is contained in:
Scott Anderson
2025-05-23 23:42:47 -04:00
committed by GitHub
parent 08216855f7
commit 3a65efd6e2
9 changed files with 40 additions and 44 deletions
+10 -10
View File
@@ -7,11 +7,12 @@ import {
GameStartInfo,
PlayerRecord,
ServerMessage,
Winner,
} from "../core/Schemas";
import { createGameRecord } from "../core/Util";
import { ServerConfig } from "../core/configuration/Config";
import { getConfig } from "../core/configuration/ConfigLoader";
import { Cell, Team, UnitType } from "../core/game/Game";
import { Cell, UnitType } from "../core/game/Game";
import { TileRef } from "../core/game/GameMap";
import {
ErrorUpdate,
@@ -186,6 +187,13 @@ export class ClientGameRunner {
this.lastMessageTime = Date.now();
}
private getWinner(update: WinUpdate): Winner {
if (update.winner[0] !== "player") return update.winner;
const clientId = this.gameView.playerBySmallID(update.winner[1]).clientID();
if (clientId === null) return;
return ["player", clientId];
}
private saveGame(update: WinUpdate) {
if (this.myPlayer === null) {
return;
@@ -199,14 +207,7 @@ export class ClientGameRunner {
stats: update.allPlayersStats[this.lobby.clientID],
},
];
let winner: ClientID | Team | null = null;
if (update.winnerType === "player") {
winner = this.gameView
.playerBySmallID(update.winner as number)
.clientID();
} else {
winner = update.winner as Team;
}
const winner = this.getWinner(update);
if (this.lobby.gameStartInfo === undefined) {
throw new Error("missing gameStartInfo");
@@ -220,7 +221,6 @@ export class ClientGameRunner {
startTime(),
Date.now(),
winner,
update.winnerType,
);
endGame(record);
}
+1 -2
View File
@@ -193,8 +193,7 @@ export class LocalServer {
this.turns,
this.startedAt,
Date.now(),
this.winner?.winner ?? null,
this.winner?.winnerType ?? null,
this.winner?.winner,
);
if (!saveFullGame) {
// Clear turns because beacon only supports up to 64kb
+2 -5
View File
@@ -6,7 +6,6 @@ import {
GameType,
PlayerID,
PlayerType,
Team,
Tick,
UnitType,
} from "../core/game/Game";
@@ -14,7 +13,6 @@ import { PlayerView } from "../core/game/GameView";
import {
AllPlayersStats,
ClientHashMessage,
ClientID,
ClientIntentMessage,
ClientJoinMessage,
ClientLogMessage,
@@ -23,6 +21,7 @@ import {
Intent,
ServerMessage,
ServerMessageSchema,
Winner,
} from "../core/Schemas";
import { LobbyConfig } from "./ClientGameRunner";
import { LocalServer } from "./LocalServer";
@@ -142,9 +141,8 @@ export class SendSetTargetTroopRatioEvent implements GameEvent {
export class SendWinnerEvent implements GameEvent {
constructor(
public readonly winner: ClientID | Team,
public readonly winner: Winner,
public readonly allPlayersStats: AllPlayersStats,
public readonly winnerType: "player" | "team",
) {}
}
export class SendHashEvent implements GameEvent {
@@ -537,7 +535,6 @@ export class Transport {
type: "winner",
winner: event.winner,
allPlayersStats: event.allPlayersStats,
winnerType: event.winnerType,
} satisfies ClientSendWinnerMessage;
this.sendMsg(JSON.stringify(msg));
} else {
+8 -12
View File
@@ -2,9 +2,8 @@ import { LitElement, css, html } from "lit";
import { customElement, state } from "lit/decorators.js";
import { translateText } from "../../../client/Utils";
import { EventBus } from "../../../core/EventBus";
import { Team } from "../../../core/game/Game";
import { GameUpdateType } from "../../../core/game/GameUpdates";
import { GameView, PlayerView } from "../../../core/game/GameView";
import { GameView } from "../../../core/game/GameView";
import { SendWinnerEvent } from "../../Transport";
import { Layer } from "./Layer";
@@ -185,26 +184,23 @@ export class WinModal extends LitElement implements Layer {
const updates = this.game.updatesSinceLastTick();
const winUpdates = updates !== null ? updates[GameUpdateType.Win] : [];
winUpdates.forEach((wu) => {
if (wu.winnerType === "team") {
this.eventBus.emit(
new SendWinnerEvent(wu.winner as Team, wu.allPlayersStats, "team"),
);
if (wu.winner === this.game.myPlayer()?.team()) {
if (wu.winner[0] === "team") {
this.eventBus.emit(new SendWinnerEvent(wu.winner, wu.allPlayersStats));
if (wu.winner[1] === this.game.myPlayer()?.team()) {
this._title = translateText("win_modal.your_team");
} else {
this._title = translateText("win_modal.other_team", {
team: wu.winner,
team: wu.winner[1],
});
}
this.show();
} else {
const winner = this.game.playerBySmallID(
wu.winner as number,
) as PlayerView;
const winner = this.game.playerBySmallID(wu.winner[1]);
if (!winner.isPlayer()) return;
const winnerClient = winner.clientID();
if (winnerClient !== null) {
this.eventBus.emit(
new SendWinnerEvent(winnerClient, wu.allPlayersStats, "player"),
new SendWinnerEvent(["player", winnerClient], wu.allPlayersStats),
);
}
if (
+10 -4
View File
@@ -391,11 +391,18 @@ export const ServerMessageSchema = z.union([
// Client
export const WinnerSchema = z
.union([
z.tuple([z.literal("player"), ID]),
z.tuple([z.literal("team"), SafeString]),
])
.optional();
export type Winner = z.infer<typeof WinnerSchema>;
export const ClientSendWinnerSchema = z.object({
type: z.literal("winner"),
winner: z.union([ID, TeamSchema]).nullable(),
winner: WinnerSchema,
allPlayersStats: AllPlayersStatsSchema,
winnerType: z.enum(["player", "team"]),
});
export const ClientHashSchema = z.object({
@@ -451,8 +458,7 @@ export const GameEndInfoSchema = GameStartInfoSchema.extend({
end: z.number(),
duration: z.number().nonnegative(),
num_turns: z.number(),
winner: z.union([ID, SafeString]).nullable().optional(),
winnerType: z.enum(["player", "team"]).nullable().optional(),
winner: WinnerSchema,
});
export type GameEndInfo = z.infer<typeof GameEndInfoSchema>;
+3 -5
View File
@@ -1,15 +1,15 @@
import DOMPurify from "dompurify";
import { customAlphabet } from "nanoid";
import twemoji from "twemoji";
import { Cell, Team, Unit } from "./game/Game";
import { Cell, Unit } from "./game/Game";
import { GameMap, TileRef } from "./game/GameMap";
import {
ClientID,
GameConfig,
GameID,
GameRecord,
PlayerRecord,
Turn,
Winner,
} from "./Schemas";
import {
@@ -191,8 +191,7 @@ export function createGameRecord(
allTurns: Turn[],
start: number,
end: number,
winner: ClientID | Team | null,
winnerType: "player" | "team" | null,
winner: Winner,
): GameRecord {
const duration = Math.floor((end - start) / 1000);
const version = "v0.0.2";
@@ -211,7 +210,6 @@ export function createGameRecord(
duration,
num_turns,
winner,
winnerType,
},
version,
gitCommit,
+4 -2
View File
@@ -594,8 +594,10 @@ export class GameImpl implements Game {
setWinner(winner: Player | Team, allPlayersStats: AllPlayersStats): void {
this.addUpdate({
type: GameUpdateType.Win,
winner: typeof winner === "string" ? winner : winner.smallID(),
winnerType: typeof winner === "string" ? "team" : "player",
winner:
typeof winner === "string"
? ["team", winner]
: ["player", winner.smallID()],
allPlayersStats,
});
}
+1 -2
View File
@@ -178,8 +178,7 @@ export interface WinUpdate {
type: GameUpdateType.Win;
allPlayersStats: AllPlayersStats;
// Player id or team name.
winner: number | Team;
winnerType: "player" | "team";
winner: ["player", number] | ["team", Team];
}
export interface HashUpdate {
+1 -2
View File
@@ -562,8 +562,7 @@ export class GameServer {
this.turns,
this._startTime ?? 0,
Date.now(),
this.winner?.winner ?? null,
this.winner?.winnerType ?? null,
this.winner?.winner,
),
);
}