1. Player has a hand of cards. Each card has one or more actions available.
2. Player chooses one action on one card.
3. Action runs.
4. Card is removed.
5. Every rule in the player's rule collection runs, in order from lowest "step" to highest. If multiple rules have the same step number, they are run in shuffled order.
6. Any changes to the rule collection requested during rule evaluation (presumably, commanded _by_ a rule) are applied after rules are applied.
7. Turn counter increments.
8. Player draws back to their hand limit. (If the deck and hand are empty, the game ends with an "end of history" error.)
9. Game outputs all visible stats, deferred messages, warning messages, other game status updates, etc.
10. Next turn.
## Abstractions
### Rule
An arbitrary function running on player data, with a name and a step it runs in. It can ask to remove itself after execution.
### RuleCollection
Bucket of rules which may be updated during the game. Responsible for running rules (in some appropriate order) for the turn.
### Card
An event the user can act on.
### CardOption
A choice the user can make in response to some event and its consequences. Actions from CardOption instances are similar to rules, although changes to the actual rule collection are applied immediately.
An arbitrary variable (or function, if it's calculated) tagged with some stuff to make it easier to display to the player. Stats can be extracted automatically (see "Stat Extraction" below).
An arbitrary struct that contains the player's stats for some set of rules. Can be thought of as "the game-specific part of the player".
### Errors and warnings
Errors wrapped in the `Warning` type, including those created by `Warningf`, represent events that should be reported (with stack traces, in debug mode) but shoudl not crash the game.
There are some special errors that Rules can use to "communicate with" the rule execution logic in RulesCollection. Other non-Warning errors crash.
### Messages
For now, strings but inconvenient. Intended to provide forwards compatibility when we eventually include some way to format text, where all the stuff written for "it's just a string" would break if not for having this extra type in the way to wrap it where we can stay compatible with "it's just a string" mode.
The function `ExtractStats` creates a stats list automatically by searching through a struct's fields and methods. The following things are recognized as stats:
* any method with a name like `StatFoo` or `HiddenStatFoo` (the latter are marked as invisible stats, which show up only in debug mode with the implementation in BasicStatsPane)
* any exported field with a type that is already a `Stat`; the `Stored[T]` and `Hidden[T]` generic types are containers for this
* any exported field tagged with `cardsim:"stat"`
* or `cardsim:"hidden"` for hidden stats. `"hiddenstat"` also works.
* you can use `"round2"` to round to two decimal places -- you can use any integer here, not just 2. works with both `float` types.
*`"hiddenround3"` (or any other number) creates a hidden rounded stat.
* To change the display name of a stat, use a separate tag phrase in addition to the stat tag, `cardsim_name:"name"`.
* For example: `cardsim:"stat" cardsim_name:"Stat Display Name"` creates a visible stat that shows up as "Stat Display Name".
*`cardsim:"hiddenround1" cardsim_name:"Hidden Rounded Stat"` creates an invisible stat that shows up, rounded to one decimal place, as "Hidden Rounded Stat".
Stat extraction can implement most or all of your type's `Stats` method for you:
```
func (e *ExampleType) Stats() []cardsim.Stat {
return cardsim.ExtractStats(e)
}
```
ExtractStats puts methods first (lexicographically), then fields (in the order they appear). You can use `cardsim.SortStats` to instead put visible stats before hidden stats, alphabetized (case-insensitive).