# Pushing

Source: https://zafnok.github.io/shuyi-engine/learn/tutorial/04-push/

This is the step where it becomes a game. You'll push a box, and win:

![The box is on the target and has turned green, and the help line says Solved.](https://zafnok.github.io/shuyi-engine/tutorial-tries/solved/before.png)

Here's the pay-off from step 3. **All of the game's new behaviour goes in the rules**. The screen
barely changes, because it was already sending commands and showing whatever the state says.

| File | What changes |
| ---- | ------------ |
| `crates/game/src/rules.rs` | Pushing, and winning |
| `crates/game/src/board.rs` | One line: the help line after a win |
| `crates/game/files/en.ron` | The words for that |
| `crates/game/tests/board.rs` | Tests of pushing and winning |

## New words for what can happen

A box can now move, or be stuck, and a level can be over. So the rules need new words. In
`rules.rs`, `Event` gains `Pushed`:

```rust
/// What happened.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
pub enum Event {
    /// The player walked to this tile.
    Walked(Pos),
    /// A box was pushed from one tile to another.
    Pushed {
        /// Where it was.
        from: Pos,
        /// Where it is now.
        to: Pos,
    },
}
```

> **Rust:** an enum's possibility can have named fields, like a struct. `Pushed { from, to }`
> says plainly which is which, where `Pushed(Pos, Pos)` would leave you to remember.

and `Refusal` swaps `Box` for two new reasons:

```rust
/// Why a command was refused.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Refusal {
    /// A wall is that way.
    Wall,
    /// The box that way has a wall or another box behind it.
    Stuck,
    /// Every box is already on a target.
    Over,
}
```

The `Display` code below it needs the same two reasons. Here the compiler helps you: as soon as
you remove `Box`, the `match` in `fmt` stops compiling, and tells you what to fix. This happens all
the time in Rust. Change a type, and follow the errors. They lead you to every place that needs
to change.

## Pushing

A box can move if the tile behind it is free. Picture the player, the box and the tile past the
box, in a line:

```rust
    fn check(&self, command: &Command) -> Result<(), Refusal> {
        if self.outcome().is_some() {
            return Err(Refusal::Over);
        }
        let Command::Step(dir) = *command;
        let to = self.player.step(dir);
        if self.is_wall(to) {
            return Err(Refusal::Wall);
        }
        let past = to.step(dir);
        if self.boxes.contains(&to) && (self.is_wall(past) || self.boxes.contains(&past)) {
            return Err(Refusal::Stuck);
        }
        Ok(())
    }
```

The new part is the middle: `to` is where the player would step, and `past` is one further. If
there's a box at `to`, and `past` holds a wall or another box, the push is refused. `&&` is "and",
`||` is "or", and the brackets group them as in arithmetic.

And `apply` moves the box first, then the player:

```rust
    fn apply(&mut self, command: &Command) -> Result<Vec<Event>, Refusal> {
        // All or nothing: a refused command changes nothing.
        self.check(command)?;
        let Command::Step(dir) = *command;
        let to = self.player.step(dir);
        let mut events = Vec::new();
        if self.boxes.remove(&to) {
            let past = to.step(dir);
            self.boxes.insert(past);
            events.push(Event::Pushed { from: to, to: past });
        }
        self.player = to;
        events.push(Event::Walked(to));
        Ok(events)
    }
```

`self.boxes.remove(&to)` removes a box from the set at `to` if there is one, and answers whether
it did. So that `if` reads: "if there was a box here, it's now one further along". Notice that
`apply` doesn't re-check anything. `check` has already said this is allowed, which is why it's the
first thing `apply` calls.

Now run it, and push the box around. Here it is after one step right:

![The player has pushed the box one tile right, and it is one tile from the target.](https://zafnok.github.io/shuyi-engine/tutorial-tries/pushed/before.png)

## Winning

> **Game idea:** the game's outcome is a question you ask of the state, not something you have
> to remember to record. No command has to remember to say "you won". There can't be a bug where
> one forgets.

Add a type for how a level ends. `Won` holds no data: it's a struct with no fields, as the title was:

```rust
/// How a level ends: only one way, every box on a target.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
pub struct Won;
```

In `TurnRules`, change `type Outcome = ();` to `type Outcome = Won;`, and answer the question:

```rust
    /// Won once every box is on a target.
    fn outcome(&self) -> Option<Won> {
        (self.boxes == self.targets()).then_some(Won)
    }
```

That's the entire win condition: the set of boxes is the same as the set of targets. A set was
a good choice here, since two sets are equal however they were filled. `.then_some(Won)` turns true
into `Some(Won)`, and false into `None`.

The start of `check` also refuses every move once the level is won, which is the `Refusal::Over`
you added.

The box turning green is already done. In step 2, `scene` worked out each box's `home`, and
`Pieces` picks the `box_home` color for it. Nobody has to tell the screen about winning, because it
only ever draws the state.

## Saying so

The board picks its help line according to the outcome. In `view`, replace the `let help` line:

```rust
        let help = if self.puzzle.outcome().is_some() {
            "board.help.won"
        } else {
            "board.help"
        };
```

and add the words to `files/en.ron`:

```ron
    // The board.
    "board.help": "{Cursor} walk · {Cancel} back",
    "board.help.won": "Solved! {Cancel} back",
```

Once again, words and colors are in their own files, so they're quick to change:

In `crates/game/files/en.ron`:

```diff
- "board.help.won": "Solved! {Cancel} back",
+ "board.help.won": "You did it! {Cancel} back",
```

![The help line starts with Solved.](https://zafnok.github.io/shuyi-engine/tutorial-tries/won-words/before.png)

![The help line starts with You did it.](https://zafnok.github.io/shuyi-engine/tutorial-tries/won-words/after.png)

In `crates/game/files/palette.ron`:

```diff
- "box_home": "#a0e07a",
+ "box_home": "#5a9bff",
```

![A box on its target is green.](https://zafnok.github.io/shuyi-engine/tutorial-tries/box-home-blue/before.png)

![A box on its target is blue.](https://zafnok.github.io/shuyi-engine/tutorial-tries/box-home-blue/after.png)

## The tests

Add these to `tests/board.rs`, with `use shuyi::core::TurnRules;` at the top so that `outcome()`
can be called:

```rust
#[test]
fn pushing_the_box_onto_the_target_wins() {
    let mut h = on_board();
    h.keys("Right");
    assert!(board(&h).puzzle.boxes().contains(&Pos::new(4, 2)));
    assert!(board(&h).puzzle.outcome().is_none());
    h.keys("Right");
    assert!(board(&h).puzzle.boxes().contains(&Pos::new(5, 2)));
    assert!(board(&h).puzzle.outcome().is_some());
}
```

```rust
#[test]
fn a_box_against_a_wall_stays_and_so_does_the_player() {
    let mut h = on_board();
    // Round the box, and push it up against the top wall.
    h.keys("Down Right Up");
    assert_eq!(board(&h).puzzle.player(), Pos::new(3, 2));
    assert!(board(&h).puzzle.boxes().contains(&Pos::new(3, 1)));
    h.keys("Up");
    assert_eq!(board(&h).puzzle.player(), Pos::new(3, 2));
}
```

Each of these plays the actual game, by pressing keys. Reading them, you can follow what's
happening on the board in your head: that's a good sign in a test. The second walks round the box,
pushes it up against the wall, and tries once more.

Level 1 is won in two moves. Try it.

## What you learned

- **Rust:** enum possibilities with named fields, `||`, and letting the compiler's errors walk you
  through a change.
- **Game idea:** the outcome is a question about the state.
- **Shuyi:** how little the screen has to do, once the rules own everything.

Next: a puzzle needs an undo.
