---
sidebar_position: 3
title: "Passo a Passo"
---

Roteiro completo para criar uma nova atividade de programação do zero. Use a atividade `Exemplo` como gabarito de cada etapa.

:::tip 
Se sua atividade for visual (sprite em grade/mapa), copie `exemplo/`. Se for baseada em texto ou saída de dados, copie `exemplo2/`. Depois siga este guia para entender o que adaptar.
:::

---

## Passo 1 — Estrutura de pastas

Crie a pasta da atividade em `app/src/atividades/programacao/<nome-da-atividade>/`:

```
meu-jogo/
├── MeuJogoGame.jsx          ← componente de entrada (React)
├── game.js                  ← cena Phaser + factory createGame
├── blocks/
│   └── blocks.js            ← definição e geradores dos blocos
├── config/
│   └── config.js            ← fases, maxBlocks, mensagens
├── hooks/
│   └── setupMeuJogoAPI.js   ← ponte JS-Interpreter ↔ Phaser
├── validation/
│   └── validators.js        ← regras de sucesso e falha
└── ui/
    ├── constants.js         ← assets, cores, dimensões
    └── layout.js            ← funções de construção visual
```

---

## Passo 2 — `config/config.js`

O config é a **espinha dorsal pedagógica** da atividade. Define todas as fases e seus parâmetros.

```javascript
export const gameConfig = {
  gameId: "meu-jogo",
  gameName: "Meu Jogo",
  route: "/atividades/programacao/meu-jogo",
  component: "MeuJogoGame",

  fases: [
    {
      id: 1,
      nome: "Fase 1: Introdução",
      descricao: "Descrição curta e motivadora do desafio desta fase.",
      timeout: 15,       // segundos — protege contra loops infinitos
      maxBlocks: 4,      // limite pedagógico (não técnico)
      allowedBlocks: ["meu_bloco_a", "meu_bloco_b"],

      // Dados específicos do jogo (lidos em montarFase)
      // ex: posição inicial, mapa, obstáculos...
    },
  ],

  mensagens: {
    semMovimento: "Você não executou nenhuma ação!",
    // Mensagens específicas da sua atividade...
  },
};
```

**Checklist do config:**

- [ ] `allowedBlocks` contém apenas os blocos necessários para **esta fase**
- [ ] `maxBlocks` força a solução elegante (não a solução de força bruta)
- [ ] `timeout` é generoso o suficiente para o aluno ter tempo de ver a execução
- [ ] Mensagens de erro são em linguagem acessível (sem jargão técnico)

---

## Passo 3 — `blocks/blocks.js`

Define os blocos visuais e seus geradores de código JavaScript.

```javascript
"use strict";
import "blockly/blocks";
import { CORES_CUSTOMIZADAS } from "@/blockly/blocklyColors";
import { configurarGerador, gerarStatement } from "@/blockly/generator";
import { gerarToolboxDeEstrutura } from "@/blockly/toolbox";
import { criarBlocoStatementSimples } from "@/blockly/blockFactory";

const ESTRUTURA_TOOLBOX = [
  {
    nome: "Movimento",
    cor: CORES_CUSTOMIZADAS.MOVIMENTO,
    cssContainer: "cat_movimento",
    blocos: ["meu_bloco_a"],
  },
];

export const registerBlocks = () => {
  defineBlocks();
  defineGenerators();
};

export const generateDynamicToolbox = (allowedBlocks = []) =>
  gerarToolboxDeEstrutura(ESTRUTURA_TOOLBOX, allowedBlocks);

const defineBlocks = () => {
  criarBlocoStatementSimples("meu_bloco_a", "executar ação A", CORES_CUSTOMIZADAS.MOVIMENTO);
};

const defineGenerators = () => {
  configurarGerador(); // ← sempre chamar primeiro (ativa highlight)
  gerarStatement("meu_bloco_a", "executarAcaoA");
};
```

### Factories de bloco disponíveis (`@/blockly/blockFactory`)

| Factory | Quando usar |
|---|---|
| `criarBlocoStatementSimples` | Ação sem parâmetros: `mover()`, `girar()` |
| `criarBlocoStatementComDropdown` | Ação com opção: `virar('DIREITA' \| 'ESQUERDA')` |
| `criarBlocoStatementComValor` | Ação com input de valor: `imprimir(texto)`, `repetir(n)` |
| `criarBlocoExpressaoSimples` | Condição sem parâmetros: `aindaTemSujeira()` |
| `criarBlocoExpressaoComDropdown` | Condição com opção: `caminhoBloqueado('FRENTE')` |
| `criarBlocoCondicional` | Bloco Se/Faça |
| `criarBlocoNegacao` | Bloco NÃO |

### Blocos nativos do Blockly (não precisam de `defineBlocks`)

Estes blocos estão disponíveis após `import "blockly/blocks"` — adicione-os direto em `ESTRUTURA_TOOLBOX` e em `allowedBlocks`:

| ID | Bloco |
|---|---|
| `controls_repeat_ext` | Repetir N vezes |
| `controls_whileUntil` | Enquanto / Até |
| `math_number` | Número literal |
| `text` | Texto literal (string) |
| `text_join` | Juntar textos |
| `robo_if` / `robo_if_else` | Se / Se-Senão (customizados da plataforma) |

---

## Passo 4 — `ui/constants.js` e `ui/layout.js`

Mantenha **todo código visual fora do `game.js`**. A cena deve ser legível — quem a lê não deve precisar vasculhar criação de objetos Phaser.

**`ui/constants.js`** — valores que nunca mudam:

```javascript
// Assets em /public — URL direta, sem import de módulo
export const Assets = {
  CHAVES: { PERSONAGEM: "meu_personagem" },
  PATHS: { PERSONAGEM: "/img/meu-personagem.png" },
};

export const Constantes = { CELL_SIZE: 80, COLS: 6, ROWS: 6 };

export const Cores = { FUNDO: 0x1a1a2e, ALVO: 0x69f0ae };
```

:::info 
**Assets em `/public` vs. `src/`**

Arquivos em `public/` (ex: `public/img/logo.png`) são servidos diretamente pelo servidor web. Referencie-os com a string da URL: `"/img/logo.png"`.  
Arquivos em `src/atividades/.../assets/` devem ser importados como módulos: `import img from "./assets/img.png"`. O Vite os processa e otimiza durante o build.
:::

**`ui/layout.js`** — funções que constroem a cena:

```javascript
import { Assets, Constantes, Cores } from "./constants.js";

export function montarCenario(scene, cols, rows) { /* ... */ }
export function criarPersonagem(scene, col, row) { /* ... */ }
export function criarAlvo(scene, col, row) { /* ... */ }
```

---

## Passo 5 — `hooks/setupMeuJogoAPI.js`

A **ponte** entre o código do aluno (rodando no JS-Interpreter) e a cena Phaser.

```javascript
import { ApiHelpers } from "../../../../interpreters/ApiHelpers.js";

export const setupMeuJogoAPI = (scene, config) => {
  const delay = config?.animationSpeed || 200;

  // Retorna a função de init do js-interpreter
  return function(interpreter, globalScope) {

    // Ações com animação → createAsyncFunction (aguarda callback para continuar)
    ApiHelpers.registerFunction(
      interpreter, globalScope,
      "executarAcaoA",
      ApiHelpers.createActionWrapper(scene, "executarAcaoA", delay),
      true  // isAsync = true
    );

    // Condições síncronas → createNativeFunction (retorna valor imediatamente)
    ApiHelpers.registerFunction(
      interpreter, globalScope,
      "verificarCondicao",
      ApiHelpers.createConditionWrapper(scene, "verificarCondicao"),
      false // isAsync = false
    );

    // Obrigatório para o highlight de blocos durante a execução
    ApiHelpers.registerFunction(
      interpreter, globalScope,
      "highlightBlock",
      ApiHelpers.createHighlightWrapper(scene),
      false
    );
  };
};
```

**Regra de ouro:**
- Funções que **animam algo** → `createAsyncFunction` (`isAsync: true`)
- Funções que **retornam um valor** (sensores, condições) → `createNativeFunction` (`isAsync: false`)

---

## Passo 6 — `game.js`

A cena Phaser. Herda `BaseGameScene` e implementa os hooks do ciclo de execução.

```javascript
import Phaser from "phaser";
import { BaseGameScene } from "../../../shared/BaseGameScene.js";
import { setupMeuJogoAPI } from "./hooks/setupMeuJogoAPI.js";
import { validationSolution } from "./validation/validators.js";
import { gameConfig } from "./config/config.js";
import { Assets, Constantes } from "./ui/constants.js";
import { montarCenario, criarPersonagem, criarAlvo } from "./ui/layout.js";

export class MeuJogoScene extends BaseGameScene {
  constructor() {
    super("MeuJogoScene");
    // Estado lógico do jogo (NÃO posição visual — isso é do Phaser)
    this.posicaoLogica = { x: 0, y: 0 };
    this.executionStopped = false;
  }

  preload() {
    this.preloadGlobalAssets(); // sons globais de sucesso/falha
    this.load.image(Assets.CHAVES.PERSONAGEM, Assets.PATHS.PERSONAGEM);
  }

  create() {
    // 1. Cria o validador com referência à cena (para ler estado em tempo de validação)
    this.validatorFunc = (historico) =>
      validationSolution(historico, this.configFase, gameConfig, this);

    // 2. Conecta o interpreter e os handlers de run/reset ao gameEventBus
    this.setupStandardController(
      () => setupMeuJogoAPI(this, { animationSpeed: 200 }),
      this.validatorFunc
    );

    this.montarFase();
  }

  onBeforeRun() {
    this.isRunning = true;
    this.historico = [];
    this.executionStopped = false;
  }

  onReset() {
    this.isRunning = false;
    this.executionStopped = true;
    this.montarFase();
  }

  async onSuccess() {
    this.isRunning = false;
    // Animação de vitória antes do modal aparecer
    return new Promise(resolve => {
      this.tweens.add({ targets: this.personagemSprite, /* ... */, onComplete: resolve });
    });
  }

  async onFailure() {
    this.isRunning = false;
    // Animação de falha antes do modal aparecer
    return new Promise(resolve => {
      this.tweens.add({ targets: this.personagemSprite, /* ... */, onComplete: resolve });
    });
  }

  montarFase() {
    if (this.personagemSprite) this.personagemSprite.destroy();
    // ... destroy outros sprites
    this.personagemSprite = criarPersonagem(this, 0, 0);
    this.executionStopped = false;
  }

  // --- API exposta ao interpreter ---

  executarAcaoA() {
    if (this.executionStopped) return Promise.resolve();
    // Lógica da ação, registro no historico, animação...
    this.historico.push({ tipo: "acao_a" });
    return this._animarPersonagem(() => this._checarCondicaoFim());
  }

  _checarCondicaoFim() {
    if (this.executionStopped) return;
    // ... verifica sucesso ou falha ...
    // Sucesso detectado mid-execution:
    this.executionStopped = true;
    this.gameInterpreter.stopInternal(); // para sem marcar como parado pelo usuário
    this.time.delayedCall(300, () => this.handleValidation(this.validatorFunc));
    // Falha detectada mid-execution:
    // this.executionStopped = true;
    // this.gameInterpreter.stopInternal();
    // this.time.delayedCall(100, () => this.handleFailure("Mensagem de falha"));
  }
}

export const createGame = (elementoPai, configFaseAtual) => {
  const scene = new MeuJogoScene();
  return {
    type: Phaser.AUTO,
    width: /* largura */,
    height: /* altura */,
    parent: elementoPai,
    scale: { mode: Phaser.Scale.FIT, autoCenter: Phaser.Scale.CENTER_BOTH },
    scene,
    callbacks: {
      // Injeta configs no registry antes do boot → BaseGameScene.init() as lê
      preBoot: (game) => {
        game.registry.set("configFase", configFaseAtual);
        game.registry.set("gameConfig", gameConfig);
      },
    },
  };
};
```

### Quando usar `stopInternal()` vs. deixar o interpreter terminar

| Cenário | Abordagem |
|---|---|
| Condição de fim detectada **durante** uma ação (ex: chegou ao alvo) | `stopInternal()` + `handleValidation()` com delay |
| Condição de falha detectada **durante** uma ação (ex: saiu da tela) | `stopInternal()` + `handleFailure()` com delay |
| Execução termina naturalmente (todos os blocos rodaram) | Nada — `BaseGameScene` agenda `handleValidation()` automaticamente |

---

## Passo 7 — `validation/validators.js`

```javascript
import { BaseGameValidator } from "../../../../shared/BaseGameValidator";

export class MeuJogoValidator extends BaseGameValidator {
  validatePhase(history, config, gameConfig, sceneRef) {
    // sceneRef é a cena Phaser — leia o estado lógico direto dela
    const { posicaoLogica } = sceneRef;
    const alvo = config?.alvo;

    if (posicaoLogica.x === alvo.x && posicaoLogica.y === alvo.y) {
      return this.success();
    }

    return this.failure(
      gameConfig?.mensagens?.naoAlcancou || "Não chegou ao destino. Tente de novo!"
    );
  }
}

export function validationSolution(history, config, gameConfig, sceneRef) {
  return new MeuJogoValidator().validate(history, config, gameConfig, sceneRef);
}
```

`BaseGameValidator.validate()` já executa antes do seu código:
1. Verifica se a config da fase existe.
2. Verifica se o `historico` tem ao menos uma ação (evita validação de tela em branco).
3. (Opcional) Verifica sequência exata via `configFase.expectedSequence`.

Implemente apenas as **regras específicas** do seu jogo em `validatePhase()`.

---

## Passo 8 — `MeuJogoGame.jsx`

O componente React de entrada segue sempre o mesmo padrão:

```jsx
import React, { useEffect, useMemo } from "react";
import GameBase from "../../../components/game/GameBase";
import GameEditor from "../../../components/game/GameEditor";
import BlocklyEditor from "../../../components/game/editors/BlocklyEditor";
import { createGame } from "./game";
import { gameConfig } from "./config/config";
import { generateDynamicToolbox, registerBlocks } from "./blocks/blocks";
import { GameStateProvider, useGameState } from "../../../contexts/GameStateContext";

function MeuJogoContent() {
  const { setFailureMessage } = useGameState();

  useEffect(() => { registerBlocks(); }, []);

  const toolboxGenerator = useMemo(
    () => (allowedBlocks) => generateDynamicToolbox(allowedBlocks),
    []
  );

  return (
    <GameBase
      gameFactory={createGame}
      gameConfig={gameConfig}
      customFailureHandler={setFailureMessage}
      failureHandler={setFailureMessage}
    >
      <GameEditor>
        <BlocklyEditor toolboxGenerator={toolboxGenerator} />
      </GameEditor>
    </GameBase>
  );
}

export default function MeuJogoGame() {
  return (
    <GameStateProvider gameConfig={gameConfig}>
      <MeuJogoContent />
    </GameStateProvider>
  );
}
```

---

## Passo 9 — Registrar a rota em `App.jsx`

```jsx
// app/src/App.jsx

// 1. Adicionar o lazy import com os outros jogos:
const MeuJogoGame = lazy(() => import("./atividades/programacao/meu-jogo/MeuJogoGame"));

// 2. Adicionar a rota dentro de <Routes>:
<Route path="/atividades/programacao/meu-jogo" element={<MeuJogoGame />} />
```

---

## Checklist final

Antes de considerar a atividade pronta, verifique:

- [ ] A atividade roda sem erros no console do navegador
- [ ] O fluxo de **Sucesso** dispara o modal corretamente
- [ ] O fluxo de **Falha** dispara o modal com a mensagem certa
- [ ] O botão **Reset** restaura o estado visual e lógico da fase
- [ ] O `timeout` da fase é suficiente para o aluno executar a solução correta
- [ ] O `maxBlocks` é suficiente para a solução ótima, mas não para a solução "preguiçosa"
- [ ] As mensagens de erro são compreensíveis para um aluno iniciante
- [ ] A rota está registrada em `App.jsx`
- [ ] O código visual (criação de sprites, cores, fontes) está em `ui/`, não em `game.js`