Merge pull request #87 from ilan-schemoul/nukes-stats

feat: stats system to see number of nukes sent by a player in PlayerPanel
This commit is contained in:
evanpelle
2025-03-02 13:12:41 -08:00
committed by GitHub
8 changed files with 132 additions and 17 deletions
+46 -12
View File
@@ -4,7 +4,12 @@ import { EventBus } from "../../../core/EventBus";
import { GameView, PlayerView } from "../../../core/game/GameView";
import { Layer } from "./Layer";
import { MouseUpEvent } from "../../InputHandler";
import { AllPlayers, Player, PlayerActions } from "../../../core/game/Game";
import {
AllPlayers,
Player,
PlayerActions,
UnitType,
} from "../../../core/game/Game";
import { TileRef } from "../../../core/game/GameMap";
import { renderNumber, renderTroops } from "../../Utils";
import targetIcon from "../../../../resources/images/TargetIconWhite.svg";
@@ -128,6 +133,24 @@ export class PlayerPanel extends LitElement implements Layer {
this.requestUpdate();
}
getTotalNukesSent(): number {
const stats = this.actions.interaction?.stats;
if (!stats) {
return 0;
}
let sum = 0;
const nukes = stats.sentNukes[this.g.myPlayer().id()];
if (!nukes) {
return 0;
}
for (const nukeType in nukes) {
if (nukeType != UnitType.MIRVWarhead) {
sum += nukes[nukeType];
}
}
return sum;
}
render() {
if (!this.isVisible) {
return html``;
@@ -165,7 +188,7 @@ export class PlayerPanel extends LitElement implements Layer {
<!-- Close button -->
<button
@click=${this.handleClose}
class="absolute -top-2 -right-2 w-6 h-6 flex items-center justify-center
class="absolute -top-2 -right-2 w-6 h-6 flex items-center justify-center
bg-red-500 hover:bg-red-600 text-white rounded-full
text-sm font-bold transition-colors"
>
@@ -177,7 +200,7 @@ export class PlayerPanel extends LitElement implements Layer {
<div class="flex items-center gap-1 lg:gap-2">
<div
class="px-4 h-8 lg:h-10 flex items-center justify-center
bg-opacity-50 bg-gray-700 text-opacity-90 text-white
bg-opacity-50 bg-gray-700 text-opacity-90 text-white
rounded text-sm lg:text-xl w-full"
>
${other?.name()}
@@ -212,6 +235,7 @@ export class PlayerPanel extends LitElement implements Layer {
</div>
</div>
<!-- Embargo -->
<div class="flex flex-col gap-1">
<div class="text-white text-opacity-80 text-sm px-2">
Embargo against you
@@ -221,12 +245,22 @@ export class PlayerPanel extends LitElement implements Layer {
</div>
</div>
<!-- Stats -->
<div class="flex flex-col gap-1">
<div class="text-white text-opacity-80 text-sm px-2">
Nukes sent by them to you
</div>
<div class="bg-opacity-50 bg-gray-700 rounded p-2 text-white">
${this.getTotalNukesSent()}
</div>
</div>
<!-- Action buttons -->
<div class="flex justify-center gap-2">
${canTarget
? html`<button
@click=${(e) => this.handleTargetClick(e, other)}
class="w-10 h-10 flex items-center justify-center
class="w-10 h-10 flex items-center justify-center
bg-opacity-50 bg-gray-700 hover:bg-opacity-70
text-white rounded-lg transition-colors"
>
@@ -237,8 +271,8 @@ export class PlayerPanel extends LitElement implements Layer {
? html`<button
@click=${(e) =>
this.handleBreakAllianceClick(e, myPlayer, other)}
class="w-10 h-10 flex items-center justify-center
bg-opacity-50 bg-gray-700 hover:bg-opacity-70
class="w-10 h-10 flex items-center justify-center
bg-opacity-50 bg-gray-700 hover:bg-opacity-70
text-white rounded-lg transition-colors"
>
<img
@@ -252,8 +286,8 @@ export class PlayerPanel extends LitElement implements Layer {
? html`<button
@click=${(e) =>
this.handleAllianceClick(e, myPlayer, other)}
class="w-10 h-10 flex items-center justify-center
bg-opacity-50 bg-gray-700 hover:bg-opacity-70
class="w-10 h-10 flex items-center justify-center
bg-opacity-50 bg-gray-700 hover:bg-opacity-70
text-white rounded-lg transition-colors"
>
<img src=${allianceIcon} alt="Alliance" class="w-6 h-6" />
@@ -262,8 +296,8 @@ export class PlayerPanel extends LitElement implements Layer {
${canDonate
? html`<button
@click=${(e) => this.handleDonateClick(e, myPlayer, other)}
class="w-10 h-10 flex items-center justify-center
bg-opacity-50 bg-gray-700 hover:bg-opacity-70
class="w-10 h-10 flex items-center justify-center
bg-opacity-50 bg-gray-700 hover:bg-opacity-70
text-white rounded-lg transition-colors"
>
<img src=${donateIcon} alt="Donate" class="w-6 h-6" />
@@ -272,8 +306,8 @@ export class PlayerPanel extends LitElement implements Layer {
${canSendEmoji
? html`<button
@click=${(e) => this.handleEmojiClick(e, myPlayer, other)}
class="w-10 h-10 flex items-center justify-center
bg-opacity-50 bg-gray-700 hover:bg-opacity-70
class="w-10 h-10 flex items-center justify-center
bg-opacity-50 bg-gray-700 hover:bg-opacity-70
text-white rounded-lg transition-colors"
>
<img src=${emojiIcon} alt="Emoji" class="w-6 h-6" />
+1
View File
@@ -168,6 +168,7 @@ export class GameRunner {
canBreakAlliance: player.isAlliedWith(other),
canDonate: player.canDonate(other),
canEmbargo: !player.hasEmbargoAgainst(other),
stats: this.game.stats().getPlayerStats(other.id()),
};
}
+8
View File
@@ -55,6 +55,14 @@ export class MirvExecution implements Execution {
this.pathFinder = PathFinder.Mini(mg, 10_000, true);
this.player = mg.player(this.senderID);
this.targetPlayer = this.mg.owner(this.dst);
this.mg
.stats()
.increaseNukeCount(
this.player.id(),
this.targetPlayer.id(),
UnitType.MIRV,
);
}
tick(ticks: number): void {
+11 -4
View File
@@ -8,6 +8,7 @@ import {
UnitType,
TerraNullius,
MessageType,
NukeType,
} from "../game/Game";
import { PseudoRandom } from "../PseudoRandom";
import { consolex } from "../Consolex";
@@ -22,10 +23,7 @@ export class NukeExecution implements Execution {
private random: PseudoRandom;
constructor(
private type:
| UnitType.AtomBomb
| UnitType.HydrogenBomb
| UnitType.MIRVWarhead,
private type: NukeType,
private senderID: PlayerID,
private dst: TileRef,
private src?: TileRef,
@@ -74,6 +72,14 @@ export class NukeExecution implements Execution {
target.id(),
);
}
this.mg
.stats()
.increaseNukeCount(
this.senderID,
target.id(),
this.nuke.type() as NukeType,
);
}
}
if (this.waitTicks > 0) {
@@ -157,6 +163,7 @@ export class NukeExecution implements Execution {
const prev = attacked.get(mp);
attacked.set(mp, prev + 1);
}
if (this.mg.isLand(tile)) {
this.mg.setFallout(tile, true);
}
+9
View File
@@ -9,6 +9,7 @@ import {
PlayerUpdate,
UnitUpdate,
} from "./GameUpdates";
import { PlayerStats, Stats } from "./Stats";
export type PlayerID = string;
export type Tick = number;
@@ -79,6 +80,11 @@ export enum UnitType {
MIRVWarhead = "MIRV Warhead",
Construction = "Construction",
}
export type NukeType =
| UnitType.AtomBomb
| UnitType.HydrogenBomb
| UnitType.MIRVWarhead
| UnitType.MIRV;
export enum Relation {
Hostile = 0,
@@ -379,6 +385,8 @@ export interface Game extends GameMap {
nations(): Nation[];
numTilesWithFallout(): number;
// Optional as it's not initialized before the end of spawn phase
stats(): Stats;
}
export interface PlayerActions {
@@ -408,6 +416,7 @@ export interface PlayerInteraction {
canTarget: boolean;
canDonate: boolean;
canEmbargo: boolean;
stats: PlayerStats;
}
export interface EmojiMessage {
+8 -1
View File
@@ -30,7 +30,8 @@ import { UnitImpl } from "./UnitImpl";
import { consolex } from "../Consolex";
import { GameMap, GameMapImpl, TileRef, TileUpdate } from "./GameMap";
import { DefenseGrid } from "./DefensePostGrid";
import { simpleHash } from "../Util";
import { StatsImpl } from "./StatsImpl";
import { Stats } from "./Stats";
export function createGame(
gameMap: GameMap,
@@ -67,6 +68,9 @@ export class GameImpl implements Game {
private updates: GameUpdates = createGameUpdatesMap();
private defenseGrid: DefenseGrid;
// Not initialized until the game has finished spawning
private _stats: StatsImpl = new StatsImpl();
constructor(
private _map: GameMap,
private miniGameMap: GameMap,
@@ -639,6 +643,9 @@ export class GameImpl implements Game {
numTilesWithFallout(): number {
return this._map.numTilesWithFallout();
}
stats(): Stats {
return this._stats;
}
}
// Or a more dynamic approach that will catch new enum values:
+15
View File
@@ -0,0 +1,15 @@
import { NukeType, PlayerID } from "./Game";
export interface PlayerStats {
sentNukes: {
// target
[key: PlayerID]: {
[key in NukeType]: number;
};
};
}
export interface Stats {
increaseNukeCount(sender: PlayerID, target: PlayerID, type: NukeType): void;
getPlayerStats(player: PlayerID): PlayerStats;
}
+34
View File
@@ -0,0 +1,34 @@
import { NukeType, Player, PlayerID, UnitType } from "./Game";
import { PlayerStats, Stats } from "./Stats";
interface StatsInternalData {
// player
[key: PlayerID]: PlayerStats;
}
export class StatsImpl implements Stats {
data: StatsInternalData = {};
_createUserData(sender: PlayerID, target: PlayerID): void {
if (!this.data[sender]) {
this.data[sender] = { sentNukes: {} };
}
if (!this.data[sender].sentNukes[target]) {
this.data[sender].sentNukes[target] = {
[UnitType.MIRV]: 0,
[UnitType.MIRVWarhead]: 0,
[UnitType.AtomBomb]: 0,
[UnitType.HydrogenBomb]: 0,
};
}
}
increaseNukeCount(sender: PlayerID, target: PlayerID, type: NukeType): void {
this._createUserData(sender, target);
this.data[sender].sentNukes[target][type]++;
}
getPlayerStats(player: PlayerID): PlayerStats {
return this.data[player];
}
}