Skip to content

Commit 880346d

Browse files
committed
CG-0MM60OEIA1YX98BY: Rename gem types to generic resource names across Feudalism
Systematic rename of all gem-specific type aliases, constants, and function names to generic resource equivalents, preparing the codebase for the medieval Irish crops theme: GemColor -> ResourceType, GemOrGold -> ResourceOrWild, GemTokens -> ResourceTokens, GemCost -> ResourceCost, GEM_COLORS -> RESOURCE_TYPES, ALL_TOKEN_COLORS -> ALL_RESOURCE_TYPES, gemAbbrev -> resourceAbbrev, gemDisplayName -> resourceDisplayName, GEM_FILL -> RESOURCE_FILL, GEM_TEXT_COLOR -> RESOURCE_TEXT_COLOR, SPGemTokens -> SPResourceTokens Updated 8 source/test files and 2 doc files. All 1616 unit tests pass, build succeeds, zero remaining references to old names.
1 parent 29e91c7 commit 880346d

File tree

10 files changed

+240
-240
lines changed

10 files changed

+240
-240
lines changed

docs/games/the-build/engine-capabilities-audit.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,7 @@ A 2-player card-drafting game with 108 custom-typed cards (8 types). Players sim
191191
A 2-player engine-building game with 90 development cards across 3 tiers, gem tokens, noble tiles, and a 15-prestige win threshold. **The closest existing example to a "buildy" resource-management game.**
192192

193193
- **Engine APIs used:** `shuffleArray`, `MultiplayerSetupOptions`, `resolveSetupOptions`, `getCurrentPlayer`, `CardGameScene`, `AiPlayer`, `pickRandom`, `TranscriptRecorderBase`, overlay helpers, `createSceneHeader`, `HelpPanel`, `SettingsPanel`
194-
- **Notable patterns:** Resource economy system (`GemTokens` with add/subtract helpers); market system (3 tier decks with 4 visible each); purchase validation with bonus discounts and gold wildcards; token limit (10) with discard mechanic; noble auto-visit; final-round trigger; `GreedyStrategy` AI with noble-progress scoring. Most relevant precedent for "The Build."
194+
- **Notable patterns:** Resource economy system (`ResourceTokens` with add/subtract helpers); market system (3 tier decks with 4 visible each); purchase validation with bonus discounts and gold wildcards; token limit (10) with discard mechanic; noble auto-visit; final-round trigger; `GreedyStrategy` AI with noble-progress scoring. Most relevant precedent for "The Build."
195195

196196
### Lost Cities (`example-games/lost-cities/`)
197197
A 2-player expedition card game with 60 custom cards (5 colors, investment multipliers + numbered 2-10), played over 3 rounds. Features two-phase turns and ascending-play constraints.
@@ -210,7 +210,7 @@ A 2-player cooperative real-time card game where players simultaneously play num
210210
## 3. Minor Extensions (< 1 day each)
211211

212212
### 3.1 Generic Resource/Token Container
213-
**What:** Extract Feudalism's `GemTokens` helper (add/subtract/canAfford/total) into a generic `ResourceBank<K extends string>` class in `src/core-engine/` that works with any set of named resource types.
213+
**What:** Extract Feudalism's `ResourceTokens` helper (add/subtract/canAfford/total) into a generic `ResourceBank<K extends string>` class in `src/core-engine/` that works with any set of named resource types.
214214
**How it helps "The Build":** A crafty/buildy game needs resource tracking (wood, stone, iron, food, etc.). A generic container avoids every game re-implementing arithmetic and validation for named resources.
215215
**Effort:** ~4 hours.
216216

docs/games/the-build/ideation-concepts.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ Combines the universally familiar tri-peaks solitaire with spatial grid building
4646
| Seeded RNG | Existing | `createSeededRng()` |
4747
| Grid/spatial placement | **NEW** | Need a `Grid<T>` abstraction for the cottage layout |
4848
| Adjacency evaluation | **NEW** | Need adjacency-based synergy resolver |
49-
| Resource tracking | Minor extension | Generalize Feudalism's `GemTokens` into `ResourceBank` |
49+
| Resource tracking | Minor extension | Generalize Feudalism's `ResourceTokens` into `ResourceBank` |
5050
| Market row | **NEW** | Need reusable `Market<T>` component |
5151
| Seasonal/round timer | Minor extension | `PhaseManager` can handle this with some extension |
5252
| Scoring system | Game-specific | Build on existing patterns |

example-games/feudalism/AiStrategy.ts

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,10 @@
88
*/
99

1010
import {
11-
type GemColor,
12-
type GemTokens,
13-
GEM_COLORS,
14-
ALL_TOKEN_COLORS,
11+
type ResourceType,
12+
type ResourceTokens,
13+
RESOURCE_TYPES,
14+
ALL_RESOURCE_TYPES,
1515
tokenCount,
1616
} from './FeudalismCards';
1717
import {
@@ -109,7 +109,7 @@ export const GreedyStrategy: FeudalismAiStrategy = {
109109

110110
const eff = effectiveCost(card.cost, bonuses);
111111
let totalNeeded = 0;
112-
for (const c of GEM_COLORS) {
112+
for (const c of RESOURCE_TYPES) {
113113
totalNeeded += Math.max(0, (eff[c] ?? 0) - tokenCount(player.tokens, c));
114114
}
115115
const score = card.points * 10 - totalNeeded;
@@ -139,7 +139,7 @@ export const GreedyStrategy: FeudalismAiStrategy = {
139139
for (const card of allCards) {
140140
const eff = effectiveCost(card.cost, bonuses);
141141
let shortfall = 0;
142-
for (const c of GEM_COLORS) {
142+
for (const c of RESOURCE_TYPES) {
143143
shortfall += Math.max(0, (eff[c] ?? 0) - tokenCount(player.tokens, c));
144144
}
145145
const value = card.points * 10 - shortfall;
@@ -218,7 +218,7 @@ export class FeudalismAiPlayer extends AiPlayerBase<FeudalismAiStrategy> {
218218
function scoreNobleProgress(
219219
session: FeudalismSession,
220220
player: FeudalismPlayerState,
221-
bonusColor: GemColor,
221+
bonusColor: ResourceType,
222222
): number {
223223
const bonuses = getBonuses(player);
224224
let bestScore = 0;
@@ -229,7 +229,7 @@ function scoreNobleProgress(
229229
// This bonus brings us closer to this noble
230230
let totalProgress = 0;
231231
let totalReq = 0;
232-
for (const c of GEM_COLORS) {
232+
for (const c of RESOURCE_TYPES) {
233233
const r = noble.requirements[c] ?? 0;
234234
totalReq += r;
235235
totalProgress += Math.min(bonuses[c] + (c === bonusColor ? 1 : 0), r);
@@ -247,9 +247,9 @@ function buildRandomDiscard(
247247
excess: number,
248248
rng: () => number,
249249
): TokenDiscard {
250-
const tokens: GemTokens = {};
250+
const tokens: ResourceTokens = {};
251251
let remaining = excess;
252-
const colors = [...ALL_TOKEN_COLORS].filter(c => tokenCount(player.tokens, c) > 0);
252+
const colors = [...ALL_RESOURCE_TYPES].filter(c => tokenCount(player.tokens, c) > 0);
253253

254254
while (remaining > 0 && colors.length > 0) {
255255
const idx = Math.floor(rng() * colors.length);
@@ -279,13 +279,13 @@ function buildSmartDiscard(
279279

280280
// Calculate usefulness of each color
281281
const usefulness: Record<string, number> = {};
282-
for (const c of ALL_TOKEN_COLORS) {
282+
for (const c of ALL_RESOURCE_TYPES) {
283283
usefulness[c] = 0;
284284
}
285285

286286
for (const card of allCards) {
287287
const eff = effectiveCost(card.cost, bonuses);
288-
for (const c of GEM_COLORS) {
288+
for (const c of RESOURCE_TYPES) {
289289
const need = (eff[c] ?? 0) - tokenCount(player.tokens, c);
290290
if (need < 0) {
291291
// We have more than needed — this color is less useful per excess token
@@ -299,10 +299,10 @@ function buildSmartDiscard(
299299
usefulness.gold = 100;
300300

301301
// Discard least useful tokens
302-
const tokens: GemTokens = {};
302+
const tokens: ResourceTokens = {};
303303
let remaining = excess;
304304

305-
const sortedColors = [...ALL_TOKEN_COLORS]
305+
const sortedColors = [...ALL_RESOURCE_TYPES]
306306
.filter(c => tokenCount(player.tokens, c) > 0)
307307
.sort((a, b) => usefulness[a] - usefulness[b]);
308308

example-games/feudalism/FeudalismCards.ts

Lines changed: 45 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
* FeudalismCards.ts
33
*
44
* Type definitions and data for the Feudalism card game:
5-
* - Gem colors and token types
5+
* - Resource types and token types
66
* - Development cards (90 total across 3 tiers)
77
* - Noble tiles (10 total)
88
* - Supply initialization
@@ -11,64 +11,64 @@
1111
*/
1212

1313
// ---------------------------------------------------------------------------
14-
// Gem types
14+
// Resource types
1515
// ---------------------------------------------------------------------------
1616

17-
/** The five gem colors plus gold (wild). */
18-
export type GemColor = 'emerald' | 'sapphire' | 'ruby' | 'diamond' | 'onyx';
19-
export type GemOrGold = GemColor | 'gold';
17+
/** The five resource types plus gold (wild). */
18+
export type ResourceType = 'emerald' | 'sapphire' | 'ruby' | 'diamond' | 'onyx';
19+
export type ResourceOrWild = ResourceType | 'gold';
2020

21-
export const GEM_COLORS: readonly GemColor[] = [
21+
export const RESOURCE_TYPES: readonly ResourceType[] = [
2222
'emerald',
2323
'sapphire',
2424
'ruby',
2525
'diamond',
2626
'onyx',
2727
] as const;
2828

29-
export const ALL_TOKEN_COLORS: readonly GemOrGold[] = [
30-
...GEM_COLORS,
29+
export const ALL_RESOURCE_TYPES: readonly ResourceOrWild[] = [
30+
...RESOURCE_TYPES,
3131
'gold',
3232
] as const;
3333

34-
/** A bag of gem/gold token counts. Missing keys imply 0. */
35-
export type GemTokens = Partial<Record<GemOrGold, number>>;
34+
/** A bag of resource/gold token counts. Missing keys imply 0. */
35+
export type ResourceTokens = Partial<Record<ResourceOrWild, number>>;
3636

37-
/** Shorthand: cost uses only gem colors (no gold in costs). */
38-
export type GemCost = Partial<Record<GemColor, number>>;
37+
/** Shorthand: cost uses only resource types (no gold in costs). */
38+
export type ResourceCost = Partial<Record<ResourceType, number>>;
3939

4040
// ---------------------------------------------------------------------------
4141
// Helper to read token counts safely
4242
// ---------------------------------------------------------------------------
4343

4444
/** Return the count for a color, defaulting to 0. */
45-
export function tokenCount(tokens: GemTokens, color: GemOrGold): number {
45+
export function tokenCount(tokens: ResourceTokens, color: ResourceOrWild): number {
4646
return tokens[color] ?? 0;
4747
}
4848

4949
/** Return the total number of tokens. */
50-
export function totalTokens(tokens: GemTokens): number {
50+
export function totalTokens(tokens: ResourceTokens): number {
5151
let sum = 0;
52-
for (const c of ALL_TOKEN_COLORS) {
52+
for (const c of ALL_RESOURCE_TYPES) {
5353
sum += tokenCount(tokens, c);
5454
}
5555
return sum;
5656
}
5757

5858
/** Add two token bags together (returns new object). */
59-
export function addTokens(a: GemTokens, b: GemTokens): GemTokens {
60-
const result: GemTokens = {};
61-
for (const c of ALL_TOKEN_COLORS) {
59+
export function addTokens(a: ResourceTokens, b: ResourceTokens): ResourceTokens {
60+
const result: ResourceTokens = {};
61+
for (const c of ALL_RESOURCE_TYPES) {
6262
const val = tokenCount(a, c) + tokenCount(b, c);
6363
if (val !== 0) result[c] = val;
6464
}
6565
return result;
6666
}
6767

6868
/** Subtract b from a (returns new object). Does NOT check for negatives. */
69-
export function subtractTokens(a: GemTokens, b: GemTokens): GemTokens {
70-
const result: GemTokens = {};
71-
for (const c of ALL_TOKEN_COLORS) {
69+
export function subtractTokens(a: ResourceTokens, b: ResourceTokens): ResourceTokens {
70+
const result: ResourceTokens = {};
71+
for (const c of ALL_RESOURCE_TYPES) {
7272
const val = tokenCount(a, c) - tokenCount(b, c);
7373
if (val !== 0) result[c] = val;
7474
}
@@ -84,8 +84,8 @@ export type Tier = 1 | 2 | 3;
8484
export interface DevelopmentCard {
8585
readonly id: number;
8686
readonly tier: Tier;
87-
readonly cost: GemCost;
88-
readonly bonus: GemColor;
87+
readonly cost: ResourceCost;
88+
readonly bonus: ResourceType;
8989
readonly points: number;
9090
}
9191

@@ -95,8 +95,8 @@ export interface DevelopmentCard {
9595

9696
export interface NobleTile {
9797
readonly id: number;
98-
/** The gem bonus counts required from purchased cards. */
99-
readonly requirements: GemCost;
98+
/** The resource bonus counts required from purchased cards. */
99+
readonly requirements: ResourceCost;
100100
readonly points: number; // always 3
101101
}
102102

@@ -115,18 +115,18 @@ export { shuffleArray } from '../../src/card-system/Deck';
115115
// ---------------------------------------------------------------------------
116116

117117
/**
118-
* Token counts per gem color based on player count:
119-
* - 2 players: 4 of each gem, 5 gold
120-
* - 3 players: 5 of each gem, 5 gold
121-
* - 4 players: 7 of each gem, 5 gold
118+
* Token counts per resource type based on player count:
119+
* - 2 players: 4 of each resource, 5 gold
120+
* - 3 players: 5 of each resource, 5 gold
121+
* - 4 players: 7 of each resource, 5 gold
122122
*/
123-
export function createTokenSupply(playerCount: number): GemTokens {
123+
export function createTokenSupply(playerCount: number): ResourceTokens {
124124
if (playerCount < 2 || playerCount > 4) {
125125
throw new Error(`Invalid player count: ${playerCount}. Must be 2-4.`);
126126
}
127127
const gemCount = playerCount === 2 ? 4 : playerCount === 3 ? 5 : 7;
128-
const supply: GemTokens = { gold: 5 };
129-
for (const color of GEM_COLORS) {
128+
const supply: ResourceTokens = { gold: 5 };
129+
for (const color of RESOURCE_TYPES) {
130130
supply[color] = gemCount;
131131
}
132132
return supply;
@@ -150,7 +150,7 @@ export function selectNobles(
150150
// ---------------------------------------------------------------------------
151151

152152
let nextId = 1;
153-
function card(tier: Tier, bonus: GemColor, points: number, cost: GemCost): DevelopmentCard {
153+
function card(tier: Tier, bonus: ResourceType, points: number, cost: ResourceCost): DevelopmentCard {
154154
return { id: nextId++, tier, cost, bonus, points };
155155
}
156156

@@ -303,7 +303,7 @@ export const TOTAL_CARD_COUNT = 90;
303303
// ---------------------------------------------------------------------------
304304

305305
let nobleId = 1;
306-
function noble(requirements: GemCost): NobleTile {
306+
function noble(requirements: ResourceCost): NobleTile {
307307
return { id: nobleId++, requirements, points: 3 };
308308
}
309309

@@ -352,11 +352,11 @@ export const MAX_RESERVED = 3;
352352
export const MAX_TOKENS = 10;
353353

354354
// ---------------------------------------------------------------------------
355-
// Card label helpers (for UI display)
355+
// Resource label helpers (for UI display)
356356
// ---------------------------------------------------------------------------
357357

358-
/** Short abbreviation for a gem color. */
359-
export function gemAbbrev(color: GemOrGold): string {
358+
/** Short abbreviation for a resource type. */
359+
export function resourceAbbrev(color: ResourceOrWild): string {
360360
switch (color) {
361361
case 'emerald': return 'G';
362362
case 'sapphire': return 'U';
@@ -367,33 +367,33 @@ export function gemAbbrev(color: GemOrGold): string {
367367
}
368368
}
369369

370-
/** Display name for a gem color. */
371-
export function gemDisplayName(color: GemOrGold): string {
370+
/** Display name for a resource type. */
371+
export function resourceDisplayName(color: ResourceOrWild): string {
372372
return color.charAt(0).toUpperCase() + color.slice(1);
373373
}
374374

375375
/** Format a cost object as a short string, e.g. "2W 3R 1K". */
376-
export function formatCost(cost: GemCost): string {
376+
export function formatCost(cost: ResourceCost): string {
377377
const parts: string[] = [];
378-
for (const c of GEM_COLORS) {
378+
for (const c of RESOURCE_TYPES) {
379379
const n = cost[c];
380-
if (n && n > 0) parts.push(`${n}${gemAbbrev(c)}`);
380+
if (n && n > 0) parts.push(`${n}${resourceAbbrev(c)}`);
381381
}
382382
return parts.join(' ') || 'Free';
383383
}
384384

385385
/** Format a card as a display label. */
386386
export function cardLabel(card: DevelopmentCard): string {
387387
const pts = card.points > 0 ? ` [${card.points}pt]` : '';
388-
return `T${card.tier} ${gemAbbrev(card.bonus)}${pts} (${formatCost(card.cost)})`;
388+
return `T${card.tier} ${resourceAbbrev(card.bonus)}${pts} (${formatCost(card.cost)})`;
389389
}
390390

391391
/** Format a noble tile as a display label. */
392392
export function nobleLabel(noble: NobleTile): string {
393393
const reqs: string[] = [];
394-
for (const c of GEM_COLORS) {
394+
for (const c of RESOURCE_TYPES) {
395395
const n = noble.requirements[c];
396-
if (n && n > 0) reqs.push(`${n}${gemAbbrev(c)}`);
396+
if (n && n > 0) reqs.push(`${n}${resourceAbbrev(c)}`);
397397
}
398398
return `Noble [3pt] (${reqs.join(' ')})`;
399399
}

0 commit comments

Comments
 (0)