# A level on the board

Source: https://zafnok.github.io/shuyi-engine/learn/tutorial/02-board/

By the end of this step, pressing Confirm on the title opens a real level:

![The board: First push at the top left, a walled room in the middle with the player as an at sign, a box in square brackets and a target in angle brackets, and the help line at the bottom.](https://zafnok.github.io/shuyi-engine/tutorial-tries/board/before.png)

You can't move yet. This step is about three questions every game has to answer:
where does a level come from, how does the game hold it in memory, and how does it
get onto the screen? In Shuyi, each of those has its own file, and keeping them apart
is most of what makes a game easy to change.

| File | Is it new? | What it does |
| ---- | ---------- | ------------ |
| `crates/game/files/levels/1.map` | New | The level, as text |
| `crates/game/src/level.rs` | New | Reads that text |
| `crates/game/src/rules.rs` | New | What can change in the puzzle |
| `crates/game/src/board.rs` | New | The screen that shows it |
| `crates/game/src/stages.rs` | Changed | Tells the title to start the board |
| `crates/game/src/lib.rs` | Changed | Names the three new modules |
| `crates/game/files/palette.ron`, `en.ron` | Changed | The new colors and words |
| `Cargo.toml`, `crates/game/Cargo.toml` | Changed | Adds `serde` |
| `crates/game/tests/board.rs` | New | A test of the board |

Your project should end up like
[this step's copy](https://github.com/Zafnok/shuyi-engine/tree/main/site/tutorial/02-board).

## The level's file

> **Game idea:** keep a game's **data** apart from its **code**. If a level is a text
> file, then changing one never means changing code, and a level designer needs no
> programmer. It also means a player could make their own.

Make the folder `crates/game/files/levels/`, and in it the file `1.map`:

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

```

This is the engine's `.map` format. Below the `---` line, each character is a
**tile**, one square of the level. Above it, the header says what those characters
mean. `legend` says that `.` is floor, `#` is wall, and `o` is a target, a square a
box should end up on.

`features` says what else stands where. Tiles are counted like cells: `(x, y)`, from
the top left, starting at 0. So `(2, 2): Start` is the third column of the third row,
where the player begins, and `(3, 2): Box` is the square right beside them.

This is so readable that you can already play with it. Here is the room made twice as
wide and twice as tall:

In `crates/game/files/levels/1.map`:

```diff
- #######
- #.....#
- #....o#
- #.....#
- #######
+ ##############
+ #............#
+ #....o.......#
+ #............#
+ #............#
+ #............#
+ #............#
+ #............#
+ #............#
+ ##############
```

![The room is seven tiles wide and five tall.](https://zafnok.github.io/shuyi-engine/tutorial-tries/level-double/before.png)

![The room is fourteen tiles wide and ten tall, twice the size each way, with the player and the box still near its top left.](https://zafnok.github.io/shuyi-engine/tutorial-tries/level-double/after.png)

Once you've finished this step, try that, then put it back. Doing it right now would
give you an error, since nothing reads this file yet.

## Getting ready: `serde`

The rules' state has to be able to turn into text and back, for saves and replays.
Rust has a well-known library for that called **serde**. Add it to your game's
dependencies. In the top `Cargo.toml`, under `[workspace.dependencies]`, add the
line `serde = { version = "1", features = ["derive"] }`, and in
`crates/game/Cargo.toml`, under `[dependencies]`, add `serde.workspace = true`.

Then, in `crates/game/src/lib.rs`, name the three files you're about to write:

```rust
pub mod app_info;
pub mod board;
pub mod level;
pub mod rules;
pub mod stages;
pub mod title;
```

(Your game won't compile again until all three exist. That's fine: you'll finish them
all before you run it.)

## Reading it: `level.rs`

Make `crates/game/src/level.rs`. It starts by saying what it uses:

```rust
use serde::{Deserialize, Serialize};
use shuyi::assets::Formats;
use shuyi::assets::grid_map::{self, GridMap, MapFeature, MapFormat, MapText};
use shuyi::core::TerrainId;
use shuyi::prelude::*;

use crate::rules::Puzzle;
```

Those `{ }` in a `use` take several names from one place at once. Then, give each
kind of ground a name, and each kind of thing that stands on it:

```rust
/// Floor: the player and the boxes go over it.
pub const FLOOR: TerrainId = TerrainId(0);
/// A wall: nothing goes through it.
pub const WALL: TerrainId = TerrainId(1);
/// A target: floor a box should end on.
pub const TARGET: TerrainId = TerrainId(2);

/// Each terrain's id in a level's legend, by [`TerrainId`].
pub const TERRAIN_NAMES: [&str; 3] = ["floor", "wall", "target"];

/// The terrain a legend's id names.
pub fn terrain_named(name: &str) -> Option<TerrainId> {
    let index = TERRAIN_NAMES.iter().position(|&n| n == name)?;
    u16::try_from(index).ok().map(TerrainId)
}

/// What a tile may hold besides its terrain.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum Feature {
    /// The player starts here.
    Start,
    /// A box starts here.
    Box,
}

impl MapFeature for Feature {}
```

> **Rust:** `const` makes a fixed value. `TerrainId(0)` is a **tuple struct**: a struct
> whose fields have no names, here just wrapping a number, so that a terrain can never
> be mixed up with some other number. `[&str; 3]` is an **array** of exactly three
> pieces of text, and `&str` is borrowed text, as opposed to the owned `String`.
>
> `pub fn terrain_named(name: &str) -> Option<TerrainId>` turns a word from the legend
> into a terrain, or `None` if there's no such word. The `?` at the end of the
> `position` line says: "if this is `None`, stop here and return `None`; if it's
> `Some`, carry on with what's inside". `?` is how Rust passes a problem up to whoever
> called, in a single character, and you'll use it constantly.
>
> `enum Feature` lists all the things a tile may hold, `Start` or `Box`. Enums are how
> you say "one of these, and nothing else", and you'll write a lot of them.

`impl MapFeature for Feature {}` promises the engine's `.map` format that `Feature` is
a thing a tile can hold. The trait asks for no functions of its own, so the `{}` is
empty. And the `derive` line now has `Serialize, Deserialize`: those come from serde,
and let this enum be written out and read back.

Next comes the reading itself:

```rust
/// A level, as its file says.
pub type Level = GridMap<TerrainId, Feature>;

/// The format of a level: one tile look, at most 20 tiles a side, which
/// the screen shows whole.
pub const FORMAT: MapFormat<'static> = MapFormat::new(&[grid_map::DEFAULT_TILES], 20);

/// The formats the game reads its own files with.
pub fn formats() -> Formats {
    let mut formats = Formats::standard();
    // The first format of its kind: never refused.
    let _ = formats.register(MapText::<TerrainId, Feature>::new(FORMAT, terrain_named));
    formats
}

/// Reads the level in file `name`, which must make a puzzle.
pub fn read(bundle: &Bundle, name: &str) -> Result<Level, Vec<ContentError>> {
    let level = formats().load::<Level>(bundle, name)?;
    Puzzle::new(&level).map_err(|e| vec![ContentError::new(bundle.display_path(name), e)])?;
    Ok(level)
}
```

> **Shuyi:** the **format registry**. Everything a game reads from a file goes through
> three parts. A **kind** is what you want (here `Level`). A **format** knows how to read
> one kind from one sort of file (`MapText` reads `.map`). And the registry, `Formats`,
> holds all the formats, and finds the right one for a file. Your code just says "load
> me a `Level` from this file", and never cares what's in the file. So a game could
> switch to a different level editor later, and only the format changes.
>
> The **bundle** is your game's files, the ones `include_dir!` put inside it.

> **Rust:** `Result<Level, Vec<ContentError>>` is the other type you'll see everywhere.
> `Option` says "maybe a value". `Result` says "a value, or what went wrong": it's
> either `Ok(value)` or `Err(problem)`. The `?` works on these too, passing an `Err` up
> to the caller.
>
> `load::<Level>` fills in a generic by hand, to say which kind you want, a form of
> writing people call the "turbofish". `let _ = ...` means "I know this gives something
> back, and I don't need it".

If a level makes no puzzle, say there's no start, `read` reports it as a problem with
that file, along with every other problem, as the game loads. So you hear about a bad
level straight away, with its file name, and not when a player reaches it. Try it after
this step: remove `(2, 2): Start,` from the level, and run `cargo shuyi check`.

## What can change: `rules.rs`

Here's the most important file of your game. Make `crates/game/src/rules.rs`:

```rust
use std::collections::BTreeSet;

use serde::{Deserialize, Serialize};
use shuyi::core::{Grid, Pos, TerrainId};

use crate::level::{Feature, Level, TARGET};
```

```rust
/// The puzzle: its tiles, where the player stands and where the boxes are.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Puzzle {
    /// The tiles, which never change.
    tiles: Grid<TerrainId>,
    /// Where the player stands.
    player: Pos,
    /// Where the boxes are.
    boxes: BTreeSet<Pos>,
}
```

> **Game idea:** the **state** is everything that's true right now, and could change.
> In this puzzle, that's only where the player and the boxes are. The walls never move,
> so the state just keeps a copy of the tiles. Whatever isn't in the state doesn't exist
> as far as the rules are concerned.

Look at the top of the file: it uses no screens, no keys, no colors. That's on purpose.
In step 3, you'll see why this matters so much.

> **Rust:** `Pos` is a position, an `x` and a `y`, and `Grid<TerrainId>` is the engine's
> two-dimensional grid of terrains. `BTreeSet<Pos>` is a **set**: a collection with no
> duplicates, which is just what boxes are, since no two share a tile.
>
> Why `BTreeSet`, and not the better-known `HashSet`? A `BTreeSet` always keeps its
> items in the same order, while a `HashSet`'s order differs from one run to the next.
> Shuyi's rules must do exactly the same thing every time, on every computer. You'll see
> what that buys you in step 5 and step 9. So in rules, always use `BTree`.

Now to build a `Puzzle` from a level:

```rust
    /// The puzzle `level` lays out: it needs one start, a box, and as many
    /// targets as boxes.
    pub fn new(level: &Level) -> Result<Self, String> {
        let mut player = None;
        let mut boxes = BTreeSet::new();
        for (&pos, feature) in &level.features {
            match feature {
                Feature::Start if player.is_some() => return Err("two starts".to_owned()),
                Feature::Start => player = Some(pos),
                Feature::Box => {
                    boxes.insert(pos);
                }
            }
        }
        let player = player.ok_or("no start")?;
        let puzzle = Self {
            tiles: level.tiles.clone(),
            player,
            boxes,
        };
        let targets = puzzle.targets().len();
        if puzzle.boxes.is_empty() || puzzle.boxes.len() != targets {
            let boxes = puzzle.boxes.len();
            return Err(format!("{boxes} boxes and {targets} targets"));
        }
        Ok(puzzle)
    }
```

This function goes through the level's features, and checks that they make a puzzle.
Reading it will show you a lot of Rust at once:

> **Rust:** `Self` means "the type this `impl` is for". `Puzzle::new` is the usual way of
> writing a function that makes a new one: `new` isn't special, it's just a habit.
>
> `for (&pos, feature) in &level.features` is a **loop**, running once for each feature,
> and calling its parts `pos` and `feature` each time.
>
> **`match`** is like an `if` for all the possibilities of a value. It tries each arm
> from the top, and the compiler refuses to compile a `match` that misses a possibility.
> This is one of the best things about Rust: add a new kind of `Feature` later, and the
> compiler will take you to every `match` that needs to think about it.
> `Feature::Start if player.is_some()` is an arm with an extra condition.
>
> `let player = player.ok_or("no start")?;` turns the `Option` into a `Result`: a `None`
> becomes the error "no start", which `?` returns.

And then a few small functions that let others read the state:

```rust
    /// The tiles.
    pub fn tiles(&self) -> &Grid<TerrainId> {
        &self.tiles
    }

    /// Where the player stands.
    pub fn player(&self) -> Pos {
        self.player
    }

    /// Where the boxes are.
    pub fn boxes(&self) -> &BTreeSet<Pos> {
        &self.boxes
    }
```

```rust
    /// Every target tile.
    pub fn targets(&self) -> BTreeSet<Pos> {
        let (width, height) = (self.tiles.width(), self.tiles.height());
        (0..i32::from(height))
            .flat_map(|y| (0..i32::from(width)).map(move |x| Pos::new(x, y)))
            .filter(|&pos| self.tiles.get(pos) == Some(&TARGET))
            .collect()
    }
```

Notice that `tiles`, `player` and `boxes` in the struct aren't `pub`, so no other file
can change them. These functions let others look, and only look. That's how it stays
true that only the rules can change the rules' state.

## Drawing it: `board.rs`

In step 1, you met the idea that graphics are a skin. Now you'll use it. Make
`crates/game/src/board.rs`:

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

use shuyi::core::Pos;
use shuyi::core::TerrainId;
use shuyi::prelude::*;
use shuyi::ui::frame::Palette;
use shuyi::ui::grid::{
    Camera, GlyphSkin, Grid, GridSkin, Looks, MapColors, MapScene, MapUnit, TerrainGlyphs,
    Terrains, UnitPainter, WalkSpeeds, glyph,
};

use crate::Data;
use crate::level::TERRAIN_NAMES;
use crate::rules::Puzzle;
```

First, how much of the frame the level may use. A `Rect` is a rectangle: left, top, width,
height. This one leaves the top and bottom rows free for text:

```rust
/// The cells a level is shown in: all but the top and bottom rows.
pub const AREA: Rect = Rect::new(0, 1, 48, 22);
```

### What a terrain looks like

```rust
/// Each terrain's two glyphs and colors, by [`TerrainId`]: floor, wall,
/// target.
pub const TERRAIN_LOOKS: [([char; 2], &str, &str); 3] = [
    ([' ', ' '], "floor_fg", "floor_bg"),
    (['▓', '▓'], "wall_fg", "wall_bg"),
    (['<', '>'], "target_fg", "floor_bg"),
];
```

Each row here is one terrain, in the order `level.rs` numbered them. Each tile takes up
**two cells**, side by side, because a cell is twice as tall as it is wide, so two make a
square. Floor is two spaces, a wall is two blocks, and a target is `<>`. Then come two
names from the palette, for the text color and the background.

Try changing a wall's look. Only the first part of the row changes:

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

```diff
- (['▓', '▓'], "wall_fg", "wall_bg"),
+ (['+', '+'], "wall_fg", "wall_bg"),
```

![The walls are solid shaded blocks.](https://zafnok.github.io/shuyi-engine/tutorial-tries/wall-look/before.png)

![The walls are drawn with plus signs.](https://zafnok.github.io/shuyi-engine/tutorial-tries/wall-look/after.png)

To paint the terrains, the engine asks about them through a trait:

```rust
/// The terrains as the skin sees them.
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct Terrain;

impl Terrains for Terrain {
    fn name(&self, id: TerrainId) -> Option<&str> {
        TERRAIN_NAMES.get(usize::from(id.0)).copied()
    }

    fn glyphs(&self, id: TerrainId) -> Option<TerrainGlyphs<'_>> {
        let &(glyphs, fg, bg) = TERRAIN_LOOKS.get(usize::from(id.0))?;
        Some(TerrainGlyphs::new(glyphs, fg, bg))
    }
}
```

`TERRAIN_LOOKS.get(...)` gives an `Option`, since there might be no such row, and `?`
handles that. `usize::from(id.0)` takes the number from inside the `TerrainId` (`.0`
means the first field of a tuple struct), and turns it into the kind of number used for
counting positions in a list, `usize`.

### The skin

> **Shuyi:** the **grid kit** (`shuyi::ui::grid`) paints maps for you. You build a
> **scene**, plain data saying which terrain and which pieces are where, and a **skin**
> paints it. A `GlyphSkin` paints with characters, and a sprite skin paints with pictures.
> They take the same scene. So your rules and your scene are already ready for art.

```rust
/// The glyph skin: two cells a tile.
// check-text: not player text, the palette's names
pub const GLYPHS: GlyphSkin = GlyphSkin::new(
    MapColors::new("path", "cursor", "threat"),
    WalkSpeeds::of(8.0),
    0,
);
```

This names the palette's colors the skin uses for things this game won't draw (a path, a
cursor, a threat), and how fast a piece glides. `8.0` is a **float**, a number with a
fractional part. Floats are fine here, as this is only how something looks. They are never
allowed in the rules: floats can round differently on different computers, and rules must
be the same everywhere.

### The pieces

Now the things that stand on the tiles. Each is described as data:

```rust
/// Which piece.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Who {
    /// The player.
    Player,
    /// The box with this number.
    Box(usize),
}

/// A piece on the map, as the scene holds it.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Piece {
    /// Which piece.
    pub who: Who,
    /// The tile it is on.
    pub pos: Pos,
    /// Whether it is a box on a target.
    pub home: bool,
}

impl MapUnit for Piece {
    type Id = Who;

    fn id(&self) -> Who {
        self.who
    }

    fn pos(&self) -> Pos {
        self.pos
    }

    fn text(&self) -> String {
        let letter = if self.who == Who::Player { '@' } else { '#' };
        format!("{letter} ({},{})", self.pos.x, self.pos.y)
    }
}
```

An enum's possibilities can hold data: `Who::Box(usize)` is a box with a number. And
`MapUnit` is the trait that tells the kit what it needs to know about a piece. (`type Id =
Who;` fills in a blank the trait left, like a generic.)

Then the code that paints the pieces:

```rust
/// The pieces under the glyph skin: the player's letter over its tile, and
/// a box over both of its tile's cells.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Pieces<'a> {
    /// The palette the colors are named in.
    pub palette: &'a Palette,
}

impl UnitPainter<Piece> for Pieces<'_> {
    fn paint(&self, scene: &MapScene<Piece>, grid: &Grid, buf: &mut GlyphBuffer) {
        for piece in &scene.units {
            let (glyphs, color) = match (piece.who, piece.home) {
                (Who::Player, _) => (['@', ' '], "player"),
                (Who::Box(_), false) => (['[', ']'], "box"),
                (Who::Box(_), true) => (['[', ']'], "box_home"),
            };
            let fg = self
                .palette
                .lookup(color)
                .unwrap_or_else(|| self.palette.engine(EngineColor::Text));
            let Some((x, y)) = glyph::cell_of(grid, piece.pos) else {
                continue;
            };
            for (x, glyph) in (x..).zip(glyphs) {
                if glyph != ' '
                    && let Some(&cell) = buf.get(x, y)
                {
                    buf.set(x, y, Cell { glyph, fg, ..cell });
                }
            }
        }
    }
}
```

It takes each piece, picks its characters and its color, finds its cell with
`glyph::cell_of`, and writes the characters there. `Cell { glyph, fg, ..cell }` makes a
new cell with this character and text color, and everything else, like its background,
from the old one.

> **Rust:** the `'a` in `Pieces<'a>` is a **lifetime**. A struct that holds a borrow (the `&`)
> has to say so, so the compiler can check the borrowed thing lives long enough. You'll seldom
> need to write these yourself: when you do, the compiler tells you what to put.

So the player's character is one small piece of data:

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

```diff
- (Who::Player, _) => (['@', ' '], "player"),
+ (Who::Player, _) => (['P', ' '], "player"),
```

![The player is an at sign.](https://zafnok.github.io/shuyi-engine/tutorial-tries/player-look/before.png)

![The player is the letter P.](https://zafnok.github.io/shuyi-engine/tutorial-tries/player-look/after.png)

### The screen

The screen holds the state, and the name to show:

```rust
/// What the lines round the map show.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct BoardView {
    /// The level's name.
    pub name: String,
    /// The help line.
    pub help: String,
}
```

```rust
/// The board screen.
#[derive(Debug)]
pub struct Board {
    /// The level's name.
    name: String,
    /// The rules' state.
    pub puzzle: Puzzle,
}
```

```rust
    /// The board on the game's level.
    ///
    /// # Panics
    ///
    /// If the level makes no puzzle, which loading it rules out.
    pub fn new(ctx: &Ctx<Data>) -> Self {
        let file = &ctx.game.own.level;
        let puzzle = Puzzle::new(file).unwrap_or_else(|e| panic!("{e}"));
        Self {
            name: file.name.clone(),
            puzzle,
        }
    }
```

Starting it reads the level from the game's data, and builds the puzzle.
`unwrap_or_else(|e| panic!("{e}"))` says: if this is an error, **panic**, stop the
program with that message. A panic is for things that can't happen if the game is right.
It can't happen here, as `read` already checked this level when the game loaded. The comment
says so.

Then it builds its scene:

```rust
    /// What the map shows, in a view of `view` tiles round the player.
    pub fn scene(&self, view: (i32, i32)) -> MapScene<Piece> {
        let tiles = self.puzzle.tiles();
        let player = self.puzzle.player();
        let camera = Camera::centred_on(player, tiles.width(), tiles.height(), view);
        let mut scene = MapScene::new(camera.origin, view);
        scene.set_terrain(|pos| tiles.get(pos).copied());
        let targets = self.puzzle.targets();
        for (n, &pos) in self.puzzle.boxes().iter().enumerate() {
            let home = targets.contains(&pos);
            let who = Who::Box(n);
            scene.push_unit(Piece { who, pos, home });
        }
        let (who, pos, home) = (Who::Player, player, false);
        scene.push_unit(Piece { who, pos, home });
        scene
    }
```

The scene is built new every frame, from the state. That's a good habit: there's only one
copy of what's true, so what's on screen can never disagree with it. The **camera** picks
which part of the level is in view, here centred on the player.

```rust
    pub fn view(&self, ctx: &Ctx<Data>) -> BoardView {
        let help = "board.help";
        BoardView {
            name: self.name.clone(),
            help: ctx.text_with(help, &[]),
        }
    }
```

```rust
    /// The tiles the glyph skin fits in [`AREA`].
    pub fn view_tiles() -> (i32, i32) {
        GridSkin::<Piece>::view_tiles(&GLYPHS, AREA)
    }
```

The screen's jobs are the ones you saw on the title. For now, its only action is Cancel,
which goes back to the title with `Transition::Pop`:

```rust
impl Screen<Data> for Board {
    // check-text: not player text, the screen's id
    fn name(&self) -> &'static str {
        "board"
    }

    /// Cancel to go back.
    fn actions(&self) -> Option<Vec<UiAction>> {
        Some(vec![UiAction::Cancel])
    }

    fn update(&mut self, _: &mut Ctx<Data>, input: &FrameInput<UiAction>) -> Transition<Data> {
        if input.actions.contains(&UiAction::Cancel) {
            return Transition::Pop;
        }
        Transition::None
    }
```

And `draw` puts it all together:

```rust
    fn draw(&self, ctx: &Ctx<Data>, buf: &mut GlyphBuffer) {
        let palette = &ctx.palette;
        let c = |e| palette.engine(e);
        let bg = c(EngineColor::Panel);
        buf.fill_rect(buf.bounds(), Cell::new(' ', bg, bg));
        let view = self.view(ctx);
        buf.print(1, 0, &view.name, c(EngineColor::Highlight), bg);
        let last = i32::from(buf.height()) - 1;
        buf.print(1, last, &view.help, c(EngineColor::TextDim), bg);
        let looks = Looks::new(palette, &Terrain, &ctx.game.own.lights);
        let scene = self.scene(Self::view_tiles());
        GLYPHS.paint(&looks, &scene, AREA, &Pieces { palette }, buf);
    }
```

`print(1, 0, ...)` prints the level's name at the top left, and the help goes on the last
row. `GLYPHS.paint(...)` then paints the scene in `AREA`.

The very last function of the file is what lets a test find this exact screen on the
stack, by asking "are you a `Board`?". Every screen you want a test to look inside
needs it:

```rust
    fn as_any(&self) -> Option<&dyn Any> {
        Some(self)
    }
```

Close the `impl` with a final `}`.

## Joining it up

`crates/game/src/stages.rs` is the file that was empty in step 1. It now names your stage,
reads your own data, and answers the title's question of what comes first. Replace the whole
file with this:

```rust
//! The game's one stage: the puzzle. The title ([`crate::title`]) starts
//! it, and its part of the game's data is [`Own`].

use shuyi::assets::lighting::Lighting;
use shuyi::prelude::*;

use crate::Data;
use crate::board::Board;
use crate::level::{self, Level};

/// The stages' ids, in order.
pub fn names() -> &'static [&'static str] {
    &["board"]
}

/// The game's own part of its data.
#[derive(Debug)]
pub struct Own {
    /// The level.
    pub level: 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 {
        // check-text: not player text, a file name
        level: level::read(bundle, "levels/1.map")?,
        lights: Lighting::default(),
    })
}

/// What the title starts: the board.
pub fn first(ctx: &mut Ctx<Data>) -> Option<Box<dyn Screen<Data>>> {
    Some(Box::new(Board::new(ctx)))
}

/// 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(Board::new(ctx))]
}

/// The game's tools: the engine's, for a game on the short path.
#[cfg(feature = "tools")]
pub fn tools() -> shuyi_cli::BasicTools<UiAction, Own> {
    shuyi_cli::BasicTools::new(crate::game())
}

```

> **Shuyi:** `Own` is the game's own part of its data, which `BasicData` carries for you
> alongside the words and the keys, in `ctx.game.own`. And `game()` in `lib.rs` already
> passes `stages::own` to `BasicGame::with_own`. That's why step 1 could say `Own = ()`,
> a value that holds nothing, and you could change it here with no other change.

> **Rust:** `Box<dyn Screen<Data>>` means "a box holding some type that implements
> `Screen`, and which type isn't known until the game runs". That is how a stack can hold
> a title, a board and a menu all at once. You'll keep seeing it, and you can read it as
> "any screen".

Now the words and the colors it asked for. In `files/en.ron`, add this before the last `}`:

```ron
    // The board.
    "board.help": "{Cancel} back",
```

and in `files/palette.ron` add the game's own colors:

```ron
    // The game's.
    "floor_fg": "#3a3a46",
    "floor_bg": "#16161c",
    "wall_fg": "#5a5a6e",
    "wall_bg": "#26262e",
    "target_fg": "#ffd75a",
    "player": "#ffffff",
    "box": "#c89a5a",
    "box_home": "#a0e07a",
    "path": "#5ac8ff",
    "cursor": "#ffd75a",
    "threat": "#ff7a5a",
```

## Run it

```bash
cargo run
```

If the compiler has complaints, read them from the **top** down, as one mistake can cause
many later messages. Fix the first and run it again. Compare with
[this step's copy](https://github.com/Zafnok/shuyi-engine/tree/main/site/tutorial/02-board)
when you're stuck.

Press Enter on the title, and you'll see the picture from the top of this page. Note that
the title now shows a line saying how to play: it appeared because `stages::names()` isn't
empty any more. Backspace goes back. That's `Transition::Pop`, taking the board card off the
stack, and showing the title that was underneath, unchanged.

Colors work like they did before, for your own colors as much as the engine's:

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

```diff
- "box": "#c89a5a",
+ "box": "#5a9bff",
```

![The box is orange-brown.](https://zafnok.github.io/shuyi-engine/tutorial-tries/box-blue/before.png)

![The box is blue.](https://zafnok.github.io/shuyi-engine/tutorial-tries/box-blue/after.png)

## A test

Make `crates/game/tests/board.rs`. Each file in `tests/` is a separate little program:

```rust
//! The puzzle played through the engine's Harness: tests of what happened
//! read the board's state. Key names here are the Harness's test-script
//! names, input to a test, not game code.

use game::board::Board;
use shuyi::platform::harness::Harness;

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

fn board(h: &Harness<game::Data>) -> &Board {
    h.screen::<Board>()
        .unwrap_or_else(|| panic!("no board: {:?}", h.screens()))
}

#[test]
fn the_level_is_drawn_and_cancel_goes_back() {
    let mut h = on_board();
    assert!(h.snapshot().contains("First push"), "{}", h.snapshot());
    assert_eq!(board(&h).puzzle.boxes().len(), 1);
    h.keys("Backspace");
    assert!(h.screen::<Board>().is_none());
}

```

`on_board` and `board` are two helpers, used by every test from now on. This test checks that
the level's name is on screen, that the puzzle has one box, and that Cancel closes the
board. Run `cargo test --workspace`.

Notice that it reads the state (`puzzle.boxes()`) and not the picture. Tests of what happened
read the state. So you could repaint the entire game, and this test would never notice.

## What you learned

- **Rust:** `const`, tuple structs and arrays, `Result`, the `?` operator, `match`, `for`,
  `Self`, `panic!`, and lifetimes when you can't avoid them, `dyn`, and why `BTreeSet`.
- **Game ideas:** tiles, state, and data apart from code.
- **Shuyi:** `.map` files, the format registry and the bundle, `Own`, the grid kit, scenes and
  skins, and cameras.

For another take on the same ideas, [chapter 5](https://zafnok.github.io/shuyi-engine/learn/book/assets-and-formats/index.md) explains the
registry, and [chapter 4](https://zafnok.github.io/shuyi-engine/learn/book/frame-and-skins/index.md) the skins. Next, you'll walk.
