Skip to content
Open
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
14 changes: 10 additions & 4 deletions src/core/execution/WinCheckExecution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,9 +106,12 @@ export class WinCheckExecution implements Execution {
const timeElapsed = this.mg.elapsedGameSeconds();
const numTilesWithoutFallout =
this.mg.numLandTiles() - this.mg.numTilesWithFallout();
if (
const isTerritoryWin =
numTilesWithoutFallout > 0 &&
(max.numTilesOwned() / numTilesWithoutFallout) * 100 >
this.mg.config().percentageTilesOwnedToWin() ||
this.mg.config().percentageTilesOwnedToWin();
Comment on lines 107 to +112

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Keep the territory comparison in fixed-point arithmetic.

The positive-denominator guard is correct. Both changed conditions still use floating-point division. Compare scaled integer tile counts instead. If percentageTilesOwnedToWin() supports fractional percentages, store the threshold in a fixed integer scale first.

Suggested direction
-    const isTerritoryWin =
-      numTilesWithoutFallout > 0 &&
-      (max.numTilesOwned() / numTilesWithoutFallout) * 100 >
-        this.mg.config().percentageTilesOwnedToWin();
+    const percentageThreshold =
+      this.mg.config().percentageTilesOwnedToWin();
+    const isTerritoryWin =
+      numTilesWithoutFallout > 0 &&
+      max.numTilesOwned() * 100 >
+        numTilesWithoutFallout * percentageThreshold;

Apply the equivalent comparison to max[1] in checkWinnerTeam().

As per coding guidelines, src/core/**/*.ts must avoid floating-point math and keep the simulation deterministic.

Also applies to: 168-173

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/core/execution/WinCheckExecution.ts` around lines 107 - 112, Update the
territory-win comparisons in the current execution method and checkWinnerTeam()
to avoid floating-point division: compare scaled integer tile counts using a
fixed-point percentage scale, converting percentageTilesOwnedToWin() to that
scale first when fractional values are supported. Preserve the existing
positive-denominator guard and apply the equivalent scaled comparison to max[1].

Source: Coding guidelines

if (
isTerritoryWin ||
(this.mg.config().gameConfig().maxTimerValue !== undefined &&
timeElapsed - this.mg.config().gameConfig().maxTimerValue! * 60 >= 0) ||
timeElapsed >= WinCheckExecution.HARD_TIME_LIMIT_SECONDS
Expand Down Expand Up @@ -164,9 +167,12 @@ export class WinCheckExecution implements Execution {
const timeElapsed = this.mg.elapsedGameSeconds();
const numTilesWithoutFallout =
this.mg.numLandTiles() - this.mg.numTilesWithFallout();
const percentage = (max[1] / numTilesWithoutFallout) * 100;
const isTerritoryWin =
numTilesWithoutFallout > 0 &&
(max[1] / numTilesWithoutFallout) * 100 >
this.mg.config().percentageTilesOwnedToWin();
if (
percentage > this.mg.config().percentageTilesOwnedToWin() ||
isTerritoryWin ||
(this.mg.config().gameConfig().maxTimerValue !== undefined &&
timeElapsed - this.mg.config().gameConfig().maxTimerValue! * 60 >= 0) ||
timeElapsed >= WinCheckExecution.HARD_TIME_LIMIT_SECONDS
Expand Down
32 changes: 32 additions & 0 deletions tests/core/executions/WinCheckExecution.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,38 @@ describe("WinCheckExecution", () => {
expect(mg.setWinner).not.toHaveBeenCalled();
});

it("should not set territory winner in FFA when non-fallout tiles is zero", () => {
const player = {
numTilesOwned: vi.fn(() => 10),
name: vi.fn(() => "P1"),
};
mg.players = vi.fn(() => [player]);
mg.numLandTiles = vi.fn(() => 100);
mg.numTilesWithFallout = vi.fn(() => 100);
winCheck.checkWinnerFFA();
expect(mg.setWinner).not.toHaveBeenCalled();
});
Comment on lines +85 to +95

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Use setup() and real game state for both regression tests.

These tests replace mg methods with vi.fn() and use a partial mock. Rewrite them with setup() and drive the core simulation directly. Keep the zero non-fallout tile and positive ownership scenario, then assert the real game has no winner.

As per coding guidelines, tests under tests/**/*.test.ts must use setup() and exercise the core simulation directly instead of using mocks.

Also applies to: 97-115

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/core/executions/WinCheckExecution.test.ts` around lines 85 - 95,
Rewrite both regression tests around checkWinnerFFA to use setup() and the real
game state instead of partial mg mocks and vi.fn methods. Configure the
simulation to retain zero non-fallout tiles while a player has positive
territory ownership, invoke the core winner check, and assert that the real game
state has no winner.

Source: Coding guidelines


it("should not set territory winner in Team mode when non-fallout tiles is zero", () => {
mg.config = vi.fn(() => ({
gameConfig: vi.fn(() => ({
gameMode: GameMode.Team,
})),
percentageTilesOwnedToWin: vi.fn(() => 50),
}));
const player = {
numTilesOwned: vi.fn(() => 10),
team: vi.fn(() => ColoredTeams.Red),
name: vi.fn(() => "P1"),
};
mg.players = vi.fn(() => [player]);
mg.numLandTiles = vi.fn(() => 100);
mg.numTilesWithFallout = vi.fn(() => 100);
winCheck.init(mg, 0);
winCheck.checkWinnerTeam();
expect(mg.setWinner).not.toHaveBeenCalled();
});

it("should return false for activeDuringSpawnPhase", () => {
expect(winCheck.activeDuringSpawnPhase()).toBe(false);
});
Expand Down
Loading