# Create your first game

> Scaffold a game, understand how it runs, and play it in a test room.

This guide shows how a disko game works, using the project you created in
[Install](/get-started/install), and plays it in a private test room.

## The template

The template is a small team game: players pick Red or Blue in a Menu, move
their disc with the arrow keys or WASD, and kick with Space. A HUD shows the
score and the clock.

```text
arena/
  game.json        the manifest: version and entry module
  src/main.ts      where the game starts
  src/menu.ts      the Menu
  src/hud.ts       the HUD
  src/players.ts   player discs and controls
  src/teams.ts     team definitions
```

## How a game runs

A game is not a loop you write yourself. disko runs your code at specific
moments, and your code reacts.

**When a room loads your game,** your top-level code runs once. This is where
you set things up: build the Menu, declare which actions players can use, and
register handlers with `game.on(...)` for the moments you care about.

**When a match starts,** a fresh physical world is created: discs, walls,
goals. Players who join the match get something to control. While the match
runs, disko moves everything forward 60 times a second and calls your
handlers when something happens, such as a disc crossing a goal line.

**When the match stops,** the world is thrown away. The Menu stays, ready for
the next match.

```mermaid alt="When the game loads, its top-level code runs once and the game is Ready, showing its Menu. Starting a match creates the world and the game is Running; stopping it discards the world and returns to Ready."
stateDiagram-v2
  [*] --> Ready: game loaded
  Ready --> Running: start
  Running --> Ready: stop
```

So there are two kinds of state in a game. Things that should survive between
matches, like the Menu or the team each player picked, live in top-level
variables. Things that belong to one match, like discs, are created when it
starts and forgotten when it stops.

## A minimal game

The smallest useful game has a Menu with a Start button, and a disc for each
player in the match.

### The Menu and the Start button

The Menu is what players see before and between matches. You describe it with
`ui` functions: here, a line of text and a button.

A button doesn't run code in the browser. When someone presses it, the
player's browser sends a **request** to the game, and your `ui-request`
handler decides what to do. That's how a game stays in charge of its own
rules: a request can be refused.

```ts title="src/main.ts"
// Top-level code runs once when the game loads: it builds the Menu.
ui.menu(
  ui.stack({
    children: [
      ui.text({ text: "My first disko game" }),
      ui.button({
        label: "Start",
        on: { activate: ui.request({ event: "start" }) },
      }),
    ],
  }),
);

game.on("ui-request", (request) => {
  if (request.event === "start") {
    request.player.play();
    game.start();
  }
});
```

`request.player.play()` puts the player who pressed Start into the match, and
`game.start()` begins it.

### Discs for the players in the match

Players come and go. Rather than tracking that yourself, react to two events:
`player-enter` when someone joins the running match, and `player-exit` when
they leave it. Create a disc on enter and remove it on exit.

```ts title="src/main.ts"
game.on("player-enter", ({ player }) => {
  discs.set(player.id, spawnDisc(player, discs.size));
});

game.on("player-exit", ({ player }) => {
  const disc = discs.get(player.id);

  if (disc !== undefined) {
    world.destroy(disc);
    discs.delete(player.id);
  }
});

game.on("stop", () => {
  discs.clear();
});
```

When the match stops, the world and every disc in it are gone, so the map of
discs is simply cleared.

### Creating a disc

A disc is a round physical body. `world.createDisc` places one in the world,
`setAppearance` colors it, and `player.control` connects it to that player's
keyboard.

Kicking is an **action**. An action schema lists the actions a player can use;
here, a single Kick that disko's physics already knows how to perform.

```ts title="src/players.ts"
const controls = game.createActionSchema();

controls.defineAction({ label: "Kick", behavior: { type: "kick" } });

/** Creates a disc for a player and gives them control of it. */
export function spawnDisc(player: Disko.Player, index: number): Disko.Disc {
  const disc = world.createDisc({ x: index * 30, y: 0, radius: 15 });

  disc.setAppearance({ fill: "#63b9f2", stroke: "#000000", strokeWidth: 2 });
  player.control(disc);
  player.useActionSchema(controls);

  return disc;
}
```

## Play it

```sh
npx disko dev
```

This starts a private [test room](/get-started/test-rooms) and opens it in
your browser. Leave it running: every time you save, the game reloads.

## Next

When you're happy with your game, [publish a release](/get-started/publish)
so any room can load it, or [host a room](/get-started/host-a-room) of your
own.
