From fab63c73bf822c6ba4381a85b7f16052efa3f4c9 Mon Sep 17 00:00:00 2001 From: Aotumuri Date: Sat, 26 Apr 2025 13:50:02 +0900 Subject: [PATCH] test --- resources/territory_patterns.json | 45 ++++++++++++++++++++ src/client/ClientGameRunner.ts | 1 + src/client/Main.ts | 1 + src/client/SinglePlayerModal.ts | 1 + src/client/Transport.ts | 2 + src/client/graphics/layers/TerritoryLayer.ts | 32 +++++++++++--- src/core/GameRunner.ts | 1 + src/core/Schemas.ts | 3 ++ src/core/execution/BotSpawner.ts | 9 +++- src/core/execution/ExecutionManager.ts | 1 + src/core/game/Game.ts | 1 + src/core/game/GameUpdates.ts | 1 + src/core/game/GameView.ts | 6 +++ src/core/game/PlayerImpl.ts | 7 +++ tests/Attack.test.ts | 2 + tests/MissileSilo.test.ts | 1 + tests/PlayerInfo.test.ts | 9 ++++ tests/SAM.test.ts | 2 + tests/TerritoryCapture.test.ts | 9 +++- tests/Warship.test.ts | 2 + 20 files changed, 128 insertions(+), 8 deletions(-) create mode 100644 resources/territory_patterns.json diff --git a/resources/territory_patterns.json b/resources/territory_patterns.json new file mode 100644 index 000000000..eb99a1102 --- /dev/null +++ b/resources/territory_patterns.json @@ -0,0 +1,45 @@ +{ + "patterns": { + "stripes": { + "tileWidth": 2, + "tileHeight": 2, + "scale": 2, + "pattern": [ + [1, 0], + [1, 0] + ] + }, + "checkerboard": { + "tileWidth": 2, + "tileHeight": 2, + "scale": 2, + "pattern": [ + [1, 0], + [0, 1] + ] + }, + "diagonal": { + "tileWidth": 16, + "tileHeight": 16, + "scale": 1, + "pattern": [ + [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1] + ] + } + } +} diff --git a/src/client/ClientGameRunner.ts b/src/client/ClientGameRunner.ts index 1b8aefe22..754d7e62e 100644 --- a/src/client/ClientGameRunner.ts +++ b/src/client/ClientGameRunner.ts @@ -39,6 +39,7 @@ import { createRenderer, GameRenderer } from "./graphics/GameRenderer"; export interface LobbyConfig { serverConfig: ServerConfig; + pattern: string; flag: string; playerName: string; clientID: ClientID; diff --git a/src/client/Main.ts b/src/client/Main.ts index 0d00af662..f7c0af249 100644 --- a/src/client/Main.ts +++ b/src/client/Main.ts @@ -208,6 +208,7 @@ class Client { { gameID: lobby.gameID, serverConfig: config, + pattern: localStorage.getItem("territoryPattern"), flag: this.flagInput.getCurrentFlag() == "xx" ? "" diff --git a/src/client/SinglePlayerModal.ts b/src/client/SinglePlayerModal.ts index 6a8438f6f..7bbd7b3ad 100644 --- a/src/client/SinglePlayerModal.ts +++ b/src/client/SinglePlayerModal.ts @@ -404,6 +404,7 @@ export class SinglePlayerModal extends LitElement { flagInput.getCurrentFlag() == "xx" ? "" : flagInput.getCurrentFlag(), + pattern: localStorage.getItem("territoryPattern"), }, ], config: { diff --git a/src/client/Transport.ts b/src/client/Transport.ts index 013b5d298..42b47e058 100644 --- a/src/client/Transport.ts +++ b/src/client/Transport.ts @@ -339,6 +339,7 @@ export class Transport { persistentID: this.lobbyConfig.persistentID, username: this.lobbyConfig.playerName, flag: this.lobbyConfig.flag, + pattern: localStorage.getItem("territoryPattern"), }), ), ); @@ -393,6 +394,7 @@ export class Transport { type: "spawn", clientID: this.lobbyConfig.clientID, flag: this.lobbyConfig.flag, + pattern: this.lobbyConfig.pattern, name: this.lobbyConfig.playerName, playerType: PlayerType.Human, x: event.cell.x, diff --git a/src/client/graphics/layers/TerritoryLayer.ts b/src/client/graphics/layers/TerritoryLayer.ts index 88ec807c3..424e5c6bf 100644 --- a/src/client/graphics/layers/TerritoryLayer.ts +++ b/src/client/graphics/layers/TerritoryLayer.ts @@ -1,5 +1,6 @@ import { PriorityQueue } from "@datastructures-js/priority-queue"; import { Colord } from "colord"; +import territory_patterns from "../../../../resources/territory_patterns.json"; import { Theme } from "../../../core/configuration/Config"; import { EventBus } from "../../../core/EventBus"; import { Cell, PlayerType, UnitType } from "../../../core/game/Game"; @@ -280,12 +281,31 @@ export class TerritoryLayer implements Layer { ); } } else { - this.paintCell( - this.game.x(tile), - this.game.y(tile), - this.theme.territoryColor(owner), - 150, - ); + const patternName = owner.pattern(); + if (!patternName) { + this.paintCell( + this.game.x(tile), + this.game.y(tile), + this.theme.territoryColor(owner), + 150, + ); + } else { + const x = this.game.x(tile); + const y = this.game.y(tile); + const baseColor = this.theme.territoryColor(owner); + const patternConfig = territory_patterns.patterns[patternName]; + + const { tileWidth, tileHeight, scale, pattern } = patternConfig; + + const px = Math.floor(x / scale) % tileWidth; + const py = Math.floor(y / scale) % tileHeight; + + const patternValue = pattern[py][px]; + + const colorToUse = + patternValue === 1 ? baseColor.darken(0.2) : baseColor; + this.paintCell(x, y, colorToUse, 150); + } } } diff --git a/src/core/GameRunner.ts b/src/core/GameRunner.ts index 8a3eba036..b15f5b24a 100644 --- a/src/core/GameRunner.ts +++ b/src/core/GameRunner.ts @@ -38,6 +38,7 @@ export async function createGameRunner( gameStart.players.map( (p) => new PlayerInfo( + p.pattern, p.flag, p.clientID == clientID ? sanitize(p.username) diff --git a/src/core/Schemas.ts b/src/core/Schemas.ts index 3fb370804..8ef5f6be9 100644 --- a/src/core/Schemas.ts +++ b/src/core/Schemas.ts @@ -185,6 +185,7 @@ export const AttackIntentSchema = BaseIntentSchema.extend({ export const SpawnIntentSchema = BaseIntentSchema.extend({ flag: z.string().nullable(), + pattern: z.string().nullable(), type: z.literal("spawn"), name: SafeString, playerType: PlayerTypeSchema, @@ -321,6 +322,7 @@ export const PlayerSchema = z.object({ clientID: ID, username: SafeString, flag: SafeString.optional(), + pattern: SafeString.optional(), }); export const GameStartInfoSchema = z.object({ @@ -397,6 +399,7 @@ export const ClientJoinMessageSchema = ClientBaseMessageSchema.extend({ lastTurn: z.number(), // The last turn the client saw. username: SafeString, flag: SafeString.nullable().optional(), + pattern: SafeString.nullable().optional(), }); export const ClientMessageSchema = z.union([ diff --git a/src/core/execution/BotSpawner.ts b/src/core/execution/BotSpawner.ts index 000768ad8..117481009 100644 --- a/src/core/execution/BotSpawner.ts +++ b/src/core/execution/BotSpawner.ts @@ -47,7 +47,14 @@ export class BotSpawner { } } return new SpawnExecution( - new PlayerInfo("", botName, PlayerType.Bot, null, this.random.nextID()), + new PlayerInfo( + null, + "", + botName, + PlayerType.Bot, + null, + this.random.nextID(), + ), tile, ); } diff --git a/src/core/execution/ExecutionManager.ts b/src/core/execution/ExecutionManager.ts index 83795f90d..e457083c4 100644 --- a/src/core/execution/ExecutionManager.ts +++ b/src/core/execution/ExecutionManager.ts @@ -124,6 +124,7 @@ export class Executor { new FakeHumanExecution( this.gameID, new PlayerInfo( + null, nation.flag || "", nation.name, PlayerType.FakeHuman, diff --git a/src/core/game/Game.ts b/src/core/game/Game.ts index cde489f37..1865d7d65 100644 --- a/src/core/game/Game.ts +++ b/src/core/game/Game.ts @@ -242,6 +242,7 @@ export class PlayerInfo { public readonly clan: string | null; constructor( + public readonly pattern: string, public readonly flag: string, public readonly name: string, public readonly playerType: PlayerType, diff --git a/src/core/game/GameUpdates.ts b/src/core/game/GameUpdates.ts index 5f78111f9..fd60e1336 100644 --- a/src/core/game/GameUpdates.ts +++ b/src/core/game/GameUpdates.ts @@ -88,6 +88,7 @@ export interface PlayerUpdate { type: GameUpdateType.Player; nameViewData?: NameViewData; clientID: ClientID; + pattern: string; flag: string; name: string; displayName: string; diff --git a/src/core/game/GameView.ts b/src/core/game/GameView.ts index 877ec19cf..1df15eb0e 100644 --- a/src/core/game/GameView.ts +++ b/src/core/game/GameView.ts @@ -177,6 +177,11 @@ export class PlayerView { flag(): string { return this.data.flag; } + + pattern(): string { + return this.data.pattern; + } + name(): string { return userSettings.anonymousNames() && this.anonymousName !== null ? this.anonymousName @@ -275,6 +280,7 @@ export class PlayerView { } info(): PlayerInfo { return new PlayerInfo( + this.pattern(), this.flag(), this.name(), this.type(), diff --git a/src/core/game/PlayerImpl.ts b/src/core/game/PlayerImpl.ts index b0f889671..e7f989776 100644 --- a/src/core/game/PlayerImpl.ts +++ b/src/core/game/PlayerImpl.ts @@ -80,6 +80,7 @@ export class PlayerImpl implements Player { public _units: UnitImpl[] = []; public _tiles: Set = new Set(); + private _pattern: string; private _flag: string; private _name: string; private _displayName: string; @@ -107,6 +108,7 @@ export class PlayerImpl implements Player { startTroops: number, private readonly _team: Team | null, ) { + this._pattern = playerInfo.pattern; this._flag = playerInfo.flag; this._name = sanitizeUsername(playerInfo.name); this._targetTroopRatio = 95n; @@ -127,6 +129,7 @@ export class PlayerImpl implements Player { return { type: GameUpdateType.Player, clientID: this.clientID(), + pattern: this.pattern(), flag: this.flag(), name: this.name(), displayName: this.displayName(), @@ -176,6 +179,10 @@ export class PlayerImpl implements Player { return this._smallID; } + pattern(): string { + return this._pattern; + } + flag(): string { return this._flag; } diff --git a/tests/Attack.test.ts b/tests/Attack.test.ts index 81f3b49d7..5df47b206 100644 --- a/tests/Attack.test.ts +++ b/tests/Attack.test.ts @@ -33,6 +33,7 @@ describe("Attack", () => { infiniteTroops: true, }); const attackerInfo = new PlayerInfo( + null, "us", "attacker dude", PlayerType.Human, @@ -41,6 +42,7 @@ describe("Attack", () => { ); game.addPlayer(attackerInfo); const defenderInfo = new PlayerInfo( + null, "us", "defender dude", PlayerType.Human, diff --git a/tests/MissileSilo.test.ts b/tests/MissileSilo.test.ts index e8e926b3d..82ebd9832 100644 --- a/tests/MissileSilo.test.ts +++ b/tests/MissileSilo.test.ts @@ -32,6 +32,7 @@ describe("MissileSilo", () => { beforeEach(async () => { game = await setup("Plains", { infiniteGold: true, instantBuild: true }); const attacker_info = new PlayerInfo( + null, "fr", "attacker_id", PlayerType.Human, diff --git a/tests/PlayerInfo.test.ts b/tests/PlayerInfo.test.ts index 9cb1275e2..a5f788e98 100644 --- a/tests/PlayerInfo.test.ts +++ b/tests/PlayerInfo.test.ts @@ -4,6 +4,7 @@ describe("PlayerInfo", () => { describe("clan", () => { test("should extract clan from name when format is [XX]Name", () => { const playerInfo = new PlayerInfo( + null, "fr", "[CL]PlayerName", PlayerType.Human, @@ -15,6 +16,7 @@ describe("PlayerInfo", () => { test("should extract clan from name when format is [XXX]Name", () => { const playerInfo = new PlayerInfo( + null, "fr", "[ABC]PlayerName", PlayerType.Human, @@ -26,6 +28,7 @@ describe("PlayerInfo", () => { test("should extract clan from name when format is [XXXX]Name", () => { const playerInfo = new PlayerInfo( + null, "fr", "[ABCD]PlayerName", PlayerType.Human, @@ -37,6 +40,7 @@ describe("PlayerInfo", () => { test("should extract clan from name when format is [XXXXX]Name", () => { const playerInfo = new PlayerInfo( + null, "fr", "[ABCDE]PlayerName", PlayerType.Human, @@ -48,6 +52,7 @@ describe("PlayerInfo", () => { test("should return null when name doesn't start with [", () => { const playerInfo = new PlayerInfo( + null, "fr", "PlayerName", PlayerType.Human, @@ -59,6 +64,7 @@ describe("PlayerInfo", () => { test("should return null when name doesn't contain ]", () => { const playerInfo = new PlayerInfo( + null, "fr", "[ABCPlayerName", PlayerType.Human, @@ -70,6 +76,7 @@ describe("PlayerInfo", () => { test("should return null when clan tag is not 2-5 uppercase letters", () => { const playerInfo = new PlayerInfo( + null, "fr", "[A]PlayerName", PlayerType.Human, @@ -81,6 +88,7 @@ describe("PlayerInfo", () => { test("should return null when clan tag contains non-uppercase letters", () => { const playerInfo = new PlayerInfo( + null, "fr", "[Abc]PlayerName", PlayerType.Human, @@ -92,6 +100,7 @@ describe("PlayerInfo", () => { test("should return null when clan tag is too long", () => { const playerInfo = new PlayerInfo( + null, "fr", "[ABCDEF]PlayerName", PlayerType.Human, diff --git a/tests/SAM.test.ts b/tests/SAM.test.ts index b3600310b..88a7b8263 100644 --- a/tests/SAM.test.ts +++ b/tests/SAM.test.ts @@ -18,6 +18,7 @@ describe("SAM", () => { beforeEach(async () => { game = await setup("Plains", { infiniteGold: true, instantBuild: true }); const defender_info = new PlayerInfo( + null, "us", "defender_id", PlayerType.Human, @@ -25,6 +26,7 @@ describe("SAM", () => { "defender_id", ); const attacker_info = new PlayerInfo( + null, "fr", "attacker_id", PlayerType.Human, diff --git a/tests/TerritoryCapture.test.ts b/tests/TerritoryCapture.test.ts index aee96aec6..150cc477c 100644 --- a/tests/TerritoryCapture.test.ts +++ b/tests/TerritoryCapture.test.ts @@ -6,7 +6,14 @@ describe("Territory management", () => { test("player owns the tile it spawns on", async () => { const game = await setup("Plains"); game.addPlayer( - new PlayerInfo("us", "test_player", PlayerType.Human, null, "test_id"), + new PlayerInfo( + null, + "us", + "test_player", + PlayerType.Human, + null, + "test_id", + ), ); const spawnTile = game.map().ref(50, 50); game.addExecution( diff --git a/tests/Warship.test.ts b/tests/Warship.test.ts index c1ca61ecf..a69fd44fd 100644 --- a/tests/Warship.test.ts +++ b/tests/Warship.test.ts @@ -21,6 +21,7 @@ describe("Warship", () => { instantBuild: true, }); const player_1_info = new PlayerInfo( + null, "us", "boat dude", PlayerType.Human, @@ -29,6 +30,7 @@ describe("Warship", () => { ); game.addPlayer(player_1_info); const player_2_info = new PlayerInfo( + null, "us", "boat dude", PlayerType.Human,