wip first
This commit is contained in:
504
plan.md
Normal file
504
plan.md
Normal file
@@ -0,0 +1,504 @@
|
||||
# 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
|
||||
Reference in New Issue
Block a user