# ads

sdk.ads.rewarded() and interstitial() show an ad where the host has one; available() says whether to draw the button; on() tells you when to mute.

filudi.com has no ad network today: both ad kinds answer `unavailable` with reason `host` there. On
CrazyGames, Poki and Playgama the SDK maps them to the portal's own ads. Write the game so that every
result is handled, and nothing depends on an ad showing.

## `ads.rewarded(): Promise<AdResult>`

Grant the reward on `status === 'completed'` and on nothing else. `skipped`, `unavailable` and `error`
give nothing: a game that rewards an unavailable ad gives free rewards on every host that has none.

<CodePanel title="A rewarded ad">
  <Fragment slot="javascript">
    ```js
    lifeButton.addEventListener('click', async () => {
      lifeButton.hidden = true;
      const result = await sdk.ads.rewarded();
      if (result.status === 'completed') grantSecondLife();
    });
    ```

  </Fragment>
  <Fragment slot="typescript">
    ```ts
    import type { AdResult } from '@filudi/game-sdk';

    lifeButton.addEventListener('click', async () => {
      lifeButton.hidden = true;
      const result: AdResult = await sdk.ads.rewarded();
      if (result.status === 'completed') grantSecondLife();
    });
    ```

  </Fragment>
</CodePanel>

## `ads.interstitial(): Promise<AdResult>`

At a natural break, after `gameplay.stop()`: a game over, a level end. The SDK refuses it with `too-soon`
inside the host's minimum interval, 120 s on filudi.com and on the mock, counted from the end of the last
ad shown of either kind. Portals pace their own interstitials. The result is informative; most games
ignore it.

<CodePanel title="An interstitial at game over">
  <Fragment slot="javascript">
    ```js
    async function gameOver() {
      sdk.gameplay.stop();
      overs += 1;
      if (overs % 3 === 0) await sdk.ads.interstitial();
      showGameOver();
    }
    ```

  </Fragment>
  <Fragment slot="typescript">
    ```ts
    async function gameOver(): Promise<void> {
      sdk.gameplay.stop();
      overs += 1;
      if (overs % 3 === 0) await sdk.ads.interstitial();
      showGameOver();
    }
    ```

  </Fragment>
</CodePanel>

## `AdResult`

<CodePanel title="AdResult">
  <Fragment slot="javascript">
    ```js
    const result = await sdk.ads.rewarded();
    switch (result.status) {
      case 'completed':   // rewarded: grant the reward
      case 'skipped':     // the player closed it early
      case 'unavailable': // result.reason: 'no-fill' | 'adblock' | 'host' | 'policy' | 'busy' | 'too-soon'
      case 'error':       // result.message
    }
    ```

  </Fragment>
  <Fragment slot="typescript">
    ```ts
    type AdResult =
      | { status: 'completed' }            // rewarded: grant the reward
      | { status: 'skipped' }              // the player closed it early
      | { status: 'unavailable'; reason: 'no-fill' | 'adblock' | 'host' | 'policy' | 'busy' | 'too-soon' }
      | { status: 'error'; message: string };
    ```

  </Fragment>
</CodePanel>

## Rules the SDK applies

- One ad at a time. A second call while one runs returns `unavailable` with reason `busy`.
- Before an ad the SDK calls `gameplay.stop()` and fires the `start` event; after it, the `end` event, then
  `gameplay.start()` if play was running. The game does not bracket ads itself.
- Without ad consent the filudi host answers `unavailable` with reason `policy`.
- No answer from the host within 90 s is `{ status: 'error', message: 'host timeout' }`.

## `ads.on(type, listener): Off`

`type` is `'start'` or `'end'`. Mute audio on `start`, not when you request the ad: a refused ad never
starts, and the game would stay silent. The listener takes no argument. The call returns a function that
removes the listener.

<CodePanel title="Mute during an ad">
  <Fragment slot="javascript">
    ```js
    sdk.ads.on('start', () => audio.mute());
    sdk.ads.on('end', () => audio.unmute());
    ```

  </Fragment>
  <Fragment slot="typescript">
    ```ts
    import type { Off } from '@filudi/game-sdk';

    const offStart: Off = sdk.ads.on('start', () => audio.mute());
    const offEnd: Off = sdk.ads.on('end', () => audio.unmute());
    // offStart(); offEnd();   when the scene that owns the audio goes away
    ```

  </Fragment>
</CodePanel>

## `ads.available(): { rewarded: boolean; interstitial: boolean }`

Whether a "watch an ad" button is worth drawing. It can change after `init()` (adblock detection): read it
each time you draw the button, not once. Throws before `init()` resolves.

<CodePanel title="Draw the button only when an ad can show">
  <Fragment slot="javascript">
    ```js
    function showGameOver() {
      lifeButton.hidden = usedLife || !sdk.ads.available().rewarded;
    }
    ```

  </Fragment>
  <Fragment slot="typescript">
    ```ts
    import type { AdsAvailability } from '@filudi/game-sdk';

    function showGameOver(): void {
      const ads: AdsAvailability = sdk.ads.available();
      lifeButton.hidden = usedLife || !ads.rewarded;
    }
    ```

  </Fragment>
</CodePanel>

## Unavailable cases

| Reason     | When                                                                                           |
| ---------- | ---------------------------------------------------------------------------------------------- |
| `host`     | The host has no ads: filudi.com today, the standalone mock, a portal in a disabled environment |
| `no-fill`  | The portal had no ad to show                                                                   |
| `adblock`  | The portal detected an ad blocker; `available()` turns both kinds off                          |
| `policy`   | No ad consent, or the site's age policy forbids ads                                            |
| `busy`     | Another ad is running                                                                          |
| `too-soon` | An interstitial inside the host's minimum interval                                             |

Where to put ads in a game, and what filudi.com offers, is in [ads on portals](/docs/games/ads/).
