505 lines
14 KiB
Markdown
505 lines
14 KiB
Markdown
# Canopy Refactor And Rules Expansion Plan
|
|
|
|
## Goals
|
|
|
|
- Convert the game from JavaScript to TypeScript.
|
|
- Refactor the single-file implementation into small rules and UI modules.
|
|
- Fix the turn-order leverage problem with a public initiative seat draft.
|
|
- Add a public weather/effect card draft-ban phase to break symmetry.
|
|
- Add an alternate per-turn income mode with extra positional costs for balance.
|
|
|
|
## Recommended Foundation
|
|
|
|
- Use plain TypeScript with typed domain models and pure rules modules.
|
|
- Do not migrate to a board-game framework during this feature pass.
|
|
- Keep rendering simple and browser-native for now.
|
|
- Treat the game as a deterministic rules engine with DOM rendering layered on top.
|
|
|
|
Why this approach:
|
|
|
|
- The game is turn-based and rules-heavy, not entity-heavy.
|
|
- ECS-style libraries are a poor fit for this problem.
|
|
- `boardgame.io` is the main browser board-game framework, but it would expand scope too much for this refactor.
|
|
- If formal phase handling becomes painful later, `XState` is the best follow-up addition.
|
|
|
|
## Card System Goals
|
|
|
|
The weather/effect cards should:
|
|
|
|
- be readable from a TV screen
|
|
- be resolved with perfect information
|
|
- create asymmetric incentives each round
|
|
- be strong enough to disrupt mirror play
|
|
- avoid too much arithmetic or long text
|
|
- mostly modify incentives, not replace the core game
|
|
|
|
## Card Market Structure
|
|
|
|
Recommended round flow:
|
|
|
|
1. Determine round initiative order.
|
|
2. Reveal `playerCount + 2` public cards.
|
|
3. In initiative order, each player takes exactly one action:
|
|
- draft one card for the round, or
|
|
- ban one card from the row
|
|
4. Drafted cards become active global rules for the round.
|
|
5. Banned cards are removed.
|
|
6. Undrafted cards are discarded.
|
|
|
|
Recommended limits:
|
|
|
|
- Start with 8 to 14 total cards.
|
|
- Keep active cards per round low, ideally 1 to 3.
|
|
- Prefer cards with one sentence and one number.
|
|
|
|
## Candidate Weather / Effect Cards
|
|
|
|
These are written to fit a large shared screen. The title should be the main thing players read; the rule text should stay short.
|
|
|
|
### Economy / Position Cards
|
|
|
|
- **Leaf Surge**: Each leaf gains `+1` energy.
|
|
- **Branching Season**: Each branch beyond your first gives `+1` energy.
|
|
- **West Light**: Left third columns give `+1` energy.
|
|
- **East Light**: Right third columns give `+1` energy.
|
|
- **High Noon**: Center third columns give `+1` energy.
|
|
- **Sun Ladder**: Straight-up growth costs `-1` once per turn.
|
|
- **Storehouse**: Banking is enabled this round.
|
|
- **Shade Cloth**: Your covered nodes still count for `1` energy.
|
|
|
|
### Conflict / Interaction Cards
|
|
|
|
- **Sap Theft**: When your branch crosses an opponent branch, gain `+1` energy.
|
|
- **Hedge Trimmer**: Branches above the lowest player height are cut down before scoring.
|
|
- **Disease Sweep**: Before each turn, that player loses `1` energy.
|
|
- **Stalemate**: Contested columns give no energy.
|
|
- **Split Light**: Contested columns give half energy to each tied player.
|
|
- **Shared Light**: Contested columns give full energy to every tied player.
|
|
|
|
### Additional Cards Worth Considering
|
|
|
|
- **Root Pulse**: Root shifts cost `0` this round.
|
|
- **Still Soil**: Root shifts are disabled this round.
|
|
- **Thin Air**: Diagonal growth costs `+1`.
|
|
- **Tailwind**: Diagonal growth costs `-1` once per turn.
|
|
- **Canopy Tax**: The tallest branch of each player costs `+1` to extend.
|
|
- **Undergrowth**: The first growth each turn costs `0`.
|
|
- **Dry Season**: Banking loses `1` energy when used.
|
|
- **Tall Reward**: Your tallest leaf gains `+2` energy.
|
|
- **Wide Reward**: If you control the most columns, gain `+2` energy.
|
|
- **Twig Bloom**: New leaves created this round gain `+1` energy when scored.
|
|
- **Heavy Shade**: Columns with 2 or more players present count as contested.
|
|
- **Sunbreak**: Empty columns next to your branch give `+1` energy.
|
|
|
|
## Best Initial Card Set
|
|
|
|
For the first implementation, avoid the cards that require the most new geometry or crossing logic. Start with cards that reuse existing board evaluation.
|
|
|
|
### Phase 1 card set
|
|
|
|
- **Leaf Surge**: Each leaf gains `+1` energy.
|
|
- **Branching Season**: Each branch beyond your first gives `+1` energy.
|
|
- **West Light**: Left third columns give `+1` energy.
|
|
- **East Light**: Right third columns give `+1` energy.
|
|
- **High Noon**: Center third columns give `+1` energy.
|
|
- **Sun Ladder**: Straight-up growth costs `-1` once per turn.
|
|
- **Storehouse**: Banking is enabled this round.
|
|
- **Stalemate**: Contested columns give no energy.
|
|
- **Split Light**: Contested columns give half energy to each tied player.
|
|
- **Shared Light**: Contested columns give full energy to every tied player.
|
|
|
|
### Phase 2 card set
|
|
|
|
Add later after the rules core is stable:
|
|
|
|
- **Shade Cloth**
|
|
- **Sap Theft**
|
|
- **Hedge Trimmer**
|
|
- **Disease Sweep**
|
|
- **Root Pulse**
|
|
- **Still Soil**
|
|
|
|
Why split it this way:
|
|
|
|
- Phase 1 mostly changes scoring and move costs.
|
|
- Phase 2 needs new concepts like coverage protection, branch crossing checks, pruning, or per-turn drain.
|
|
|
|
## Rules Clarifications To Lock Down
|
|
|
|
### Initiative seat draft
|
|
|
|
- Players use a pure seat-selection token, not growth bidding.
|
|
- Players choose specific seat numbers.
|
|
- Seats are drafted publicly in bidding order, so no seat conflict resolution is needed.
|
|
- Bidding order is determined by setup rule:
|
|
- rotating from a random starting anchor, or
|
|
- lowest lifetime growth income first
|
|
|
|
### Lifetime growth income
|
|
|
|
Track all growth ever gained.
|
|
|
|
This should include:
|
|
|
|
- starting growth at game start
|
|
- round-end sunlight income
|
|
- per-turn sunlight income
|
|
- seat bonus growth
|
|
- card-generated growth
|
|
- random event growth like sunbeam bonuses
|
|
- recovered or protected growth if the game records that as gain
|
|
|
|
This should not double-count:
|
|
|
|
- banked energy carried into a later round after it was already counted when first gained
|
|
|
|
Implementation note:
|
|
|
|
- Add a dedicated `lifetimeGrowthIncome` stat to each player.
|
|
- Update it only at the moment growth is awarded.
|
|
|
|
### Banking
|
|
|
|
Current open issue:
|
|
|
|
- Banking should likely become conditional on a round card rather than always-on.
|
|
|
|
Recommended rule:
|
|
|
|
- By default, banking is disabled.
|
|
- `Storehouse` enables banking for that round.
|
|
|
|
Why:
|
|
|
|
- This helps reduce the economy snowball.
|
|
- It gives the weather deck a meaningful strategic lever.
|
|
|
|
### Contested columns
|
|
|
|
The current game reads column ownership by the top intercepting node. Weather cards that talk about contested columns require a clear definition.
|
|
|
|
Recommended definition:
|
|
|
|
- A column is contested if 2 or more players have a node in that column and they are tied under the active contest rule.
|
|
|
|
Open design task:
|
|
|
|
- Decide whether contest is based on equal top row, any shared presence, or special card-defined presence.
|
|
|
|
Recommended default:
|
|
|
|
- Contest means two or more players share the best topmost position for that column.
|
|
|
|
This keeps the rule closest to current sunlight logic.
|
|
|
|
### Leaves and branches
|
|
|
|
These terms need firm technical definitions before implementation.
|
|
|
|
Recommended definitions:
|
|
|
|
- `leaf`: a node with no children
|
|
- `branch`: a root-to-leaf path, or more simply for scoring, each leaf represents one branch
|
|
|
|
Recommended implementation simplification:
|
|
|
|
- Use `leaf count` instead of true branch count in the first pass.
|
|
- Rename card text if needed to match the actual rule.
|
|
|
|
Example:
|
|
|
|
- Instead of `Branching Season: Each branch beyond your first gives +1 energy`, consider `Leaf Burst: Each leaf after your first gives +1 energy`.
|
|
|
|
This is much easier to explain and compute.
|
|
|
|
### Branch crossing
|
|
|
|
`Sap Theft` needs a precise crossing rule.
|
|
|
|
Risk:
|
|
|
|
- The current data model stores branch segments, but crossing detection between diagonal edges is new logic.
|
|
|
|
Recommendation:
|
|
|
|
- Do not include crossing-based cards in phase 1.
|
|
- If added later, count only actual geometric edge intersections between different players' segments.
|
|
|
|
### Hedge Trimmer
|
|
|
|
This card is flavorful but potentially destructive and swingy.
|
|
|
|
Risks:
|
|
|
|
- Requires pruning nodes and edges from the board.
|
|
- Can invalidate pending move assumptions.
|
|
- Needs a clear “same level” definition.
|
|
|
|
Recommendation:
|
|
|
|
- Delay to phase 2.
|
|
- Define it as: `Before scoring, all nodes above the shortest tallest-player height are removed.`
|
|
- Only apply between turns or at round end, never mid-turn.
|
|
|
|
### Disease Sweep
|
|
|
|
This can be implemented in more than one way.
|
|
|
|
Choices:
|
|
|
|
- lose growth before each turn
|
|
- lose sunlight before end-of-round tally
|
|
- kill twigs as the current disease event does
|
|
|
|
Recommendation:
|
|
|
|
- For a card, use the simple economy version: `Before each turn, that player loses 1 energy.`
|
|
- Keep the existing random disease event separate.
|
|
|
|
## Implementation Risks
|
|
|
|
### Player identity and turn order are currently coupled
|
|
|
|
Current risk:
|
|
|
|
- `player.id` is treated as both a stable identity and an array index.
|
|
|
|
Plan:
|
|
|
|
- Keep `players` in stable id order.
|
|
- Introduce `turnOrder: PlayerId[]` for seats and active order.
|
|
- Update turn flow and render helpers to use `turnOrder` where needed.
|
|
|
|
### The game needs explicit phases
|
|
|
|
Current risk:
|
|
|
|
- The code uses ad hoc flow control plus animation locks.
|
|
|
|
Plan:
|
|
|
|
- Add a typed phase model:
|
|
- `initiative_bid`
|
|
- `weather_draft`
|
|
- `turn`
|
|
- `round_end`
|
|
- `game_over`
|
|
|
|
### The code is currently one large file
|
|
|
|
Current risk:
|
|
|
|
- Rules, state, rendering, and DOM events are tightly mixed.
|
|
|
|
Plan:
|
|
|
|
- Convert to TypeScript first.
|
|
- Extract pure rules before feature work.
|
|
|
|
### Per-turn income changes the economy deeply
|
|
|
|
Current risk:
|
|
|
|
- Existing logic assumes income is only awarded between rounds.
|
|
|
|
Plan:
|
|
|
|
- Keep `round_end` income as the stable default.
|
|
- Add `per_turn` as an alternate rules path after the initiative and weather phases are working.
|
|
- Add positional surcharges to slow runaway chaining.
|
|
|
|
### TV readability
|
|
|
|
Current risk:
|
|
|
|
- Too many active modifiers will be hard to track.
|
|
|
|
Plan:
|
|
|
|
- Cap active cards per round.
|
|
- Keep card titles short.
|
|
- Show active effects in one compact panel with icons or one-line summaries.
|
|
|
|
### Banking may conflict with weather cards
|
|
|
|
Current risk:
|
|
|
|
- If banking is always available and also appears on a card, the card has no value.
|
|
|
|
Plan:
|
|
|
|
- Make banking conditional on round effects, or convert the card into a banking bonus instead of availability.
|
|
|
|
### Randomness vs strategy
|
|
|
|
Current risk:
|
|
|
|
- Too much randomness in the weather row can obscure good play.
|
|
|
|
Plan:
|
|
|
|
- Start with a small curated deck.
|
|
- Reveal a modest row each round.
|
|
- Consider a rotating deck instead of full shuffle if needed after testing.
|
|
|
|
## Proposed TypeScript Module Structure
|
|
|
|
- `src/main.ts`
|
|
- `src/constants.ts`
|
|
- `src/state/types.ts`
|
|
- `src/state/createGame.ts`
|
|
- `src/state/reducer.ts`
|
|
- `src/rules/board.ts`
|
|
- `src/rules/moves.ts`
|
|
- `src/rules/scoring.ts`
|
|
- `src/rules/initiative.ts`
|
|
- `src/rules/weather.ts`
|
|
- `src/rules/turnFlow.ts`
|
|
- `src/ui/renderBoard.ts`
|
|
- `src/ui/renderSidebar.ts`
|
|
- `src/ui/renderScoreboard.ts`
|
|
- `src/ui/renderModals.ts`
|
|
- `src/app/events.ts`
|
|
|
|
## Proposed Type Model
|
|
|
|
Core types to introduce early:
|
|
|
|
- `PlayerId`
|
|
- `NodeKey`
|
|
- `Setup`
|
|
- `GameState`
|
|
- `Player`
|
|
- `Phase`
|
|
- `TurnMove`
|
|
- `WeatherCardId`
|
|
- `WeatherCardDefinition`
|
|
- `ActiveRoundEffect`
|
|
|
|
New setup fields:
|
|
|
|
- `initiativeMode: "fixed" | "bid"`
|
|
- `biddingOrderRule: "rotating" | "lowest_growth_income"`
|
|
- `incomeTiming: "round_end" | "per_turn"`
|
|
- `weatherDraftEnabled: boolean`
|
|
|
|
New player fields:
|
|
|
|
- `lifetimeGrowthIncome: number`
|
|
|
|
New game state fields:
|
|
|
|
- `phase`
|
|
- `turnOrder`
|
|
- `seatChoices`
|
|
- `initiativeAnchorPlayerId`
|
|
- `weatherRow`
|
|
- `draftedWeather`
|
|
- `bannedWeather`
|
|
- `activeRoundEffects`
|
|
|
|
## Execution Order
|
|
|
|
### Milestone 1: TypeScript conversion and safe refactor
|
|
|
|
1. Add TypeScript and config.
|
|
2. Convert `src/main.js` to `src/main.ts` without changing behavior.
|
|
3. Extract constants and types.
|
|
4. Extract pure board, move, and scoring helpers.
|
|
5. Keep current gameplay working exactly as-is.
|
|
|
|
### Milestone 2: Turn-order refactor
|
|
|
|
1. Separate stable player identity from seat order.
|
|
2. Introduce `turnOrder`.
|
|
3. Update active-player logic to walk `turnOrder`.
|
|
4. Add explicit `phase` handling.
|
|
|
|
### Milestone 3: Initiative draft phase
|
|
|
|
1. Add setup controls for initiative mode and bidding-order rule.
|
|
2. Add lifetime growth income tracking.
|
|
3. Implement rotating bid order.
|
|
4. Implement lowest-growth-income bid order.
|
|
5. Add public seat selection modal.
|
|
6. Apply seat growth bonuses.
|
|
|
|
### Milestone 4: Weather draft-ban phase
|
|
|
|
1. Define weather card data in `rules/weather.ts`.
|
|
2. Add setup toggle for weather phase.
|
|
3. Implement weather row generation.
|
|
4. Implement draft-or-ban action in seat order.
|
|
5. Render active effects clearly.
|
|
6. Apply phase 1 weather cards in scoring and move cost logic.
|
|
|
|
### Milestone 5: Per-turn income mode
|
|
|
|
1. Add setup control for income timing.
|
|
2. Implement payout to the active player immediately after their turn.
|
|
3. Keep round-end mode intact.
|
|
4. Verify banking and pass logic still make sense.
|
|
|
|
### Milestone 6: Distance-based balancing
|
|
|
|
1. Add root-distance surcharge support.
|
|
2. Start with a simple formula such as:
|
|
- `extraCost = floor((abs(dx) + abs(dy)) / 4)`
|
|
3. Apply only in `per_turn` mode at first.
|
|
4. Tune after playtesting.
|
|
|
|
### Milestone 7: Phase 2 weather cards
|
|
|
|
1. Add crossing-based cards if still desired.
|
|
2. Add hedge-trimming/pruning cards.
|
|
3. Add disease drain cards.
|
|
4. Rebalance text and values based on playtests.
|
|
|
|
## Testing Plan
|
|
|
|
Add rule-level tests as soon as the core logic is modularized.
|
|
|
|
Priority coverage:
|
|
|
|
- legal growth moves
|
|
- root shift legality
|
|
- scoring by column
|
|
- contested column resolution modes
|
|
- seat draft ordering
|
|
- lifetime growth income tracking
|
|
- weather draft resolution
|
|
- weather effect application
|
|
- per-turn payout behavior
|
|
- distance surcharge cost calculation
|
|
|
|
## Recommended First Shipping Configuration
|
|
|
|
- initiative mode: `bid`
|
|
- bidding order rule: `rotating`
|
|
- income timing: `round_end`
|
|
- weather draft: `enabled`
|
|
- banking: enabled only through `Storehouse`
|
|
- weather deck: only phase 1 cards
|
|
|
|
Why this first:
|
|
|
|
- It directly attacks the symmetry problem.
|
|
- It fixes turn-order leverage without also changing all economy timing at once.
|
|
- It keeps the new concepts readable for group play.
|
|
|
|
## Summary
|
|
|
|
The most important design shift is this:
|
|
|
|
- turn order becomes a public seat draft
|
|
- each round gains a public asymmetric card market
|
|
- mirror play is no longer automatically safe because players can deny and reshape incentives before growth begins
|
|
|
|
The best implementation strategy is:
|
|
|
|
- TypeScript first
|
|
- modular rules second
|
|
- initiative phase third
|
|
- weather card system fourth
|
|
- per-turn economy mode after the game flow is stable
|