182 lines
8.3 KiB
Markdown
182 lines
8.3 KiB
Markdown
# Effect String Syntax
|
|
|
|
This document covers how effect strings work across the three card tables (citizens, domains, monsters), where the syntax currently diverges, and the proposed unified grammar.
|
|
|
|
---
|
|
|
|
## Current state by table
|
|
|
|
### Citizens — `special_payout_on_turn` / `special_payout_off_turn`
|
|
|
|
| Card | String | Meaning |
|
|
|---|---|---|
|
|
| Merchant | `choose g 2 m 2` | Pick one: +2g or +2m |
|
|
| Mercenary | `exchange s 1 g 2` | Pay 1s, gain 2g |
|
|
| Champion | `exchange g 1 s 4` | Pay 1g, gain 4s |
|
|
| Paladin | `exchange s 1 m 3` | Pay 1s, gain 3m |
|
|
| Butcher | `count owned_worker g 2` | Gain 2g per owned Worker citizen |
|
|
|
|
### Domains — `activation_effect`
|
|
|
|
| Card | String | Meaning |
|
|
|---|---|---|
|
|
| Ancient Tomb | `action.modify_monster_strength +3` | Prompt: add 3 to a monster's strength cost |
|
|
| Pretorius Conclave | `choose <citizens>` | Prompt: take any citizen from the board |
|
|
| Cursed Cavern | `m 4 + concurrent_flip_one_citizen` | Gain 4m; all players flip a citizen |
|
|
| Darktide Harbour | `choose <citizens where role==shadow>` | Prompt: take a shadow citizen |
|
|
| Cloudrider's Camp | `s 3 + choose <citizens where role==soldier and gold_cost<=2>` | Gain 3s; prompt: take a soldier citizen worth ≤2g |
|
|
| Wisborg | `manipulate_resources mode=self_convert pay=g:3 gain=v:3 optional=true` | Optionally pay 3g to gain 3vp |
|
|
|
|
### Domains — `passive_effect`
|
|
|
|
| Card | String | Meaning |
|
|
|---|---|---|
|
|
| Jousting Field | `harvest.gain_per_owned_citizen_name Knight g 1` | Harvest phase: gain 1g per Knight owned |
|
|
| Foxgrove Palisade | `roll.set_one_die target=6 cost=g:2` | Roll phase: pay 2g to set a die to 6 |
|
|
| The Desert Orchid | `roll.set_one_die target=1 cost=g_per_owned_role:holy_citizen` | Roll phase: pay 1g per holy citizen to set a die to 1 |
|
|
| Emerald Stronghold | `effect.add action.emeraldstronghold` | Flag: ignore + when buying citizens |
|
|
| Pratchett's Plateau | `effect.add action.pratchettsplateau` | Flag: domains cost 1g less |
|
|
| Shelley Commons | `action.end manipulate_resources mode=pay_to_player gain=v:1 pay=g:1 optional=true` | End of action: optionally pay 1g to a player for 1vp |
|
|
| Cathedral of St Aquila | `action.end manipulate_resources mode=take_from_player take=g:1 optional=true` | End of action: optionally take 1g from a player |
|
|
| King Tower | `action.end manipulate_resources mode=pay_to_player gain=v:1 pay=m:1 optional=true` | End of action: optionally pay 1m to a player for 1vp |
|
|
| The Orb of Urdr | `action.end manipulate_resources mode=take_from_player take=m:1 optional=true` | End of action: optionally take 1m from a player |
|
|
|
|
### Monsters — `special_reward`
|
|
|
|
| Card | String | Meaning |
|
|
|---|---|---|
|
|
| Goblin Mage | `choose g 1 m 1` | Pick one: +1g or +1m |
|
|
| Goblin Bomber | `choose g 2 m 2 s 2` | Pick one: +2g, +2m, or +2s |
|
|
| Goblin King | `count area Hills g 1` | Gain 1g per Hills monster slain |
|
|
| Skeleton King | `count area Ruins g 2` | Gain 2g per Ruins monster slain |
|
|
| Bane Spider | `choose g 3 <citizens where name==Knight>` | Pick one: +3g or take a Knight citizen |
|
|
| Ettercap | `choose <citizens where gold_cost<=2>` | Take a citizen worth ≤2g |
|
|
| Spider Queen | `choose <count area Forest g 2> <citizens + v 1>` | Pick one: 2g per Forest monster slain, or take a citizen and gain 1vp |
|
|
| Satyr Mage | `choose g 5 m 5 s 5` | Pick one: +5g, +5m, or +5s |
|
|
| Troll | `count area Valley m 2` | Gain 2m per Valley monster slain |
|
|
| Dire Bear | `choose g 2 m 2` | Pick one: +2g or +2m |
|
|
| Orc Warrior | `choose <citizens where gold_cost<=3>` | Take a citizen worth ≤3g |
|
|
| Orc Batrider | `choose <citizens>` | Take any citizen |
|
|
| Orc Chieftain | `count area Mountain g 2` | Gain 2g per Mountain monster slain |
|
|
|
|
---
|
|
|
|
## Where the syntax diverges
|
|
|
|
### 1. Resource notation — two forms
|
|
|
|
Citizens and monsters use positional shorthand; domain KV pairs use colon notation:
|
|
|
|
```
|
|
# positional (citizens, monsters)
|
|
choose g 2 m 2
|
|
count owned_worker g 2
|
|
|
|
# colon inside KV values (domain passives)
|
|
action.end manipulate_resources mode=pay_to_player gain=v:1 pay=g:1
|
|
manipulate_resources mode=self_convert pay=g:3 gain=v:3
|
|
roll.set_one_die cost=g:2
|
|
```
|
|
|
|
### 2. `count` — same structure, different second word
|
|
|
|
The two count patterns are syntactically parallel but semantically distinct. No unification needed beyond being aware they share a parser.
|
|
|
|
```
|
|
count owned_worker g 2 # count by citizen role owned
|
|
count area Hills g 1 # count by monster area slain
|
|
```
|
|
|
|
### 3. `choose` — brackets sometimes, not always
|
|
|
|
The bracket vs no-bracket distinction does carry real meaning and is worth keeping:
|
|
|
|
```
|
|
choose g 1 m 1 # pick one of these resource amounts
|
|
choose g 3 <citizens where name==Knight> # pick a resource amount OR an entity
|
|
choose <citizens where gold_cost<=2> # pick an entity from a filtered set
|
|
```
|
|
|
|
### 4. `exchange` — only exists in citizens
|
|
|
|
No equivalent pattern in domains or monsters. Could be expressed as a compound but `exchange` is readable:
|
|
|
|
```
|
|
exchange s 1 g 2 # pay 1s, receive 2g
|
|
```
|
|
|
|
### 5. `.` is doing three different jobs
|
|
|
|
```
|
|
harvest.gain_per_owned_citizen_name ... # dot = phase separator (phase.verb)
|
|
roll.set_one_die ... # dot = phase separator (phase.verb)
|
|
action.end manipulate_resources ... # dot = phase separator, then space, then verb
|
|
action.modify_monster_strength +3 # dot = namespace separator, not timing
|
|
effect.add action.emeraldstronghold # dot = verb separator, then dot = namespace
|
|
```
|
|
|
|
### 6. `manipulate_resources` wrapper verbosity
|
|
|
|
The `mode=` value is doing the same work as a first-word verb. The wrapper adds noise:
|
|
|
|
```
|
|
# current
|
|
action.end manipulate_resources mode=pay_to_player gain=v:1 pay=g:1 optional=true
|
|
|
|
# without the wrapper — same information
|
|
action.end pay_to_player g 1 v 1 optional
|
|
```
|
|
|
|
---
|
|
|
|
## Proposed unified grammar
|
|
|
|
### Core rules
|
|
|
|
1. **`.` means phase prefix only.** The left side is always a timing trigger (`harvest`, `roll`, `action.end`). Bare verbs have no dot.
|
|
2. **Resource amounts are always positional: `g N`.** Colon notation (`g:N`) only appears inside `=` assignments in KV strings where a space would be ambiguous.
|
|
3. **`choose` uses brackets for entity picks, bare words for resource picks.** Mixed is allowed: `choose g 3 <citizens where name==Knight>`.
|
|
4. **Compound effects use ` + `.** Each leg is a self-contained effect: `m 4 + concurrent_flip_one_citizen`.
|
|
|
|
### Proposed rewrites
|
|
|
|
**Domain activation:**
|
|
|
|
```
|
|
# before → after
|
|
action.modify_monster_strength +3 → modify_monster_strength 3
|
|
m 4 + concurrent_flip_one_citizen → no change
|
|
manipulate_resources mode=self_convert pay=g:3 gain=v:3 ... → self_convert g 3 v 3 optional
|
|
choose <citizens where role==shadow> → no change
|
|
s 3 + choose <citizens where role==soldier and gold_cost<=2> → no change
|
|
```
|
|
|
|
**Domain passive:**
|
|
|
|
```
|
|
# before → after
|
|
action.end manipulate_resources mode=pay_to_player gain=v:1 pay=g:1 optional=true → action.end pay_to_player g 1 v 1 optional
|
|
action.end manipulate_resources mode=take_from_player take=g:1 optional=true → action.end take_from_player g 1 optional
|
|
action.end manipulate_resources mode=pay_to_player gain=v:1 pay=m:1 optional=true → action.end pay_to_player m 1 v 1 optional
|
|
action.end manipulate_resources mode=take_from_player take=m:1 optional=true → action.end take_from_player m 1 optional
|
|
|
|
harvest.gain_per_owned_citizen_name Knight g 1 → no change
|
|
roll.set_one_die target=6 cost=g:2 → no change
|
|
effect.add action.emeraldstronghold → no change
|
|
```
|
|
|
|
**Citizens and monsters:** no changes needed — syntax is already consistent within each table and the proposed rules codify what they already do.
|
|
|
|
---
|
|
|
|
## What stays as parsed strings vs opaque keys
|
|
|
|
All effects in the tables above are **parsed strings** — a new card with different numbers works without any code change.
|
|
|
|
The only candidates for opaque keys are effects with branching prompt logic unique to a single card that cannot be generalized with different parameters:
|
|
|
|
- `concurrent_flip_one_citizen` (Cursed Cavern) — multi-player concurrent event
|
|
- `modify_monster_strength 3` (Ancient Tomb) — board-state mutation prompt
|
|
|
|
Even these stay as strings under the unified grammar; they just dispatch to named functions rather than an inline parsing branch. The DB string is the key; the function is the implementation.
|