# Polish

Source: https://zafnok.github.io/shuyi-engine/learn/tutorial/08-polish/

The game is whole. This step adds what makes it pleasant: a moves counter, and a way for players to change
their keys. Both are short, because the work's already been done, by you or by the engine.

![The board with Moves: 1 at the top right.](https://zafnok.github.io/shuyi-engine/tutorial-tries/moves/before.png)

| File | What changes |
| ---- | ------------ |
| `crates/game/src/board.rs` | Shows the moves |
| `crates/game/src/options.rs` | New: which options to offer |
| `crates/game/src/levels.rs` | An Options item on the menu |
| `crates/game/src/stages.rs` | Names the two new screens |
| `crates/game/src/lib.rs` | `pub mod options;` |
| `crates/game/files/en.ron` | Words for all of it |
| `crates/game/tests/board.rs` | Tests of both |

## A moves counter

How many moves has the player made? You don't need a new variable, because you already keep that: it's
how many commands are in the history. And undo already takes one away, and a walk into a wall already
doesn't count, as it's never recorded.

> **Game idea:** a second copy of a fact is a chance for the two to disagree. If you can work a thing out from
> what you already hold, do that.

Add a `moves` field to `BoardView`, as `pub moves: String,`, and fill it in `view`:

```rust
            moves: ctx.text_with("board.moves", &[("n", &self.history.len())]),
```

> **Rust:** `("n", &self.history.len())` is a **tuple**, two values side by side, and `&[...]` is a borrowed
> list of those. This fills in `{n}` in the text.

Then draw it at the top right, in `draw`, by working out where it must start from how long it is:

```rust
        let width = i32::try_from(view.moves.chars().count()).unwrap_or(0);
        let right = i32::from(buf.width()) - width - 1;
        buf.print(right, 0, &view.moves, c(EngineColor::Text), bg);
```

Its words go in `files/en.ron`, as `"board.moves": "Moves: {n}",`.

And here's a nice thing about measuring. Use longer words, and it stays against the edge:

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

```diff
- "board.moves": "Moves: {n}",
+ "board.moves": "Steps taken: {n}",
```

![The top right says Moves: 1.](https://zafnok.github.io/shuyi-engine/tutorial-tries/moves-words/before.png)

![The top right says Steps taken: 1, and stays against the edge.](https://zafnok.github.io/shuyi-engine/tutorial-tries/moves-words/after.png)

Or centre it, by changing the sum:

In `crates/game/src/board.rs`:

```diff
- let right = i32::from(buf.width()) - width - 1;
+ let right = (i32::from(buf.width()) - width) / 2;
```

![The moves counter is at the top right.](https://zafnok.github.io/shuyi-engine/tutorial-tries/moves-left/before.png)

![The moves counter is centred at the top.](https://zafnok.github.io/shuyi-engine/tutorial-tries/moves-left/after.png)

The test presses keys, and reads the screen's text. It also shows what the counter doesn't count:

```rust
#[test]
fn the_moves_counter_counts_moves_and_undo_takes_one_back() {
    let mut h = on_board();
    assert!(h.snapshot().contains("Moves: 0"), "{}", h.snapshot());
    // Up, then a wall: the refused step is no move.
    h.keys("Up Up");
    assert!(h.snapshot().contains("Moves: 1"), "{}", h.snapshot());
    h.keys("Down u");
    assert!(h.snapshot().contains("Moves: 1"), "{}", h.snapshot());
}
```

## Options and rebinding

Letting players change their keys is a lot of work to get right: reading the new key, refusing keys
already taken, saving them, restoring the defaults. It's also just the same in every game, which makes it
perfect for an engine. You only say what you want.

![The options screen: Frame rate, Key bindings and Restore defaults.](https://zafnok.github.io/shuyi-engine/tutorial-tries/options/before.png)

Make `crates/game/src/options.rs`, which lists the rows you want, and every action a player can rebind:

```rust
use shuyi::prelude::*;
use shuyi::ui::screens::key_bindings::{KeyBindingsScreen, KeyRow, RowGroup};
use shuyi::ui::screens::options::{self, OptionRow, RowAction, RowValue};

use crate::Data;
use crate::action::Action;
```

```rust
/// The Key bindings screen's rows: every action must keep a key.
pub const KEY_ROWS: [KeyRow<Action>; 7] = [
    (Action::Up, "key_bindings.action.up", RowGroup::Required),
    (Action::Down, "key_bindings.action.down", RowGroup::Required),
    (Action::Left, "key_bindings.action.left", RowGroup::Required),
    (
        Action::Right,
        "key_bindings.action.right",
        RowGroup::Required,
    ),
    (
        Action::Confirm,
        "key_bindings.action.confirm",
        RowGroup::Required,
    ),
    (
        Action::Cancel,
        "key_bindings.action.cancel",
        RowGroup::Required,
    ),
    (Action::Undo, "key_bindings.action.undo", RowGroup::Required),
];

/// The options' rows: the frame rate, the keys, and Restore defaults.
pub fn option_rows(_: &Ctx<Data>) -> Vec<OptionRow<Data>> {
    vec![
        OptionRow::frame_cap(),
        OptionRow::new(
            "options.row.key_bindings",
            RowValue::action(
                |_| None,
                |ctx| RowAction::Open(Box::new(KeyBindingsScreen::new(ctx, &KEY_ROWS))),
                |_| options::HELP_OPEN,
            ),
        )
        .starting_group(),
        OptionRow::restore_defaults().starting_group(),
    ]
}
```

`KEY_ROWS` lists each action once, with the text key of its name. `RowGroup::Required` says it must keep a key.

`option_rows` builds the rows of the options screen. There's a frame rate, then a row that opens the Key
bindings screen, then Restore defaults. The engine makes the common ones for you (`OptionRow::frame_cap()`).
The engine has more that you could add, like text speed and volume, when your game has those things.

> **Rust:** the two `|ctx| ...` and `|_| ...` are closures again, this time saying what the row shows and
> what it does when chosen. And `.starting_group()` adds a gap above that row.

The menu gets an item for it. In `levels.rs`, `Choice` gains `Options`, and the items go on as one list:

```rust
    /// The menu's items: a text key and a value each.
    pub fn menu(&self, ctx: &Ctx<Data>) -> Menu<Choice> {
        let levels = ctx.game.own.levels.len();
        let items = (0..levels).map(|n| {
            MenuItem::keyed("levels.level")
                .with_arg("n", n + 1)
                .with_enabled(n < self.open)
                .with_value(Choice::Level(n))
        });
        let options = MenuItem::keyed("levels.options").with_value(Choice::Options);
        Menu::new(items.chain([options]).collect()).focused(self.focus)
    }
```

`items.chain([options])` joins two sequences, so the Options item comes after the levels. And in `update`,
add an arm for it:

```rust
                Some(MenuEvent::Chosen(Choice::Options)) => {
                    return Transition::Push(Box::new(OptionsScreen::new(option_rows)));
                }
```

> **Rust:** `OptionsScreen::new(option_rows)` passes the function itself, by name, without calling it: note there
> are no `()` after `option_rows`. The screen calls it when it needs its rows.

Notice you didn't have to change `Choice`'s users in a hundred places. The compiler told you which `match` wanted a
new arm, and there was only this one.

Next, `screens` in `stages.rs` names both new screens, so `cargo shuyi check --target` can check that their controls
fit each machine:

```rust
/// Every screen the game can show past the title, for the check that its
/// controls fit each target.
pub fn screens(ctx: &mut Ctx<Data>) -> Vec<Box<dyn Screen<Data>>> {
    vec![
        Box::new(LevelMenu::new(ctx)),
        Box::new(Board::new(ctx, 0)),
        Box::new(OptionsScreen::new(option_rows)),
        Box::new(KeyBindingsScreen::new(ctx, &KEY_ROWS)),
    ]
}
```

Their text goes in `files/en.ron`. The engine's screens ask for a lot of words, one key each, so a game can be
translated in full. You'll want all of them, and there's no need to type them: take them from
[this step's `en.ron`](https://github.com/Zafnok/shuyi-engine/blob/main/site/tutorial/08-polish/crates/game/files/en.ron),
as they are. If you miss one, a debug build stops and names the key it couldn't find.

```rust
#[test]
fn the_options_open_from_the_menu_and_reach_the_key_bindings() {
    let mut h = game::game().harness();
    let menu = "Enter Up";
    h.keys(menu);
    h.keys("Enter");
    assert_eq!(h.screens(), ["title", "levels", "options"]);
    h.keys("Down Enter");
    assert_eq!(h.screens(), ["title", "levels", "options", "key_bindings"]);
}
```

The `Up` in that test wraps round from the top of the menu to its last item, Options.

Run it, and open **Options**, then **Key bindings**:

![The Key bindings screen: a row for each action, with its keys in columns.](https://zafnok.github.io/shuyi-engine/tutorial-tries/key-bindings/before.png)

Now change your Undo key. Then go back to a level, and read the help line. It names your new key, and you didn't
do anything to make that happen. Every line of text has said `{Undo}` all along, since step 5. **This is why text
never names a key.**

## What you learned

- **Rust:** tuples, passing a function by name, and `.chain`.
- **Game idea:** don't keep two copies of one fact.
- **Shuyi:** the engine's options and key-binding screens, and how much a game gets free by using actions.

Next: the game can test itself.
