No description
Find a file
Pagwin b27a73bc1f Renaming and ETA added
can now rename solves for remembering what they are and they have an ETA
for starting and finishing
2026-06-19 15:32:37 -04:00
.serena initial hopefully correct model 2026-06-16 13:59:07 -04:00
.gitignore share links added 2026-06-17 23:33:12 -04:00
.python-version initial hopefully correct model 2026-06-16 13:59:07 -04:00
agents.txt added other agents 2026-06-17 14:35:29 -04:00
docker-compose.yml share links added 2026-06-17 23:33:12 -04:00
Dockerfile docker 2026-06-17 16:30:31 -04:00
example.json initial hopefully correct model 2026-06-16 13:59:07 -04:00
index.html Renaming and ETA added 2026-06-19 15:32:37 -04:00
main.py Renaming and ETA added 2026-06-19 15:32:37 -04:00
pyproject.toml initial hopefully correct model 2026-06-16 13:59:07 -04:00
README.md initial hopefully correct model 2026-06-16 13:59:07 -04:00
solve.py illegal amounts disallowed 2026-06-19 14:55:59 -04:00
uv.lock initial hopefully correct model 2026-06-16 13:59:07 -04:00

dws-solve

A planner optimizer for the Days Without Strife Faction Planner role, built on Google OR-Tools (CP-SAT). It plans Industry Actions across the game's Turns to maximize a weighted score over the end-game resources.

What it optimizes

Given:

  • the Cities a Faction controls (type, renown, foundry vats, installed upgrades),
  • the Agents available to appoint as Governors (notably the Planner with Overwork),
  • per-turn availability for cities and agents,
  • optional hard constraints (force a city's action on a turn; force an agent to govern a particular city on a turn),
  • starting resources,

it finds the sequence of per-city Industry Actions (Collect / Renovate / Upgrade / Launch Airship / Idle) that maximizes the objective.

Objective is one of two forms over the resources Renown, Luxuries, Capital, Steel, Brass, Electrum, Trade Goods, Express tickets:

  • linear: sum_n scalar_n * amount_n
  • log: sum_n scalar_n * log_mapping[n][amount_n] where log_mapping is a caller-supplied lookup table (list indexed by integer resource amount).

"Renown" is scored as the total final Renown of controlled assets (each City capped to 1..9), plus extra_renown for assets not modeled as Cities.

Usage

As a CLI (JSON in / JSON out)

uv run python solve.py input.json --time 30 -o plan.json
# or read stdin / write stdout
uv run python solve.py < input.json

As a module

from solve import Problem, City, Agent, Objective, CityType, solve

problem = Problem(
    turns=5,
    start={"capital": 3, "luxuries": 3, "steel": 3, "brass": 3, "electrum": 3},
    cities=[
        City("Aridias", CityType.HUB, renown=2),
        City("Bearhearth", CityType.FOUNDRY, renown=2,
             vat_steel=3, vat_brass=2, vat_electrum=1),
        City("Kingsland", CityType.METROPOLIS, renown=4, can_renovate=False),
    ],
    agents=[Agent("Planner", overwork=True)],
    objective=Objective(mode="linear",
                        scalars={"renown": 5, "electrum": 2, "express": 3}),
)
solution = solve(problem, max_time_seconds=30)
print(solution.objective_value, solution.final_renown_total)
for step in solution.plan:
    print(step)

Input JSON schema

{
  "turns": 5,
  "start": {"capital": 3, "luxuries": 3, "steel": 3, "brass": 3, "electrum": 3},
  "extra_renown": 0,                       // renown of non-City assets
  "tradeable_into": ["capital","luxuries","steel","brass","electrum"],
  "max_resource": 300, "max_vat": 12,      // accumulator bounds (optional)
  "cities": [
    {
      "name": "Aridias", "type": "hub", "renown": 2,
      "vat_steel": 0, "vat_brass": 0, "vat_electrum": 0,   // foundry only
      "upgrades": [],                      // already-installed upgrade keys
      "available_turns": null,             // null = all turns, or e.g. [0,2,4]
      "can_renovate": true,                // metropolis must be false
      "forced_action": {"1": "renovate"}   // turn -> action (hard constraint)
    }
  ],
  "agents": [
    {
      "name": "Planner", "overwork": true,
      "free_upgrade": false,               // e.g. Brotherhood Builder
      "bonus_trade_goods": 0,              // e.g. Baron
      "available_turns": null,
      "forced_city": {"0": "Mon1"}         // turn -> city (hard constraint)
    }
  ],
  "objective": {
    "mode": "linear",                      // or "log"
    "scalars": {"renown": 5, "electrum": 2},
    "log_mapping": {                       // required for "log" mode
      "renown": [0,0,1,2,3,4,5,6,7,8,9]
    }
  }
}

Action keys: collect, renovate, upgrade, launch, idle. City types: hub, foundry, monument, metropolis. Upgrade keys: infrastructure, harvester, fine_dining, overflow_vats, transit_authority, propaganda, fortification.

Modeled mechanics

  • Collection per city type (Hub capital/luxuries choice, Foundry vat pick, Monument/Metropolis renown + trade goods), with the 1 Capital cost.

  • Foundry vats: collecting a vat yields its level, empties it, and adds +1 to the other two (full stateful per-turn model; Overflow Vats adds +1 more).

  • Overwork (Planner Governor): doubles a city's collection that turn, waives the Capital cost, and locks that city out of collecting the next turn.

  • Upgrades with Steel costs (Infrastructure 0 / Harvester 2 / type-specific 2 / Fortification 4), Infrastructure's 1 future-cost discount, and the +1/+2 Renown each grants. Harvester, Fine Dining, Transit Authority yield effects are applied; Propaganda/Fortification military effects are not.

  • Renovation changes a city's type for subsequent turns.

  • Airship launch (7 Steel; adds +3 to the asset Renown total).

  • Resource balances are constrained non-negative every turn, so the plan is always affordable in sequence.

  • Trade Goods exchange: each Turn, Trade Goods may be converted 1-for-1 into any resource in tradeable_into; the converted resource is available that same Turn (so it can fund Upgrades/Airships). Reported under trade_conversions.

  • Wildcard governor Agents via Agent.planner() / Agent.baron() (assumes 3 Bastions → +3 Trade Goods on Collect) / Agent.builder() (free type Upgrade), or the generic overwork / bonus_trade_goods / free_upgrade flags.

Simplifications

  • Buying Trade Goods with Electrum (2.5 each) is not modeled.
  • Combat, diplomacy, espionage, bidding, and travel are out of scope — this optimizes the Planner's resource/upgrade decisions only.
  • Resource amounts are integers; fractional starting Electrum is rounded.

Output

{
  "status": "OPTIMAL",                 // or FEASIBLE / INFEASIBLE
  "objective_value": 231.0,
  "final_resources": {"capital": 2.0, ...},
  "final_renown_total": 35,
  "plan": [
    {"turn": 0, "city": "Aridias", "action": "collect",
     "detail": "hub: +2 luxuries", "governor": "", "overwork": false}
  ]
}

See example.json for a complete runnable input.