civ6-roguelike-ai
v2.1.0
Published
Civilization Roguelike strategy game for AI agents. Full TypeScript source included - read, modify, and republish your own variant.
Maintainers
Readme
civ6-roguelike-ai
A hex-grid, turn-based strategy game designed for AI agents. Full TypeScript source included -- read it, modify it, republish your own variant.
Quick Start
npm install civ6-roguelike-ai
npx tsx play.tsOr use it programmatically:
import { CivGame } from 'civ6-roguelike-ai';
const game = new CivGame('my-agent');
while (!game.isGameOver()) {
const { actions, phase } = game.getValidActions();
// Pick actions based on your strategy...
game.endTurn();
}
console.log(game.getScore());Game Rules
You manage a hex-grid city for 40 turns. Each turn:
- Shopping Phase - Buy terrain cards from the shop, place them on empty hexes
- Build - Construct improvements (farms, mines) or districts (campus, harbor) on placed tiles
- Workers - Assign population to tiles to harvest yields
- Tech Tree - Spend accumulated science/culture/faith to unlock upgrades
- End Turn - Collect yields, grow population, refresh shop
Resources
| Resource | Icon | Role |
|----------|------|------|
| Gold | gold | Buy cards, unlock hexes |
| Production | production | Build improvements & districts |
| Food | food | Feed population, surplus grows pop |
| Science | science | Accumulates for tech tree + scoring |
| Culture | culture | Accumulates for policy tree + scoring |
| Faith | faith | Accumulates for faith tree + scoring |
Factions
Cards and tech nodes are tagged with one of 5 factions. As you pick tech nodes with faction boosts, the shop increasingly offers cards of that faction.
- Maritime - Water, coast, ocean tiles
- Highland - Mountains, hills, volcanic terrain
- Pastoral - Grassland, farms, food-focused
- Wilderness - Forests, tundra, natural terrain
- Woodland - Vegetation, growth-focused
Victory
Default goal: maximize total score (science + culture + faith) in 40 turns. Configurable via IGameConfig.
Source Map
This package ships full TypeScript source. Here's what each file does:
src/data/ -- Game Data (Primary Modification Targets)
| File | What It Contains | Key Constants |
|------|-----------------|---------------|
| card-pool.ts | 36 terrain cards (T1/T2/T3), shop level configs, faction metadata | ALL_CARD_TEMPLATES, SHOP_LEVEL_CONFIGS, FACTION_META |
| tech-trees.ts | 3 tech trees x 30 nodes = 90 nodes, 17 Eureka triggers | SCIENCE_TREE, POLICY_TREE, FAITH_TREE, EUREKA_DEFS |
| improvements.ts | 17 improvements (farm, mine, trading post, ...) | IMPROVEMENT_REGISTRY, BASE_IMPROVEMENTS |
| districts.ts | 11 districts (campus, harbor, holy site, ...) | DISTRICT_REGISTRY, BASE_DISTRICTS |
| terrains.ts | 14 terrains, 11 features, 19 resources | TERRAIN_REGISTRY, FEATURE_REGISTRY, RESOURCE_REGISTRY |
src/core/ -- Types & Utilities
| File | What It Contains |
|------|-----------------|
| types.ts | All interfaces: IGameState, ITile, IYields, ITechNode, ICardTemplate, Faction, ... |
| config.ts | IGameConfig with 15+ tunable parameters, DEFAULT_CONFIG, victory goal presets |
| hex.ts | Hex math: hexNeighbors, hexDistance, hexRing, coordinate conversions |
| yields.ts | Yield arithmetic: addYields, scaleYields, emptyYields |
src/game/ -- Game Engine (Rule Modification Targets)
| File | What It Contains |
|------|-----------------|
| engine.ts | Main game engine: turn flow, resource collection, building, tech, scoring (1100 lines) |
| board.ts | Hex board: 61 tiles in 4 rings, adjacency, yield calculation, worker auto-assignment |
| shop.ts | Card generation with tier weights and faction weighting |
| tech-engine.ts | Tech node selection, effect compilation, Eureka system |
src/api/ -- AI Interface
| File | What It Contains |
|------|-----------------|
| sdk.ts | CivGame class - the main entry point for playing |
| actions.ts | Action dispatcher: JSON action -> engine method call |
| valid-actions.ts | Computes all legal actions for current state |
| state-serializer.ts | Converts game state (with Maps) to pure JSON |
| index.ts | Package entry point, re-exports CivGame and types |
CivGame API Reference
Constructor
const game = new CivGame(agentName?: string, config?: Partial<IGameConfig>);Core Methods
game.getState(): ApiGameState // Full game state as JSON
game.getValidActions(): ValidActionsResponse // All legal actions
game.act(action: ActionRequest): ActionResult // Execute any action
game.endTurn(): ActionResult // End current turnShortcut Methods
game.selectCard(cardIndex: number)
game.placeCard(q: number, r: number)
game.rerollShop()
game.buildImprovement(q, r, improvementId)
game.buildDistrict(q, r, districtId)
game.upgradeTile(q, r)
game.selectTechNode(treeId, nodeId)
game.assignWorker(q, r)
game.reassignWorkers()
game.unlockHex(q, r)
game.sellTile(q, r)Query Methods
game.isGameOver(): boolean
game.getTurn(): number
game.getPhase(): string
game.getScore(): { science, culture, faith, total }
game.getGoalProgress(): { type, current, target, ratio, achieved }
game.createChallenge(): ChallengeResult
game.serialize(): string // Save game state
game.restore(json: string) // Load game stateAction Types
Actions are JSON objects with type and params:
| Action Type | Params | Phase |
|-------------|--------|-------|
| select_card | { cardIndex: number } | shopping |
| place_card | { q: number, r: number } | placing |
| deselect_card | none | placing |
| reroll_shop | none | shopping |
| toggle_shop_lock | { slotIndex: number } | shopping |
| build_improvement | { q, r, improvementId } | shopping/placing |
| build_district | { q, r, districtId } | shopping/placing |
| upgrade_tile | { q, r } | shopping/placing |
| assign_worker | { q, r } | shopping/placing |
| unassign_worker | { q, r } | shopping/placing |
| reassign_all_workers | none | shopping/placing |
| unlock_hex | { q, r } | shopping/placing |
| sell_tile | { q, r } | shopping/placing |
| select_tech_node | { treeId, nodeId } | shopping/placing |
| end_turn | none | shopping |
How to Modify
Change card stats (easiest)
Edit src/data/card-pool.ts. Each card has:
{ name: '草原', cost: 2, icon: '🌿', weight: 15, tier: 1,
description: '基础产粮地', terrainId: 'grassland', faction: 'pastoral' }Change cost, weight (shop probability), or add new cards.
Change tech tree effects
Edit src/data/tech-trees.ts. Each node:
{ id: 'sci_1_1', name: '灌溉', layer: 1, parentId: null,
description: '农场+1食物',
effects: [{ type: 'buff_improvement', targetId: 'farm', bonus: { food: 1 } }],
factionBoost: { faction: 'pastoral', amount: 1 } }Effect types: buff_improvement, buff_district, buff_terrain, buff_feature, buff_all_improvements, add_adjacency, modify_adjacency, terrain_alias, make_workable, unlock_improvement, unlock_district, reduce_cost, pattern_bonus.
Change game rules
Edit src/game/engine.ts. Key methods:
collectYields()- How resources are gathered each turncheckPopulationGrowth()- Population growth/starvation logicbuildImprovement()/buildDistrict()- Building rulesendTurn()- Turn flowgetFinalScore()- Scoring formula
Change game config
Pass config to constructor or edit src/core/config.ts:
const game = new CivGame('agent', {
maxTurns: 60, // More turns
initialGold: 20, // Start richer
shopCardCount: 6, // More cards per turn
rerollCost: 1, // Cheaper rerolls
});How to Create a Variant
- Start with this package:
mkdir my-civ6-variant && cd my-civ6-variant
npm init -y
npm install civ6-roguelike-ai
cp -r node_modules/civ6-roguelike-ai/src ./src
cp node_modules/civ6-roguelike-ai/play.ts ./play.ts
cp node_modules/civ6-roguelike-ai/tsconfig.json ./tsconfig.json- Edit
package.json:
{
"name": "@your-scope/civ6-water-meta",
"version": "1.0.0",
"description": "Maritime faction dominant variant",
"type": "module",
"main": "src/api/index.ts",
"keywords": ["civ6-roguelike", "civ6-variant"],
"files": ["src/", "play.ts", "tsconfig.json", "README.md"],
"dependencies": { "tsx": "^4.21.0", "typescript": "^5.7.0" }
}Modify the source -- change cards, tech trees, rules, anything
Test:
npx tsx play.tsPublish:
npm publish --access public
Hex Coordinate System
The board uses axial coordinates (q, r). City center is at (0, 0).
Ring 0: (0,0) -- city center (always unlocked, always worked)
Ring 1: 6 hexes -- always unlocked
Ring 2: 12 hexes -- unlock by gold or population growth
Ring 3: 18 hexes -- unlock by gold or population growth
Ring 4: 24 hexes -- unlock by gold or population growth
Total: 61 hexesNeighbors of (q, r): (q+1,r), (q-1,r), (q,r+1), (q,r-1), (q+1,r-1), (q-1,r+1).
License
MIT
