# Levels and a menu

Source: https://zafnok.github.io/shuyi-engine/learn/tutorial/06-levels/

One level isn't a game. This step adds four more, a list of them, and a menu to choose from:

![The level menu: Levels at the top, then Level 1 to Level 5, with the first in focus.](https://zafnok.github.io/shuyi-engine/tutorial-tries/level-menu/before.png)

The stack of screens gets a third card too, so you'll see how screens talk to one another.

| File | What changes |
| ---- | ------------ |
| `crates/game/files/levels/2.map` to `5.map` | New: four more levels |
| `crates/game/files/levels.ron` | New: the list of levels |
| `crates/game/src/level.rs` | Reads the list, and every level on it |
| `crates/game/src/levels.rs` | New: the menu screen |
| `crates/game/src/stages.rs` | `Own` holds every level, and the title starts the menu |
| `crates/game/src/board.rs` | Starts on a chosen level, and hands back a result |
| `crates/game/src/lib.rs` | `pub mod levels;` |
| `crates/game/files/en.ron` | The menu's words |
| `crates/game/tests/board.rs` | `on_board` goes through the menu, and new tests |

## The levels

Add these to `crates/game/files/levels/`. Designing levels is half of making a puzzle game, and
you're welcome to change them once the step works. These four were each checked to be winnable.
Step 9 has a test that proves it.

`2.map`:

```ron
// Level 2, in the engine's `.map` format.
(
    name: "Two rows",
    legend: { '.': "floor", '#': "wall", 'o': "target" },
    features: { (2, 2): Start, (3, 2): Box, (3, 4): Box },
)
---
########
#......#
#....o.#
#......#
#....o.#
#......#
########

```

`3.map`:

```ron
// Level 3, in the engine's `.map` format.
(
    name: "Round the corner",
    legend: { '.': "floor", '#': "wall", 'o': "target" },
    features: { (2, 4): Start, (4, 3): Box },
)
---
########
#o.....#
###.#..#
#......#
#......#
########

```

`4.map`:

```ron
// Level 4, in the engine's `.map` format.
(
    name: "Side door",
    legend: { '.': "floor", '#': "wall", 'o': "target" },
    features: { (2, 5): Start, (2, 2): Box, (2, 4): Box },
)
---
#########
#...#...#
#...#.o.#
#......o#
#...#...#
#...#...#
#########

```

`5.map`:

```ron
// Level 5, in the engine's `.map` format.
(
    name: "The yard",
    legend: { '.': "floor", '#': "wall", 'o': "target" },
    features: { (4, 5): Start, (2, 2): Box, (6, 2): Box, (2, 4): Box },
)
---
#########
#o..#..o#
#.......#
##.#.#.##
#.....o.#
#.......#
#########

```

Here's how the second looks, when you reach it. The camera has more to frame, but no code had to
change for that:

![Level 2, Two rows: a wider room with two boxes and two targets.](https://zafnok.github.io/shuyi-engine/tutorial-tries/level-two/before.png)

## The list

Name them in `crates/game/files/levels.ron`, in the order they're played:

```ron
// The levels, in the order they are played.
[
    "levels/1.map",
    "levels/2.map",
    "levels/3.map",
    "levels/4.map",
    "levels/5.map",
]

```

A list in a file means the order is data too. Swap the first and last, and level 5 comes first,
with no code changed:

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

```diff
-     "levels/1.map",
-     "levels/2.map",
-     "levels/3.map",
-     "levels/4.map",
-     "levels/5.map",
+     "levels/5.map",
+     "levels/2.map",
+     "levels/3.map",
+     "levels/4.map",
+     "levels/1.map",
```

![The first item of the menu opens the level First push.](https://zafnok.github.io/shuyi-engine/tutorial-tries/levels-order/before.png)

![The first item of the menu opens the level The yard.](https://zafnok.github.io/shuyi-engine/tutorial-tries/levels-order/after.png)

The list is a kind of asset of the game's own, read as RON. In `level.rs`:

```rust
/// The level list: each level's file, in the order they are played.
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
#[serde(transparent)]
pub struct LevelList(pub Vec<String>);

impl AssetKind for LevelList {
    const NAME: &'static str = "level list";
}

/// Reads every level the list in `levels.ron` names.
pub fn read_all(bundle: &Bundle) -> Result<Vec<Level>, Vec<ContentError>> {
    // check-text: not player text, a file name
    let list = formats().load::<LevelList>(bundle, "levels.ron")?;
    let mut levels = Vec::new();
    let mut errors = Vec::new();
    for name in &list.0 {
        match read(bundle, name) {
            Ok(level) => levels.push(level),
            Err(problems) => errors.extend(problems),
        }
    }
    if errors.is_empty() {
        Ok(levels)
    } else {
        Err(errors)
    }
}
```

It's the three parts from step 2 again. `LevelList` is a new **kind**, a plain type with a name for
error messages (that's all `AssetKind` asks). `Ron::<LevelList>::new()` is its **format**, which the
engine provides for any type. Add it to the **registry**, in `formats()`, below the other:

```rust
    let _ = formats.register(Ron::<LevelList>::new());
```

> **Rust:** `Vec<String>` is a list of texts, and `pub struct LevelList(pub Vec<String>)` wraps it in
> a type of its own. `#[serde(transparent)]` tells serde to read the file as just the list inside.
> You get to it as `.0`, the first field.

`read_all` shows a good habit: it doesn't stop at the first bad level. It tries them all, gathering
every problem into `errors`, so when you run `cargo shuyi check` you see everything that's wrong at
once. `errors.extend(...)` adds a whole list to a list.

Now `Own` holds them all. In `stages.rs`, the `level` field becomes `levels`:

```rust
/// The game's own part of its data.
#[derive(Debug)]
pub struct Own {
    /// Every level, in the order they are played.
    pub levels: Vec<Level>,
    /// The lights a level may be in: none, so the default, which changes
    /// nothing.
    pub lights: Lighting,
}

/// Reads the game's own part from its files.
pub fn own(bundle: &Bundle) -> Result<Own, Vec<ContentError>> {
    Ok(Own {
        levels: level::read_all(bundle)?,
        lights: Lighting::default(),
    })
}
```

and the board takes which one:

```rust
    /// The board on level `level`.
    ///
    /// # Panics
    ///
    /// If that level isn't one, or makes no puzzle, which loading it rules
    /// out for each level of the list.
    pub fn new(ctx: &Ctx<Data>, level: usize) -> Self {
        let file = &ctx.game.own.levels[level];
        let puzzle = Puzzle::new(file).unwrap_or_else(|e| panic!("{e}"));
        Self {
            level,
            name: file.name.clone(),
            history: History::new(puzzle.clone()),
            puzzle,
        }
    }
```

> **Rust:** `ctx.game.own.levels[level]` picks one item from a list by its position, counted from 0.
> A position past the end panics, and the `# Panics` part of the comment warns of that. `usize` is the
> kind of number used for positions.

Also add `pub level: usize,` to the top of `struct Board`.

## The menu

Make `crates/game/src/levels.rs` (with an `s`: `level.rs` is one level, and this is the screen that
lists them).

> **Shuyi:** before you write a screen, look for it in the engine. `shuyi::ui::widgets` has menus, lists
> and question boxes. Your screen just wraps one. The `Menu` here handles moving the focus up and down,
> wrapping round, and sounds.

```rust
use std::any::Any;

use shuyi::prelude::*;
use shuyi::ui::widgets::menu::glyph;
use shuyi::ui::widgets::{Menu, MenuEvent, MenuItem};

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

Each item of a menu carries the value it stands for, so the screen doesn't need a list beside it to
know what was chosen. Say what those values can be:

```rust
/// What an item of the menu stands for.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Choice {
    /// Plays this level, counted from 0.
    Level(usize),
}
```

The screen remembers only which item has the focus. `Default` in the derive means a `LevelMenu` can be
made with sensible starting values, and `focus` starts at 0:

```rust
/// The level menu.
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct LevelMenu {
    /// Which item has the focus.
    pub focus: usize,
}
```

Then an `impl LevelMenu { ... }` block holds two functions. `new` makes one, using that default:

```rust
    /// The menu, on the first level.
    pub fn new() -> Self {
        Self::default()
    }
```

and `menu` makes the menu's items:

```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_value(Choice::Level(n))
        });
        Menu::new(items.collect()).focused(self.focus)
    }
```

> **Rust:** `0..levels` is a **range**: the numbers from 0 up to, but not including, `levels`. And
> `.map(|n| ...)` makes something from each one. Here, a chain of builders makes a menu item: its text
> key, then what to fill `{n}` with, then its value. `n + 1` is there since people count from 1 and
> programs count from 0.

The text comes from the key `levels.level`, so it's in `en.ron` as ever:

```ron
    // The level menu.
    "levels.title": "Levels",
    "levels.level": "Level {n}",
    "levels.help": "{Cursor} move · {Confirm} choose · {Cancel} back",
```

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

```diff
- "levels.level": "Level {n}",
+ "levels.level": "Puzzle {n}",
```

![The menu lists Level 1 to Level 5.](https://zafnok.github.io/shuyi-engine/tutorial-tries/menu-words/before.png)

![The menu lists Puzzle 1 to Puzzle 5.](https://zafnok.github.io/shuyi-engine/tutorial-tries/menu-words/after.png)

Then the jobs of a screen, inside `impl Screen<Data> for LevelMenu`. Give it a `name` of `"levels"`,
as you did for the others. Its actions are whichever its menu reads:

```rust
    /// Its menu's.
    fn actions(&self) -> Option<Vec<Action>> {
        let actions = Menu::<()>::ACTIONS.iter();
        Some(actions.filter_map(|&ui| Action::for_ui(ui)).collect())
    }
```

`update` hands each action to the menu, which answers with what happened, if anything:

```rust
    fn update(&mut self, ctx: &mut Ctx<Data>, input: &FrameInput<Action>) -> Transition<Data> {
        let mut menu = self.menu(ctx);
        for &action in &input.actions {
            let event = menu.handle_with_sound(action, &mut ctx.audio);
            self.focus = menu.focus();
            match event {
                Some(MenuEvent::Chosen(Choice::Level(n))) => {
                    return Transition::Push(Box::new(Board::new(ctx, n)));
                }
                Some(MenuEvent::Cancelled) => return Transition::Pop,
                None => {}
            }
        }
        Transition::None
    }
```

If a level is chosen, it pushes a board. If the player cancels, it pops back to the title. So only
the title ever leaves the game, and only where the platform allows it, which means nothing here can
leave a browser tab frozen.

`draw` is the same shape you've written before, and `as_any` is the same as the board's:

```rust
    fn draw(&self, ctx: &Ctx<Data>, buf: &mut GlyphBuffer) {
        let c = |e| ctx.palette.engine(e);
        let bg = c(EngineColor::Background);
        buf.fill_rect(buf.bounds(), Cell::new(' ', bg, bg));
        print_centred(
            buf,
            3,
            ctx.text("levels.title"),
            c(EngineColor::Highlight),
            bg,
        );
        let view = self.menu(ctx).view();
        let (width, _) = glyph::size(ctx, &view);
        let x = (i32::from(buf.width()) - width).max(0) / 2;
        glyph::paint(ctx, &view, buf, x, 6);
        let help = ctx.text_with("levels.help", &[]);
        let last = i32::from(buf.height()) - 1;
        print_centred(buf, last, &help, c(EngineColor::TextDim), bg);
    }
```

Then, in `stages.rs`, the title starts the menu rather than the board, and the menu goes on the list of
screens:

```rust
/// What the title starts: the level menu.
pub fn first(ctx: &mut Ctx<Data>) -> Option<Box<dyn Screen<Data>>> {
    let _ = ctx;
    Some(Box::new(LevelMenu::new()))
}
```

```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()), Box::new(Board::new(ctx, 0))]
}
```

`let _ = ctx;` only says that this function doesn't need its `ctx` yet. (It will in step 7.)

## A result

Now the stack has three cards: title, menu, board. What should happen when a level is won? The menu
should move on to the next level. But the board doesn't know about the menu, and shouldn't: it's only
a board. So how does the news get across?

> **Shuyi:** a screen that finishes can hand a value to the one below it. It pops with a **result**,
> and the one below hears it in `uncovered`. Neither has to know what the other is, beyond the type
> of the value that they agree on.

So agree on a type, in `levels.rs`:

```rust
/// What a won board hands the menu: the level it was on.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct LevelWon(pub usize);
```

When the level is won, Confirm pops the board with what happened. In the board's `update`, above
`Action::Cancel`:

```rust
                Action::Confirm if self.puzzle.outcome().is_some() => {
                    return Transition::pop_with(LevelWon(self.level));
                }
```

The board's help lines in `en.ron` should now say so, and that Cancel goes back to the levels:

```ron
    // The board.
    "board.help": "{Cursor} walk · {Undo} undo · {Cancel} levels",
    "board.help.won": "Solved! {Confirm} next · {Undo} undo · {Cancel} levels",
```

> **Rust:** `Action::Confirm if ... =>` is a **match guard**, an arm that counts only if its condition is
> true too. If it's false, `match` goes on to try the next arm, which here is the plain `Action::Confirm
> => {}`. So a Confirm on a level not yet won does nothing.

The menu hears it when it's uncovered, and moves on:

```rust
    /// A won board hands back its level: the menu moves on to the next.
    fn uncovered(&mut self, ctx: &mut Ctx<Data>, result: Option<ScreenResult>) -> Transition<Data> {
        if let Some(LevelWon(level)) = result.and_then(ScreenResult::take::<LevelWon>) {
            let last = ctx.game.own.levels.len().saturating_sub(1);
            self.focus = (level + 1).min(last);
        }
        Transition::None
    }
```

> **Rust:** `result.and_then(ScreenResult::take::<LevelWon>)` asks "is there a result, and is it a
> `LevelWon`?". The `if let Some(LevelWon(level)) = ...` then opens it up, two layers at once, and calls
> the number inside `level`. Patterns like this can go as deep as you need.
>
> `saturating_sub(1)` subtracts, but stops at 0 rather than going below. And `.min(last)` takes the
> smaller of the two, so that winning the last level doesn't move the focus off the end.

## The tests

`on_board` in `tests/board.rs` needs another Enter now, and the stack has grown:

```rust
/// The game on its first level.
fn on_board() -> Harness<game::Data> {
    let mut h = game::game().harness();
    h.keys("Enter Enter");
    assert_eq!(h.screens(), ["title", "levels", "board"]);
    h
}
```

Every test that uses it keeps working. That's why it was a helper. Now a test of the whole flow:

```rust
#[test]
fn a_won_level_hands_its_result_to_the_menu() {
    let mut h = on_board();
    h.keys("Right Right Enter");
    assert_eq!(h.screens(), ["title", "levels"]);
    let menu = h.screen::<LevelMenu>().unwrap_or_else(|| panic!("no menu"));
    assert_eq!(menu.focus, 1);
    h.keys("Enter");
    assert!(h.snapshot().contains("Two rows"), "{}", h.snapshot());
}
```

It wins level 1, and checks that the board has gone, the menu's focus moved on, and that Enter opens
level 2. `h.screen::<LevelMenu>()` is why screens need `as_any`.

## What you learned

- **Rust:** `Vec`, ranges, match guards, patterns inside patterns, and indexing with `[]`.
- **Shuyi:** the engine's `Menu`, items carrying values, `AssetKind` and `Ron`, and results between
  screens.

[Chapter 2](https://zafnok.github.io/shuyi-engine/learn/book/games-and-screens/index.md) explains screens and their results. Next: the game should
remember how far you got.
