# The frame and skins

Source: https://zafnok.github.io/shuyi-engine/learn/book/frame-and-skins/

What's on the screen and how it looks are two different questions. "The second plot
has a sprout in it" is one. "A sprout is a green triangle" is another.

If one piece of code answers both, you can't change the look without risking the game,
and a test of the game breaks whenever a color changes. So the engine keeps them apart.
**Graphics are a skin.** A screen says *what* it shows, as plain data called its
**view**, and a **skin** says *how* that looks. Glyph art, pixel art and bought sprite
packs then swap without touching the rules, and a test of what happened never breaks
because a color changed.

The whole chain is short: the rules' **state** (everything that's true now) gives the
**view**, and the **skin** paints the view into the **frame**.

![The garden: a row of six plots, each a small dot, with a box round the first, 9 seeds left under them, and the help line at the bottom.](https://zafnok.github.io/shuyi-engine/tutorial-tries/book-garden/before.png)

## The view

The garden's view is built from the rules' state and the game's text, and nothing else:

```rust
/// What the garden shows: plain data, built from the rules' state and
/// the game's text, which tests read.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct GardenView {
    /// Each plot, left to right.
    pub plots: Vec<Plot>,
    /// The plot the player stands at.
    pub at: usize,
    /// The seeds left, in words.
    pub seeds: String,
    /// The help line, or how the game ended.
    pub help: String,
}

/// A plot as the garden shows it.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Plot {
    /// Bare soil.
    Soil,
    /// Something grows.
    Sprout,
}

impl GardenScreen {
    /// What the garden shows now.
    pub fn view(&self, ctx: &Ctx<Data>) -> GardenView {
        let garden = &self.garden;
        let plots = garden.plots().iter();
        let help = match garden.outcome() {
            None => "garden.help",
            Some(Outcome::Won) => "garden.won",
            Some(Outcome::Lost) => "garden.lost",
        };
        GardenView {
            plots: plots
                .map(|&grows| if grows { Plot::Sprout } else { Plot::Soil })
                .collect(),
            at: garden.at(),
            seeds: ctx.text_with("garden.seeds", &[("seeds", &garden.seeds())]),
            help: ctx.text_with(help, &[]),
        }
    }
}
```

It names no glyph, no color and no position: those are the skin's.

> **Rust:** `enum Plot` is one of a fixed list, `Soil` or `Sprout`. `usize` is a whole
> number, never negative, of the kind used to count a list's items.
>
> `plots.map(|&grows| ...).collect()` is an
> [iterator chain](https://zafnok.github.io/shuyi-engine/learn/tutorial/03-walk/index.md#the-rules): go through each plot, turn
> each into a `Plot`, and gather them into a list. `|&grows|` is a small nameless
> function whose one argument is `grows`, and the `&` takes the value out of the
> reference it comes as. In Rust, `if` gives back a value, so `if grows { Plot::Sprout }
> else { Plot::Soil }` is one `Plot`.
>
> `match garden.outcome()` opens an `Option`: `None` while the game goes on, or
> `Some(...)` with how it ended. It picks which line of text to show.

## The frame

Every screen draws into one **frame**, the picture the player sees. Its first layer is
a **console**, a grid of **cells** like an old terminal, each holding one **glyph** (a
character) with its colors. That grid is a `GlyphBuffer`, sized by the game: 48 × 24
cells of 8 × 16 pixels on the short path, `BASIC_CONSOLE`, and a game sets its own with
`BasicGame::console`. Over and under the cells go pictures from the game's images
(sprites, tiles, a backdrop), each with a layer that says what it covers.

A glyph skin paints cells: `fill_rect`, `draw_box`, `print` (which returns how many
cells it used), `set`, and the prelude's `print_centred`. Colors come from the palette
by name, never as numbers: `ctx.palette.engine(EngineColor::Text)` for the engine's own
roles, and `ctx.palette.lookup(name)` for one of the game's, from `files/palette.ron`:

```ron
    // The game's own.
    "soil": "#6b4a2b",
    "sprout": "#7ad07a",
```

A color like `#7ad07a` is three pairs of digits in base 16, for how much red, green and
blue it has, from `00` to `ff`.

## The skin

The garden's skin is one function of its view:

```rust
/// The glyph skin: paints a garden's view into the frame's cells, its
/// colors by name from the game's palette.
pub fn paint_garden(view: &GardenView, ctx: &Ctx<Data>, buf: &mut GlyphBuffer) {
    let c = |color| ctx.palette.engine(color);
    let own = |name| ctx.palette.lookup(name).unwrap_or(c(EngineColor::Text));
    let bg = c(EngineColor::Background);
    buf.fill_rect(buf.bounds(), Cell::new(' ', bg, bg));
    let count = i32::try_from(view.plots.len()).unwrap_or(0);
    let left = (i32::from(buf.width()) - count * 4) / 2;
    for (x, plot) in (left..).step_by(4).zip(&view.plots) {
        let (glyph, fg) = match plot {
            Plot::Soil => ('·', own("soil")),
            Plot::Sprout => ('▲', own("sprout")),
        };
        buf.set(x + 1, 10, Cell::new(glyph, fg, bg));
    }
    let at = left + i32::try_from(view.at).unwrap_or(0) * 4;
    buf.draw_box(
        Rect::new(at, 9, 3, 3),
        BoxStyle::Single,
        c(EngineColor::Focus),
        bg,
    );
    print_centred(buf, 14, &view.seeds, c(EngineColor::Text), bg);
    let last = i32::from(buf.height()) - 1;
    print_centred(buf, last, &view.help, c(EngineColor::TextDim), bg);
}
```

> **Rust:** `fg` and `bg` are the cell's foreground and background colors. `let
> (glyph, fg) = match plot { ... }` takes a pair apart: each arm gives back a glyph and
> a color, side by side, as a **tuple**, and this names both.
>
> `(left..).step_by(4)` counts up from `left` in fours, forever, and `.zip(&view.plots)`
> pairs each number with a plot, stopping when the plots run out. So `x` and `plot` go
> along together. `..` makes a **range**.
>
> The cell positions are `i32`, which may go negative, as something can be drawn partly
> off the screen. A list's length is a `usize`. Rust won't mix number types without
> being told, so `i32::try_from(view.at)` converts, and `unwrap_or(0)` says what to use
> if it can't.
>
> There isn't a single fractional number here: cells are counted, and everything lands
> on the same place on every machine.

A screen's `draw` builds the view and hands it to the skin. Since the view says nothing
of how it looks, changing the look changes one line of the skin:

In `src/frame.rs`:

```diff
- Plot::Sprout => ('▲', own("sprout")),
+ Plot::Sprout => ('♣', own("sprout")),
```

![The first plot has a green triangle in its box.](https://zafnok.github.io/shuyi-engine/tutorial-tries/book-skin-glyph/before.png)

![The first plot has a green club in its box. The seeds line and the plots are as they were.](https://zafnok.github.io/shuyi-engine/tutorial-tries/book-skin-glyph/after.png)

Nothing else in the game knows. And a color doesn't need code at all:

In `files/palette.ron`:

```diff
- "sprout": "#7ad07a",
+ "sprout": "#5a9bff",
```

![The sprout in the first plot is green.](https://zafnok.github.io/shuyi-engine/tutorial-tries/book-sprout-blue/before.png)

![The sprout in the first plot is blue.](https://zafnok.github.io/shuyi-engine/tutorial-tries/book-sprout-blue/after.png)

A second skin of the same view (sprites in place of glyphs, a one-bit look for a
handheld) is a second function, picked by a setting, and nothing else in the game
changes. The grid kit's map (`shuyi::ui::grid`) works this way: a **scene** of the
visible tiles, painted by a glyph skin or a sprite skin, with the game's units by a
painter of its own. Hello, Grid shows both.

## A picture the skin paints

Pictures from the game's image files are made before the game runs. Some
pictures can't be: a video's frames, an old console's screen in an
emulator, a minimap of what the player has seen, a moving plasma. Their
**pixels** (the dots a picture is made of) are made while the game runs.
A skin paints such a picture itself, as a `Painted` picture, and places
it in the frame like any other:

```rust
/// The plasma's name among the frame's pictures.
pub const PLASMA: PaintedId = PaintedId::new("plasma");

/// A skin that paints a 64 × 48 plasma from the step count, pixel by
/// pixel, and shows it twice its size with its top-left at `(x, y)`.
pub fn paint_plasma(step: u32, (x, y): (i32, i32), buf: &mut GlyphBuffer) {
    // Up from 0 to 248 and back down over 64 steps of `v`.
    let wave = |v: u32| u8::try_from((v % 64).abs_diff(32) * 31 / 4).unwrap_or(u8::MAX);
    let mut picture = Painted::rgba(64, 48);
    let rows = picture.pixels_mut().as_chunks_mut::<4>().0.chunks_mut(64);
    for (py, row) in (0u32..).zip(rows) {
        for (px, pixel) in (0u32..).zip(row) {
            let red = wave(px + step);
            let green = wave(py + 2 * step);
            let blue = wave(px + py + 3 * step);
            *pixel = [red, green, blue, 255];
        }
    }
    buf.pictures_mut().update(PLASMA, picture);
    let whole = Rect::new(0, 0, 64, 48);
    let place = Rect::new(x, y, 128, 96);
    buf.add_painted(PaintedSprite::new(PLASMA, whole, place, Layer::Over));
}
```

The frame keeps its pictures from one frame to the next, by name. Each
has a **revision**, a count that goes up whenever its pixels are written,
and the window sends a picture to the graphics card again only when its
revision moved. `update` puts the picture in only if it differs from the
one already there, so a plasma painted afresh at every frame costs
nothing in the frames where it didn't change.

> **Rust:** `as_chunks_mut::<4>()` cuts the pixels' bytes into groups of
> four, red, green, blue and how solid the pixel is (255 is fully), and
> `.0` takes the whole groups. `.chunks_mut(64)` cuts those into rows of
> 64 pixels, and `(0u32..).zip(rows)` numbers the rows, so `py` is a
> pixel's row; the inner loop numbers each row's pixels, its column `px`.
> `abs_diff` is how far apart two numbers are, never negative.

A picture comes in three kinds: `Painted::rgba` (four bytes a pixel, as
here), `Painted::indexed` (a byte a pixel and a palette of up to 256
colors, so changing a color changes every pixel of it at once) and
`Painted::one_bit` (black and white). It's drawn without smoothing, at any
size, and on a black-and-white display a colored picture is turned to a
pattern of black and white dots as it's drawn. The test reads the
picture's revision, not its pixels:

```rust
    #[test]
    fn the_plasma_moves_with_the_steps() {
        let black = Cell::new(' ', Rgb::new(0, 0, 0), Rgb::new(0, 0, 0));
        let mut buf = GlyphBuffer::new(48, 24, black);
        let mut revision = |step| {
            buf.fill_rect(buf.bounds(), black);
            paint_plasma(step, (16, 16), &mut buf);
            buf.pictures().get(PLASMA).map(Painted::revision)
        };
        let first = revision(0);
        // The same step paints the same picture: nothing to upload.
        assert_eq!(revision(0), first);
        // The next one moves it.
        assert_ne!(revision(1), first);
    }
```

## Why tests read the view

A test of what happened reads the view, or the state behind it:

```rust
    #[test]
    fn planting_shows_in_the_view() {
        let mut h = game().harness();
        h.keys("Enter Enter");
        let garden = h.screen::<GardenScreen>().unwrap();
        let view = garden.view(h.ctx());
        assert_eq!(view.at, 0);
        assert_eq!(view.seeds, "8 seeds left");
        // A test of what happened reads the view: a new glyph or color
        // doesn't break it.
        assert_eq!(view.plots.len(), 6);
    }
```

> **Rust:** `h.screen::<GardenScreen>()` asks the stack for the screen of that type, and
> gives back an `Option`, as it might not be there. `.unwrap()` opens it, and stops the
> test if it's `None`. That's fine in a test, and in a game you'd handle the `None`.

Only a test of a look reads cells and colors. `h.snapshot()` is the frame as text, the
cells' glyphs and then their colors:

```rust
    #[test]
    fn the_garden_looks_like_a_row_of_plots() {
        let mut h = game().harness();
        h.keys("Enter");
        // A test of a look reads the frame, here as text.
        let frame = h.snapshot();
        assert!(frame.contains("│·│  ·   ·   ·   ·   ·"), "{frame}");
        assert!(frame.contains("9 seeds left"), "{frame}");
    }
```

> **Rust:** `assert!` fails the test unless its first argument is true, and the rest is
> what to print if so, with `{frame}` filled in by the value of `frame`.

Change bare soil's glyph from `·`, and the second test breaks, as it should, since the
look is what it's about. The first test doesn't notice. That's the whole reason for the
view.

For a picture, `cargo shuyi frame-png garden.png --keys "Enter"` paints the frame to a
PNG with no window. Every picture in this book is made that way. Look at it, and keep
the tests on the view. [Chapter 8](https://zafnok.github.io/shuyi-engine/learn/book/testing/index.md#a-look) says how to keep a
look in a test.

## Things that move

A sprout that pops up, a menu that slides in, a title that bobs: the skin shows a change
over time, and the rules never wait for it. The engine draws such a motion from **the
time alone**. A **tween** is a value going from one end to another over a time, along an
**ease curve** that says how it speeds up and slows down, and its value at a moment is
worked out from how long it has run. Nothing steps it along from the frame before, so
it looks the same at any frame rate, and a test can ask for any moment:

```rust
/// How a new sprout comes up: from 8 pixels below its plot to its place in
/// 300 milliseconds, a little past and back, eased out with a "back".
pub const SPROUT_RISE: Tween<i32> = Tween::new(8, 0, 300).with_ease(Ease::Out(Family::Back));

/// How many pixels below its plot a sprout planted `ms` milliseconds ago is
/// drawn: worked out from the time alone, so nothing has to update it.
pub fn sprout_offset(ms: u32) -> i32 {
    SPROUT_RISE.value_at(ms)
}
```

> **Rust:** `const` makes a value the program has from the start, built by `const fn`s
> such as `Tween::new`. `Tween<i32>` is a tween of whole numbers; a tween of a pair, a
> color or a number of your own type works the same way.

The curves have the names other engines and websites give them, after Robert Penner's
equations: `Ease::Out(Family::Back)` is `easeOutBack` on easings.net, and Godot's
`TRANS_BACK` with `EASE_OUT`. `Back` goes a little past its end and comes back; `Linear`
moves as the time does, `InOut(Sine)` starts and stops gently, `Out(Bounce)` bounces.
The API docs of `shuyi::core::ease` list them all, beside other engines' names. In a
RON file a tween is data, like everything else: `(from: 8, to: 0, duration: 300, ease:
Out(Back))`.

The time is a whole number in the unit you choose. A screen counts milliseconds: it
keeps the moment its sprout was planted, and draws it at `sprout_offset(now -
planted)`. The rules count steps or turns. The curves are worked out in whole numbers,
never fractions, so they are the same on every machine, and the rules may use them too.

A test reads the motion at any moment, and asks when it ended:

```rust
    #[test]
    fn a_sprout_rises_past_its_place_and_settles() {
        assert_eq!(sprout_offset(0), 8);
        // Over half the way up in a sixth of the time, then a pixel past.
        assert_eq!(sprout_offset(50), 3);
        assert_eq!(sprout_offset(200), -1);
        assert_eq!(sprout_offset(300), 0);
        assert_eq!(sprout_offset(5000), 0);
        // "It landed": true in exactly one frame, given the last frame's
        // time and this one's.
        let frames = [0, 16, 33, 150, 290, 307, 323];
        let landed = frames
            .windows(2)
            .filter(|w| SPROUT_RISE.finished_between(w[0], w[1]))
            .count();
        assert_eq!(landed, 1);
    }
```

> **Rust:** `frames.windows(2)` goes through each pair of neighbors in the list, here
> one frame's time and the next. `.filter(...)` keeps the pairs where the sprout landed,
> and `.count()` counts them.

`finished_between` is how a screen hears "it landed": given the time of the frame
before and this one's, it is true once, so the screen plays its sound or moves on
without keeping a flag.

## Seeing what the game sees

A debug build can draw what your logic works with over the running game: where the map's
cells are, which part of it is in view, where a box collides. This is the **debug view**.
Each thing it can show is a **channel**, turned on and off from the debug menu's **Debug
view**, which lists every channel the screens running offer; the choice is kept for the
next launch. A build for players draws none of it and asks no screen for it.

A screen offers a channel in `debug_draw`, a method of `Screen` that does nothing unless
you write it. It hands in **gizmos**: rectangles, lines, crosses, circles and labels, in
console pixels, each with a **role** that says what it shows (bounds, solid, trigger,
path, bone, note). The engine picks their colors from the role, so a gizmo never names a
color, and `debug_draw` only reads the screen (`&self`), so looking never changes the
game. The garden's own channel boxes each plot:

```rust
/// The garden's channel of the debug view.
pub const PLOTS: Channel = Channel::new("plots", "Garden plots");

/// Hands in a box round each plot's cell, and a cross on the one the
/// player stands at, in console pixels: where the skin put them. The
/// garden screen's `debug_draw` calls it with its view.
pub fn plot_gizmos(view: &GardenView, ctx: &Ctx<Data>, out: &mut Gizmos) {
    if !out.channel_on(PLOTS) {
        return;
    }
    let (cell_w, cell_h) = (i32::from(ctx.console.cell_w), i32::from(ctx.console.cell_h));
    let count = i32::try_from(view.plots.len()).unwrap_or(0);
    let left = (i32::from(ctx.console.cols) - count * 4) / 2;
    for (i, x) in (0..view.plots.len()).zip((left + 1..).step_by(4)) {
        let rect = Rect::new(x * cell_w, 10 * cell_h, cell_w, cell_h);
        out.push(PLOTS, GizmoRole::Bounds, Gizmo::Rect { rect });
        if i == view.at {
            let at = (rect.x + cell_w / 2, rect.y + cell_h / 2);
            out.push(PLOTS, GizmoRole::Note, Gizmo::Cross { at, size: 3 });
        }
    }
}
```

The grid kit offers its own channel, `grid`: a map screen calls
`shuyi::ui::grid::debug::layout_gizmos` from its `debug_draw`, and the cell lines, the
map's edges, the cursor's cell and each unit's appear. Hello, Grid's stage and the stage
of a `shuyi new --template grid` game offer it already. A test reads the gizmos as it
reads a view, never their pixels:

```rust
    #[test]
    fn the_plots_channel_boxes_each_plot_and_marks_the_players() {
        let mut h = game().harness();
        h.keys("Enter Right");
        let garden = h.screen::<GardenScreen>().unwrap();
        let view = garden.view(h.ctx());
        // Off: nothing is built, but the channel is offered.
        let mut off = Gizmos::new();
        plot_gizmos(&view, h.ctx(), &mut off);
        assert_eq!(off.len(), 0);
        assert_eq!(off.offered(), [PLOTS]);
        // On: six boxes, and a cross after the second.
        let mut on = Gizmos::with_on(["plots"]);
        plot_gizmos(&view, h.ctx(), &mut on);
        let roles: Vec<GizmoRole> = on.drawn().iter().map(|d| d.role).collect();
        assert_eq!(roles.len(), 7);
        assert_eq!(roles[2], GizmoRole::Note);
    }
```

To see the same as a picture, `cargo shuyi frame-png out.png --debug plots` turns the
channels on from the first frame (`--debug all` turns on every one).

## When it goes wrong

**A glyph the font doesn't have.** The font is a picture of the characters it knows, in
`files/font/`. A character outside it can't be drawn as itself, and nothing stops the
game: the window only notes it once in its log. So look at a new glyph in a picture.

**A color name that isn't there.** `ctx.palette.lookup(name)` gives an `Option`, as a
name may be missing, and the skin above falls back to the engine's text color. A typo
in a name shows as a sprout in the wrong color, not as an error. A missing color of the
engine's, or a bad one, is caught by `cargo shuyi check`:

```text
check: 1 error(s)
  files/palette.ron: missing required colour "focus"
```

```text
check: 1 error(s)
  files/palette.ron:20: colour "sprout": "green" must start with '#' (format "#RRGGBB")
```

**Trying to change the game in `draw`.** The compiler stops you, as `draw` only has
`&self` ([chapter 2](https://zafnok.github.io/shuyi-engine/learn/book/games-and-screens/index.md#when-it-goes-wrong)).

## On a target's screen

A **target** is a kind of machine or place a game runs. Each target says how big its
display is and how small its text may be, and `cargo shuyi check --target` checks the
game's frame and scale against it ([chapter 9](https://zafnok.github.io/shuyi-engine/learn/book/targets-and-export/index.md)). A
one-bit display, such as the Playdate's, has only black and white, and takes each
palette role's pattern from the game's one-bit map.
