# Rules as commands

Source: https://zafnok.github.io/shuyi-engine/learn/book/rules-as-commands/

Most of what's hard about a game comes later: undo, saving a game in progress, a replay
of how someone got that bug, a bot that plays it, a test that doesn't need a screen,
online play. Each of those needs to rebuild or repeat the game, exactly.

You get all of them from one rule, if you keep it from the start. A game's rules are a
**state**, everything that's true right now and could change, and that state changes
**only through commands**. A **command** is plain data saying what a player asks for:
"plant a seed here". The same state and the same command always give the same events
and the same new state, on every machine, which is called being **deterministic**.
That is what replays, bug reports, bots, undo and online play all rest on.

The chain is short, and worth learning by heart. A key press becomes an **action** (the
keymap, [chapter 3](https://zafnok.github.io/shuyi-engine/learn/book/actions/index.md)), an action becomes a **command** (the
screen, [chapter 2](https://zafnok.github.io/shuyi-engine/learn/book/games-and-screens/index.md)), and a command changes the
**state** (the rules, this chapter), which the screen then shows.

A turn game sends a command for each thing a player does. A real-time game sends its
inputs once per fixed step instead, 60 steps a second, counted from whole nanoseconds
so every machine steps alike. This chapter is about turn games, until
[Rules in fixed steps](#rules-in-fixed-steps) near its end.

## The state and its commands

The garden's rules know nothing of screens, keys or the clock:

```rust
/// The garden: every plot, planted or not, where the player stands, the
/// seeds left, and its luck. Everything the rules read is here, so the
/// same state and the same command always give the same answer.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Garden {
    /// Each plot: whether something grows in it.
    plots: Vec<bool>,
    /// The plot the player stands at.
    at: usize,
    /// The seeds left.
    seeds: u32,
    /// How often a seed grows, in percent.
    grow_percent: u8,
    /// The garden's luck: the same seed, the same luck.
    rng: SimRng,
    /// The commands applied so far.
    turn: u32,
}

/// What the player asks for.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum Command {
    /// Move one plot left.
    Left,
    /// Move one plot right.
    Right,
    /// Plant a seed in the plot here.
    Plant,
}

/// What happened.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
pub enum Event {
    /// The player moved to this plot.
    Moved(usize),
    /// The seed grew in this plot.
    Grew(usize),
    /// The seed didn't grow, and is gone.
    Wilted(usize),
}

/// Why a command was refused.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Refusal {
    /// There is no plot that way.
    Edge,
    /// Something already grows here.
    Planted,
    /// The game is over.
    Over,
}

/// How the game ended.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
pub enum Outcome {
    /// Something grows in every plot.
    Won,
    /// The seeds ran out first.
    Lost,
}
```

> **Rust:** each `enum` is one of a fixed list. `Event::Moved(usize)` is a possibility
> that carries data: moved, and to which plot. `Vec<bool>` is a list of true-or-false,
> one for each plot.
>
> `Serialize` and `Deserialize`, derived here, write the code that turns a value into
> text and back. It's what lets a state be saved and hashed, as you'll see below.
> `Garden`'s fields have no `pub`, so nothing outside this file can change them: code
> elsewhere has to send a command, and the compiler enforces it.

## TurnRules

The rules implement `shuyi_core::TurnRules` for their state, the engine's **trait**,
its list of functions a turn game's rules promise to have:

```rust
impl TurnRules for Garden {
    type Command = Command;
    type Event = Event;
    type Error = Refusal;
    type Outcome = Outcome;

    fn check(&self, command: &Command) -> Result<(), Refusal> {
        if self.outcome().is_some() {
            return Err(Refusal::Over);
        }
        match command {
            Command::Left if self.at == 0 => Err(Refusal::Edge),
            Command::Right if self.at + 1 >= self.plots.len() => Err(Refusal::Edge),
            Command::Plant if self.plots[self.at] => Err(Refusal::Planted),
            _ => Ok(()),
        }
    }

    fn apply(&mut self, command: &Command) -> Result<Vec<Event>, Refusal> {
        // All or nothing: a refused command changes nothing.
        self.check(command)?;
        self.turn += 1;
        let event = match command {
            Command::Left => {
                self.at -= 1;
                Event::Moved(self.at)
            }
            Command::Right => {
                self.at += 1;
                Event::Moved(self.at)
            }
            Command::Plant => {
                self.seeds -= 1;
                // Luck comes from the state's RNG, never from outside.
                if self.rng.roll_percent() < self.grow_percent {
                    self.plots[self.at] = true;
                    Event::Grew(self.at)
                } else {
                    Event::Wilted(self.at)
                }
            }
        };
        Ok(vec![event])
    }

    fn legal_commands(&self) -> Vec<Command> {
        [Command::Left, Command::Right, Command::Plant]
            .into_iter()
            .filter(|command| self.check(command).is_ok())
            .collect()
    }

    fn outcome(&self) -> Option<Outcome> {
        if self.plots.iter().all(|&grows| grows) {
            Some(Outcome::Won)
        } else if self.seeds == 0 {
            Some(Outcome::Lost)
        } else {
            None
        }
    }
}
```

> **Rust:** the `type` lines fill in the trait's blanks: this game's command, event,
> error and outcome. `Result<(), Refusal>` is either "fine, nothing more to say",
> `Ok(())`, or a refusal, `Err(...)`.
>
> `self.check(command)?` is the line that makes `apply` all or nothing. The `?` says: if
> `check` refused, hand that refusal back now, before anything has changed. The compiler
> helps too: `check` has `&self`, so it can't change the state, and `apply` has `&mut
> self`, so it may.
>
> `Command::Left if self.at == 0 => ...` is a match arm with a guard: it only fits if
> the `if` is true too. `self.plots[self.at]` reads item number `at` from the list,
> counting from 0.
>
> `.into_iter().filter(|command| ...).collect()` goes through the three commands, keeps
> those that pass, and gathers them into a list. `.all(|&grows| grows)` asks whether
> every plot grows.

Its contract:

- **Deterministic.** Luck comes from a seeded random number generator kept in the
  state, `SimRng`. A **seed** is the number it starts from: the same seed, the same luck.
  It never comes from outside, so a game is rebuilt by replaying its commands. No clock,
  no files, no **floats** (numbers with a fractional part, whose results can differ
  between machines): integers count the same everywhere.
- **All or nothing.** `apply` either changes the state and says what happened, or
  refuses the command with a reason and changes nothing.
- `check` gives `apply`'s answer without changing anything, luck included.
- `legal_commands` lists commands `check` accepts, always in the same order.
- Once `outcome` is `Some`, the game is over and `legal_commands` is empty.

A screen sends the commands (the garden's `update`, in
[chapter 2](https://zafnok.github.io/shuyi-engine/learn/book/games-and-screens/index.md#asking-the-screen-below)) and shows the
state; it never changes the state itself. To see that the rules decide everything,
change one of them. Here, planting stops costing a seed:

In `src/rules.rs`:

```diff
- self.seeds -= 1;
+ self.seeds -= 0;
```

![After planting once, the screen says 8 seeds left.](https://zafnok.github.io/shuyi-engine/tutorial-tries/book-free-seeds/before.png)

![After planting once, the screen says 9 seeds left: planting no longer costs a seed.](https://zafnok.github.io/shuyi-engine/tutorial-tries/book-free-seeds/after.png)

That's one line of the rules. No screen and no drawing changed, and the whole game
follows. Numbers in the game's files work the same way. With a chance of 0 percent, no
seed ever grows, and the rules' `outcome` ends the game:

In `files/garden.ron`:

```diff
- grow_percent: 75,
+ grow_percent: 0,
```

![The player has pressed plant nine times on the first plot. A sprout grows there, and 8 seeds are left, as the later presses were refused.](https://zafnok.github.io/shuyi-engine/tutorial-tries/book-out-of-seeds/before.png)

![No seed ever grows. 0 seeds left, the first plot is still bare, and the help line says Out of seeds.](https://zafnok.github.io/shuyi-engine/tutorial-tries/book-out-of-seeds/after.png)

A rule's test needs no screen at all:

```rust
    #[test]
    fn planting_at_the_edge_and_moving_past_it() {
        let mut garden = Garden::new(&plan(), 7);
        // A refused command changes nothing.
        assert_eq!(garden.apply(&Command::Left), Err(Refusal::Edge));
        assert_eq!(garden, Garden::new(&plan(), 7));
        let events = garden.apply(&Command::Plant).unwrap();
        assert!(matches!(events[..], [Event::Grew(0) | Event::Wilted(0)]));
        assert_eq!(garden.seeds(), 8);
    }
```

> **Rust:** `matches!` asks whether a value fits a pattern, and `|` inside it means "or".
> `events[..]` is the whole list, seen as a slice so it can be matched.

## History and undo

A `History` is the state a game started in and every command since. Since the rules are
deterministic, that's enough: any earlier point is rebuilt by replaying, which is what
undo, rewind and saving a game in progress need. There's no need to keep a copy of the
state for each move.

```rust
    #[test]
    fn a_history_rewinds_by_replaying_its_commands() {
        let mut history = History::new(Garden::new(&plan(), 7));
        for command in [Command::Plant, Command::Right, Command::Plant] {
            history.push(command);
        }
        let now = history.state_at(history.len());
        assert_eq!(now.at(), 1);
        // Back to the game after its first command: the rest is forgotten.
        let then = history.rewind_to(1).unwrap();
        assert_eq!(then.at(), 0);
        assert_eq!(history.commands(), [Command::Plant]);
    }
```

A history is written as its first state and its commands, which is also what a record
of a play is.

## Determinism and the hash

How do you know two runs really played the same? Comparing whole states by eye doesn't
scale. A **hash** is a short number worked out from a whole value, and it changes if any
of it does.

`shuyi_core::state_hash` hashes any state that serializes, the same on every platform
and every run. Two runs that should agree compare their hashes step by step, and the
first step that differs is where to look:

```rust
    #[test]
    fn the_same_seed_and_commands_give_the_same_hash() {
        let play = |seed| {
            let mut garden = Garden::new(&plan(), seed);
            for command in [Command::Plant, Command::Right, Command::Plant] {
                garden.apply(&command).unwrap();
            }
            state_hash(&garden).unwrap()
        };
        assert_eq!(play(7), play(7));
    }
```

> **Rust:** `let play = |seed| { ... };` keeps a small nameless function in a name, so
> the test can call it twice. `.unwrap()` opens a `Result`, and stops the test if it's
> an error.

A state hashes the same each run only if it serializes the same: its maps must be
`BTreeMap` and its sets `BTreeSet`, which keep their items in order, never a hash map,
whose order changes from run to run.

## Time in rules

Rules have no clock, so they count what they have: turns, or in a real-time game, steps.
`shuyi_core::timer` has the two timers a game sets and forgets. A `Countdown` fires once
and is the cooldown too; an `Every` repeats. They live in the state, so they save, hash
and replay with it:

```rust
/// What the garden times, in its state: rain every few turns, and a watering
/// can that needs a wait between uses.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct Seasons {
    rain: Every,
    can: Countdown,
}

impl Seasons {
    /// Rain every `rain` turns, and a can ready now, then every `can` turns.
    pub fn new(rain: u32, can: u32) -> Self {
        Self {
            rain: Every::new(rain),
            can: Countdown::new_ready(can),
        }
    }

    /// Where a turn ends: whether it rains.
    pub fn end_turn(&mut self) -> bool {
        self.can.advance(1);
        self.rain.advance(1) > 0
    }

    /// Uses the can if it's ready, and says whether it was.
    pub fn water(&mut self) -> bool {
        let ready = self.can.ready();
        if ready {
            self.can.restart();
        }
        ready
    }
}
```

A turn game advances them by one where its turn ends, in `apply`. A real-time game
advances them by one in each step, stepping its rules once for each of `input.ticks()`,
so a timer fires on the same step at 30 and at 60 frames a second. `input.dt()` is the
seconds since the last frame, for what only looks, such as a glide or a blink: a rule
timed by it would change with the frame rate, and replays would stop agreeing.

## Rules in fixed steps

A real-time game's rules implement `shuyi_core::tick::TickSim` instead of `TurnRules`.
There are no commands: each step gets a `TickInput`, what each player pressed, held
and let go of since the last step, and answers with what happened. Here a bee flies
over the garden while Left or Right is held, a plot every 10 steps:

```rust
/// The game's Left and Right, by their places in its list of actions.
pub const LEFT: ActionId = ActionId(2);
/// See [`LEFT`].
pub const RIGHT: ActionId = ActionId(3);

/// A bee over the plots, in real time: it flies a plot along every 10
/// steps while Left or Right is held.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Bee {
    at: usize,
    plots: usize,
    wings: Every,
}

/// What the bee did in a step.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Buzz {
    /// It landed on this plot.
    Landed(usize),
}

impl Bee {
    /// A bee over the first of `plots` plots.
    pub fn new(plots: usize) -> Self {
        Self {
            at: 0,
            plots,
            wings: Every::new(10),
        }
    }

    /// The plot it is over.
    pub fn at(&self) -> usize {
        self.at
    }
}

impl TickSim for Bee {
    type Event = Buzz;

    fn step(&mut self, input: &TickInput) -> Vec<Buzz> {
        let left = input.is_held(0, LEFT);
        let right = input.is_held(0, RIGHT);
        if left == right {
            // Neither held, or both: it hovers.
            self.wings.reset();
            return Vec::new();
        }
        if self.wings.advance(1) == 0 {
            return Vec::new();
        }
        let last = self.plots.saturating_sub(1);
        self.at = if right {
            (self.at + 1).min(last)
        } else {
            self.at.saturating_sub(1)
        };
        vec![Buzz::Landed(self.at)]
    }

    fn hash(&self) -> u64 {
        state_hash(self).map_or(0, |hash| hash.0)
    }
}
```

> **Rust:** `ActionId(2)` is an action by its place in the game's list of actions,
> counting from 0: the rules name actions that way, so they need nothing but the
> engine's core. `left == right` is true when neither is held, and when both are.

The contract is `TurnRules`' first one: the same state and input always give the same
events and the same new state. Events are also the only way out of a step: a sound or
a picture comes from what `step` returns, never from inside it.

A screen doesn't step the rules itself. It keeps a `TickDriver` (`shuyi_ui::tick`), and
its `update` calls `driver.update(&mut self.bee, input)` with the frame's input. The
driver steps the rules as many times as the frame has steps due (`input.ticks()`, 0 to
4) and hands back each step's events with its number. Frames and steps don't line up:
on a 120 Hz screen every other frame has no step, and a late frame has several. So the
driver keeps what each frame read until a step takes it:

- each press is pressed in one step, the first one after it, and each release in a step
  after its press's, so a quick tap is never lost;
- a key's repeats never reach a step: the step reads what is held instead;
- an action pressed twice before a step waits, so the next steps have each press and
  release in turn.

A screen whose rules step calls `driver.clear()` from its `covered`, so a press made
just before a pause menu opens doesn't fire after it closes.

A **record** of a real-time run is its seed and each step's input, a `TickRecord`, with
the stamp of the build that made it. `TickDriver::recording` keeps one as the game
plays, and `tick::replay` plays it again, giving the state's hash after each step:

```rust
    #[test]
    fn the_bee_flies_a_plot_every_ten_steps_and_its_record_replays() {
        use shuyi::core::tick::{self, TickRecord};
        use shuyi::core::{ContentHash, Made};
        use shuyi::prelude::UiAction;
        use shuyi::ui::FrameInput;
        use shuyi::ui::tick::{ActionIds, TickDriver};

        // The rules' ids are the game's actions'.
        assert_eq!((UiAction::Left.id(), UiAction::Right.id()), (LEFT, RIGHT));
        let made = Made::new("garden 1.0", ContentHash(0));
        let mut bee = Bee::new(6);
        let mut driver = TickDriver::new().recording(TickRecord::new(made.clone(), 1));
        // Right held for a second, at 30 frames a second: two steps a frame.
        for _ in 0..30 {
            let frame = FrameInput::new(vec![], 1.0 / 30.0, vec![UiAction::Right]);
            driver.update(&mut bee, &frame.with_ticks(2));
        }
        assert_eq!(bee.at(), 5);
        // The record of the 60 steps replays to the same bee.
        let record = driver.take_record().unwrap();
        let mut again = Bee::new(6);
        let hashes = tick::replay(&mut again, &record, &made).unwrap();
        assert_eq!(hashes.len(), 60);
        assert_eq!(again, bee);
    }
```

> **Rust:** `use` lines inside a function name things for that function alone.
> `vec![UiAction::Right]` is the list of actions held in each of those frames.

The record holds what each step saw, after the driver handed out the presses, so it
replays the same whatever frame rate made it. A record from another build is refused,
naming what differs: a new version of the rules may step the same inputs differently.

## A rules crate of their own

A game made with `--template grid` keeps its rules in a crate of their own,
`crates/rules`, which may depend on the engine's core and plain data only. `cargo shuyi
lint` checks that fence: no floats, hash maps or clock in the rules' code
([`SH2103`](https://zafnok.github.io/shuyi-engine/learn/errors/sh2103/index.md)), and no screen, device or tool crate, nor a crate
of outside luck, under them ([`SH2104`](https://zafnok.github.io/shuyi-engine/learn/errors/sh2104/index.md)).

## When it goes wrong

**The rules ask for something a type doesn't have.** Take the `Serialize` off `Garden`,
and everything that saves or hashes it stops compiling. The message's last lines tell
you what to add:

```text
error[E0277]: the trait bound `Garden: serde::Serialize` is not satisfied
   = note: for local types consider adding `#[derive(serde::Serialize)]` to your `Garden` type
```

**A float in the rules.** Make `grow_percent` an `f32`, and the compiler stops you first,
because a type that derives `Eq`, exact equality, can't hold one, and floats don't have
that:

```text
error[E0277]: the trait bound `f32: Eq` is not satisfied
```

Not every float meets that. In a `grid` game, `cargo shuyi lint` finds the rest. In a `blank` game, with
no rules crate for it to fence, it says so under `limits`, and keeping floats out is up to you.

**A replay that disagrees.** If two runs with the same seed and commands give different
hashes, something from outside the state got in: the clock, a hash map's order, a random
number from outside `SimRng`. The first step that differs names the command to look at
([chapter 8](https://zafnok.github.io/shuyi-engine/learn/book/testing/index.md#records-and-replays)).

**Nothing happens when you press a key.** A refused command changes nothing, and the
screen above ignores the refusal with `let _ =`. So a walk into the edge is silent, as
it should be. If you expected a change, ask the rules directly in a test, and read the
refusal: `assert_eq!(garden.apply(&Command::Left), Err(Refusal::Edge))`.

## Who moves

The bots of [chapter 8](https://zafnok.github.io/shuyi-engine/learn/book/testing/index.md) play any rules that also say whose move
it is and which turn it is:

```rust
/// Who moves, for the bots: a garden has one player, so its side is `()`.
impl Sides for Garden {
    type Side = ();

    fn side_to_move(&self) {}

    fn turn(&self) -> u32 {
        self.turn
    }
}
```

> **Rust:** a garden has one player, so its side is `()`, a value holding nothing, and
> `side_to_move` has nothing to give back.
