Skip to content

Commit 32d71e7

Browse files
keanji-xclaude
andcommitted
fix: sync frontend & chain with new contract interface
- Add allHexKeys/hexExists to GameEngine for global hex enumeration - Add getAllHexKeys ABI to mcp-server, update getWorld to return all hexes - Fix frontend useGameEngine to match new getHex return signature - Update Phaser scenes, CartoonTextures, LocationCluster for new hex data - Update BuildingTags and WorldLayout for reduced slot count Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 9dd76a3 commit 32d71e7

9 files changed

Lines changed: 138 additions & 107 deletions

File tree

contracts/src/GameEngine.sol

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ contract GameEngine is Initializable, OwnableUpgradeable, UUPSUpgradeable {
5656
mapping(bytes32 => Hex) public hexes;
5757
mapping(uint256 => bytes32[]) public agentHexKeys; // agentId → owned hex keys
5858
mapping(uint256 => uint256) public hexCount; // agentId → owned hex count
59+
bytes32[] public allHexKeys; // global list of all ever-created hex keys
60+
mapping(bytes32 => bool) public hexExists; // dedup guard for allHexKeys
5961

6062
/// @notice Agent-level ore pool. All hex production flows here.
6163
mapping(uint256 => uint256) public orePool;
@@ -185,6 +187,10 @@ contract GameEngine is Initializable, OwnableUpgradeable, UUPSUpgradeable {
185187
h.reserve = INITIAL_RESERVE;
186188
h.happiness = MAX_HAPPINESS;
187189
h.happinessUpdatedAt = block.timestamp;
190+
if (!hexExists[key]) {
191+
allHexKeys.push(key);
192+
hexExists[key] = true;
193+
}
188194
}
189195

190196
// ══════════════════════════════════════════════════════════
@@ -402,6 +408,10 @@ contract GameEngine is Initializable, OwnableUpgradeable, UUPSUpgradeable {
402408
return agentHexKeys[agentId];
403409
}
404410

411+
function getAllHexKeys() external view returns (bytes32[] memory) {
412+
return allHexKeys;
413+
}
414+
405415

406416
// ══════════════════════════════════════════════════════════
407417
// RAID (composite attack)

frontend/src/game/BuildingTags.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,16 @@ export function allBuildingTextureKeys(): { key: string; file: string }[] {
2929
return result;
3030
}
3131

32-
/** Resolve a location name to a building texture key. */
33-
export function buildingKeyForLocation(name: string): string {
32+
/** Resolve a location name to a building ID (e.g. 'medieval_mine'). */
33+
export function buildingIdForLocation(name: string): string {
3434
const lower = name.toLowerCase();
3535
for (const [keyword, tile] of Object.entries(TAG_MAP)) {
36-
if (lower.includes(keyword)) return `building_${tile}`;
36+
if (lower.includes(keyword)) return tile;
3737
}
38-
return `building_${FALLBACK}`;
38+
return FALLBACK;
39+
}
40+
41+
/** Resolve a location name to a building texture key. */
42+
export function buildingKeyForLocation(name: string): string {
43+
return `building_${buildingIdForLocation(name)}`;
3944
}

frontend/src/game/world/WorldLayout.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,18 @@ export function computeWorldLayout(
2020
}
2121

2222
locArray.forEach((loc) => {
23+
const hex = locToHex[loc.id];
24+
// Only show locations that are tied to an agent's hex
25+
if (!hex || hex.ownerId <= 0) return;
26+
2327
const q = loc.q * LOCATION_SPREAD;
2428
const r = loc.r * LOCATION_SPREAD;
25-
const hex = locToHex[loc.id];
2629

2730
resolvedLocations.push({
2831
id: loc.id,
2932
name: loc.name,
30-
ownerId: hex?.ownerId ?? 0,
31-
ownerName: hex?.ownerId ? (agents[hex.ownerId]?.name ?? `#${hex.ownerId}`) : '',
33+
ownerId: hex.ownerId,
34+
ownerName: agents[hex.ownerId]?.name ?? `#${hex.ownerId}`,
3235
center: hexToPixel(q, r),
3336
centerHex: { q, r },
3437
});

frontend/src/hooks/useGameEngine.ts

Lines changed: 31 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ const GAME_ENGINE_ABI = [
3535
'function getScore(uint256) view returns (uint256)',
3636
'function hexCount(uint256) view returns (uint256)',
3737
'function getAgentHexKeys(uint256) view returns (bytes32[])',
38+
'function getAllHexKeys() view returns (bytes32[])',
3839
'function getHex(bytes32) view returns (uint256 ownerId, uint256 locationId, int32 q, int32 r, uint256 mineCount, uint256 arsenalCount, uint256 lastHarvest, uint256 reserve, uint256 happiness, uint256 happinessUpdatedAt)',
3940
'function orePool(uint256) view returns (uint256)',
4041
];
@@ -56,9 +57,7 @@ function parseBoardResult(raw: any): BoardState {
5657
}
5758

5859
export function useGameEngine() {
59-
const setAgents = useGameStore((s) => s.setAgents);
60-
const setLocations = useGameStore((s) => s.setLocations);
61-
const setHexes = useGameStore((s) => s.setHexes);
60+
const setWorldData = useGameStore((s) => s.setWorldData);
6261
const setAgentHexes = useGameStore((s) => s.setAgentHexes);
6362
const setMemories = useGameStore((s) => s.setMemories);
6463
const setLocationBoard = useGameStore((s) => s.setLocationBoard);
@@ -131,35 +130,37 @@ export function useGameEngine() {
131130
const newAgents: Record<number, Agent> = {};
132131
for (const agent of agentResults) newAgents[agent.id] = agent;
133132

134-
// Fetch hex data per agent + boards in parallel
133+
// Fetch all hex data + boards in parallel
135134
const allHexes: Record<string, HexData> = {};
136135

137136
await Promise.all([
138-
// Agent hex territories
139-
Promise.all(agentIds.map(async (aId: bigint) => {
140-
const id = Number(aId);
141-
try {
142-
const keys: string[] = await gameEngine.getAgentHexKeys(id);
143-
const hexList: HexData[] = [];
144-
for (const k of keys) {
145-
const [ownerId, locationId, q, r, mineCount, arsenalCount, lastHarvest, reserve, happiness, happinessUpdatedAt] =
146-
await gameEngine.getHex(k);
147-
const hd: HexData = {
148-
hexKey: k, ownerId: Number(ownerId), locationId: Number(locationId),
149-
q: Number(q), r: Number(r),
150-
mineCount: Number(mineCount), arsenalCount: Number(arsenalCount),
151-
lastHarvest: Number(lastHarvest),
152-
reserve: Number(reserve),
153-
happiness: Number(happiness),
154-
usedSlots: Number(mineCount) + Number(arsenalCount), totalSlots: 6,
155-
defense: Number(arsenalCount) * 5,
156-
};
157-
hexList.push(hd);
158-
allHexes[k] = hd;
137+
// All hexes (global list — includes unowned/rebelled)
138+
(async () => {
139+
const keys: string[] = await gameEngine.getAllHexKeys();
140+
const agentHexMap: Record<number, HexData[]> = {};
141+
await Promise.all(keys.map(async (k: string) => {
142+
const [ownerId, locationId, q, r, mineCount, arsenalCount, lastHarvest, reserve, happiness, happinessUpdatedAt] =
143+
await gameEngine.getHex(k);
144+
const hd: HexData = {
145+
hexKey: k, ownerId: Number(ownerId), locationId: Number(locationId),
146+
q: Number(q), r: Number(r),
147+
mineCount: Number(mineCount), arsenalCount: Number(arsenalCount),
148+
lastHarvest: Number(lastHarvest),
149+
reserve: Number(reserve),
150+
happiness: Number(happiness),
151+
usedSlots: Number(mineCount) + Number(arsenalCount), totalSlots: 6,
152+
defense: Number(arsenalCount) * 5,
153+
};
154+
allHexes[k] = hd;
155+
if (hd.ownerId > 0) {
156+
if (!agentHexMap[hd.ownerId]) agentHexMap[hd.ownerId] = [];
157+
agentHexMap[hd.ownerId].push(hd);
159158
}
160-
setAgentHexes(id, hexList);
161-
} catch { /* agent may have 0 hexes */ }
162-
})),
159+
}));
160+
for (const [id, hexList] of Object.entries(agentHexMap)) {
161+
setAgentHexes(Number(id), hexList);
162+
}
163+
})(),
163164
// Memories
164165
Promise.all(agentIds.map(async (aId: bigint) => {
165166
const id = Number(aId);
@@ -180,9 +181,7 @@ export function useGameEngine() {
180181
})),
181182
]);
182183

183-
setHexes(allHexes);
184-
setLocations(newLocs);
185-
setAgents(newAgents);
184+
setWorldData(newAgents, newLocs, allHexes);
186185
} catch (err) {
187186
console.error("RPC Error:", err);
188187
} finally {
@@ -193,5 +192,5 @@ export function useGameEngine() {
193192
pullData();
194193
const interval = setInterval(pullData, 5000);
195194
return () => clearInterval(interval);
196-
}, [setAgents, setLocations, setHexes, setAgentHexes, setMemories, setLocationBoard, setInbox]);
195+
}, [setWorldData, setAgentHexes, setMemories, setLocationBoard, setInbox]);
197196
}

frontend/src/phaser/CartoonTextures.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ const BUILDING_STYLES: Record<string, { wall: number; roof: number; accent: numb
2323
medieval_house: { wall: 0xd4b896, roof: 0xc06090, accent: 0x7b5ea7 },
2424
};
2525

26-
const AGENT_COLORS: { body: number; face: number }[] = [
26+
export const AGENT_COLORS: { body: number; face: number }[] = [
2727
{ body: 0x4a7eb5, face: 0xfff0d8 }, // blue
2828
{ body: 0x4a9e5c, face: 0xfff0d8 }, // green
2929
{ body: 0x7b5ea7, face: 0xfff0d8 }, // purple
@@ -127,13 +127,14 @@ function generateTerrainTexture(scene: Phaser.Scene, key: string, biome: string,
127127
gfx.destroy();
128128
}
129129

130-
function generateBuildingTexture(scene: Phaser.Scene, key: string, buildingId: string) {
130+
export function generateBuildingTexture(scene: Phaser.Scene, key: string, buildingId: string, roofColor?: number) {
131131
if (scene.textures.exists(key)) return;
132132

133133
const gfx = scene.add.graphics();
134134
const cx = TILE_W / 2;
135135
const cy = TILE_H / 2;
136136
const style = BUILDING_STYLES[buildingId] || BUILDING_STYLES.medieval_house;
137+
const roof = roofColor ?? style.roof;
137138

138139
// Shadow under building
139140
gfx.fillStyle(0x000000, 0.15);
@@ -169,7 +170,7 @@ function generateBuildingTexture(scene: Phaser.Scene, key: string, buildingId: s
169170
gfx.lineBetween(cx + 8.5, by + 8, cx + 8.5, by + 15);
170171

171172
// Roof
172-
gfx.fillStyle(style.roof, 1);
173+
gfx.fillStyle(roof, 1);
173174
gfx.fillTriangle(cx - bw / 2 - 6, by + 2, cx + bw / 2 + 6, by + 2, cx, by - 22);
174175

175176
// Roof highlight

frontend/src/phaser/objects/LocationCluster.ts

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import * as Phaser from 'phaser';
22
import type { ResolvedLocation } from '../../game/world/types';
3-
import { buildingKeyForLocation } from '../../game/BuildingTags';
3+
import { buildingKeyForLocation, buildingIdForLocation } from '../../game/BuildingTags';
44
import { TILE_W, TILE_H } from '../../game/world/HexGrid';
55
import { StoreBridge } from '../StoreBridge';
6+
import { generateBuildingTexture, AGENT_COLORS } from '../CartoonTextures';
67

78
const CLICK_THRESHOLD = 8;
89
const SELECT_COLOR = 0xd4a030; // warm gold
@@ -70,8 +71,18 @@ export class LocationCluster extends Phaser.GameObjects.Container {
7071
this.selectionRing.setVisible(false);
7172
this.add(this.selectionRing);
7273

73-
// Building tile
74-
const texKey = buildingKeyForLocation(loc.name);
74+
// Building tile — roof color matches owner's meeple color
75+
let texKey: string;
76+
if (loc.ownerId > 0) {
77+
const buildingId = buildingIdForLocation(loc.name);
78+
const roofColor = AGENT_COLORS[(loc.ownerId - 1) % AGENT_COLORS.length].body;
79+
texKey = `building_${buildingId}_owner${loc.ownerId}`;
80+
generateBuildingTexture(scene, texKey, buildingId, roofColor);
81+
} else {
82+
const buildingId = buildingIdForLocation(loc.name);
83+
texKey = `building_${buildingId}_unowned`;
84+
generateBuildingTexture(scene, texKey, buildingId, 0xe0d0c0);
85+
}
7586
this.tile = scene.add.image(this.cx, this.cy, texKey);
7687
this.tile.setInteractive(
7788
new Phaser.Geom.Circle(60, 70, 50),

0 commit comments

Comments
 (0)