# Scenes with props

> A menu that opens the level it has chosen, and a level that goes back saying which one to have chosen: a scene takes props like a component, and TypeScript checks them when it is given by its function.

Example `scene-props`, running at https://nacatamalon.com/en/examples/scene-props. The whole catalogue: https://nacatamalon.com/llms/examples.md.

A scene takes props, like a component: how it is opened, given by whoever opens it. The menu opens the level it has chosen with `scene.change(Level, { props: { level } })`, and the level goes back with `scene.change(Menu, { props: { chosen: level } })`, so the menu does not start on the first one again. Given the function, TypeScript checks the props: `{ levle: 2 }` does not compile. By name, `scene.change('Level', { props: { level } })`, it works the same, unchecked.

Every prop has a default, `({ level = 1 }: { level?: number })`, because a scene also starts with none: the menu is the first scene and nobody gives it anything, and the editor's Play starts any scene on its own. What stays true across scenes, lives or coins, is a store; how this one opens is a prop. ◀ ▶ choose, X plays, Z goes back.

## Code

`index.ts`:

```ts
import { createGame } from 'nacatamalon';
import { Menu } from './scenes/menu';
import { Level } from './scenes/level';
import { WIDTH, HEIGHT } from './settings';

const game = createGame('#app', {
    width: WIDTH,
    height: HEIGHT,
    background: '#0d1020'
});

// Declare Scenes
game({
    Menu,
    Level
});
```

`components/level_picker.ts`:

```ts
import { createText, createSprite, useKeyboard, useUpdate } from 'nacatamalon';
import { WIDTH, LEVELS, COLORS, FULL, DIM, tint } from '../settings';

export const LevelPicker = ({ chosen, onPlay }: { chosen: number; onPlay: (level: number) => void }) => {
    const keys = useKeyboard();
    let level = chosen;

    createText({
        text: 'CHOOSE A LEVEL',
        transform: {
            x: WIDTH / 2 - 42,
            y: 40
        },
        tint: tint('#9aa4c0')
    });

    const cards = Array.from({
        length: LEVELS
    }, (_, i) =>
        createSprite({
            width: 56,
            height: 56,
            tint: tint(COLORS[i]),
            transform: {
                x: WIDTH / 2 + (i - 1) * 76,
                y: 96
            }
        }),
    );

    const labels = cards.map((_, i) =>
        createText({
            text: `${i + 1}`,
            transform: {
                x: WIDTH / 2 + (i - 1) * 76 - 3,
                y: 92
            },
            tint: tint('#0d1020')
        }),
    );

    createText({
        text: '◀ ▶ CHOOSE   X PLAY',
        transform: {
            x: WIDTH / 2 - 54,
            y: 150
        },
        tint: tint('#6b7390')
    });

    useUpdate((_, time) => {
        if (keys.justPressed('ArrowLeft')) {
            level = level === 1 ? LEVELS : level - 1;
        }

        if (keys.justPressed('ArrowRight')) {
            level = level === LEVELS ? 1 : level + 1;
        }

        cards.forEach((card, i) => {
            const on = i + 1 === level;
            const grow = on ? 1.15 + Math.sin(time * 6) * 0.05 : 0.85;

            card.transform.scaleX = grow;
            card.transform.scaleY = grow;
            card.tint = on ? FULL[i] : DIM[i];
            labels[i].visible = on;
        });

        if (keys.justPressed('x')) {
            onPlay(level);
        }
    });

    return {
        selected: () => level
    };
};
```

`components/stars.ts`:

```ts
import { createSprite, useUpdate } from 'nacatamalon';
import { WIDTH, HEIGHT, FULL } from '../settings';

export const Stars = ({ level }: { level: number }) => {
    const color = FULL[level - 1];

    const stars = Array.from({
        length: level * 4
    }, (_, i) => ({
        sprite: createSprite({
            width: 8,
            height: 8,
            tint: color,
            transform: {
                x: 0,
                y: 0,
                rotation: Math.PI / 4
            }
        }),
        angle: (i / (level * 4)) * Math.PI * 2,
    }));

    useUpdate((delta) => {
        for (const star of stars) {
            star.angle += delta * (0.6 + level * 0.3);
            star.sprite.transform.x = WIDTH / 2 + Math.cos(star.angle) * 60;
            star.sprite.transform.y = HEIGHT / 2 + 10 + Math.sin(star.angle) * 40;
        }
    });
};
```

`scenes/level.ts`:

```ts
import { createScene, createText, useKeyboard, useScene, useSpawn, useUpdate } from 'nacatamalon';
import { fade } from 'nacatamalon/transitions';
import { FULL, LEVELS, tint, TRANSITION_TIME } from '../settings';
import { Stars } from '../components/stars';
import { Menu } from './menu';

export const Level = ({ level = 1 }: { level?: number }) => {
    const scene = useScene();
    const keys = useKeyboard();
    const color = FULL[level - 1];

    createText({
        text: `LEVEL ${level}`,
        transform: {
            x: 8,
            y: 8
        },
        tint: color
    });
    createText({
        text: level < LEVELS ? 'X NEXT   Z MENU' : 'X OR Z MENU',
        transform: {
            x: 8,
            y: 20
        },
        tint: tint('#6b7390'),
    });

    useSpawn(Stars)({
        level
    });
    useUpdate(() => {
        if (keys.justPressed('x')) {
            // Itself again, with another number: a restart, covered like any other change.
            if (level < LEVELS) {
                scene.change(Level, {
                    props: {
                        level: level + 1
                    },
                    transition: fade(TRANSITION_TIME)
                });
            } else {
                scene.change(Menu, {
                    props: {
                        chosen: 1
                    },
                    transition: fade(TRANSITION_TIME)
                });
            }
        }

        if (keys.justPressed('z')) {
            scene.change(Menu, {
                props: {
                    chosen: level
                },
                transition: fade(TRANSITION_TIME)
            });
        }
    });

    return createScene();
};
```

`scenes/menu.ts`:

```ts
import { createScene, useScene, useSpawn } from 'nacatamalon';
import { fade } from 'nacatamalon/transitions';
import { LevelPicker } from '../components/level_picker';
import { Level } from './level';
import { TRANSITION_TIME } from '../settings';

export const Menu = ({ chosen = 1 }: { chosen?: number }) => {
    const scene = useScene();

    useSpawn(LevelPicker)({
        chosen,
        onPlay: (level) => scene.change(Level, {
            props: {
                level
            },
            transition: fade(TRANSITION_TIME)
        })
    });

    return createScene();
};
```

`settings.ts`:

```ts
import { getColor } from 'nacatamalon';

export const WIDTH = 320;
export const HEIGHT = 180;
export const LEVELS = 3;
export const tint = (hex: string) => getColor(hex);
export const COLORS = ['#ffcf4a', '#7fc8f8', '#ff7a90'];
export const FULL = COLORS.map(tint);
export const DIM = COLORS.map((hex) => ({
    ...getColor(hex),
    a: 0.35
}));

export const TRANSITION_TIME = 0.25;
```
