Ficção Interativa com IA: Uma Nova Fronteira

A combinação de Twine com modelos de linguagem (LLMs) abre possibilidades fascinantes: personagens que respondem de forma única a cada jogador, descrições que se adaptam ao contexto, ou mesmo uma IA que coescreve partes da narrativa em tempo real.

Neste tutorial, usaremos o formato SugarCube (mais amigável para JavaScript) e integraremos com uma API de LLM.

Requisitos

Este tutorial requer conhecimento básico de JavaScript e acesso a uma API de LLM (OpenAI, Anthropic ou Ollama local). Chaves de API da OpenAI têm custo; o Ollama permite rodar modelos localmente e gratuitamente.

Configurando o Story Format SugarCube

  1. No Twine, abra as configurações da story (ícone ≡ → “Change Story Format”)
  2. Selecione SugarCube 2.36

O SugarCube tem suporte nativo a JavaScript e macros como <<script>>.

Opção A: Usando a API da OpenAI

Passage: “Config” (execute uma vez)

<<silently>>
  <<set $apiKey to "SUA_CHAVE_OPENAI_AQUI">>
  <<set $historico to []>>
  <<set $nomePersonagem to "Viajante">>
<</silently>>

Segurança!

Nunca publique uma história com sua chave de API exposta no código. Para produção, use um servidor proxy que mantém a chave secreta. Esta abordagem só é segura para uso local e desenvolvimento.

Macro JavaScript para chamar a API

Crie um passage chamado "setup" (marcado como JavaScript):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// Função para chamar a OpenAI
window.chamarIA = async function(prompt, contexto) {
  const mensagens = [
    {
      role: "system",
      content: `Você é um narrador de ficção interativa de fantasia sombria. 
                Responda em português, em 2-3 parágrafos vívidos. 
                Contexto: ${contexto}`
    },
    { role: "user", content: prompt }
  ];

  try {
    const response = await fetch("https://api.openai.com/v1/chat/completions", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        "Authorization": `Bearer ${State.variables.apiKey}`
      },
      body: JSON.stringify({
        model: "gpt-4o-mini",
        messages: mensagens,
        max_tokens: 300,
        temperature: 0.8
      })
    });

    const data = await response.json();
    return data.choices[0].message.content;
  } catch (e) {
    return "*(A névoa espessa impede qualquer visão. Tente novamente.)*";
  }
};

Usando a IA em um Passage

:: Sala Misteriosa
<<set $localAtual to "uma sala de pedra com tochas apagadas">>

<<set _descricao to "">>

<<button "✨ Descreva este local com IA">>
  <<script>>
    const loader = document.getElementById("ia-output");
    loader.innerHTML = "<em>A IA está narrando…</em>";
    
    chamarIA(
      "Descreva este local de forma atmosférica para um jogador de RPG textual",
      State.variables.localAtual
    ).then(texto => {
      loader.innerHTML = texto;
      State.variables.descricaoIA = texto;
    });
  <</script>>
<</button>>

<div id="ia-output" style="margin:1em 0;padding:1em;background:rgba(255,255,255,0.05);border-left:3px solid purple;min-height:3em;border-radius:4px">
  <em>Clique no botão para gerar uma descrição narrativa.</em>
</div>

[[Explorar mais fundo]]
[[Sair correndo]]

Opção B: Usando Ollama (Gratuito, Local)

O Ollama permite rodar modelos como Llama 3, Mistral e Gemma localmente, sem custo.

Instalação do Ollama

1
2
3
4
5
6
7
8
# Linux/macOS
curl -fsSL https://ollama.ai/install.sh | sh

# Baixar um modelo (ex: llama3.2)
ollama pull llama3.2

# Iniciar o servidor (roda na porta 11434)
ollama serve

Chamada à API local no SugarCube

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
window.chamarIALocal = async function(prompt) {
  try {
    const response = await fetch("http://localhost:11434/api/generate", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({
        model: "llama3.2",
        prompt: `Você é um narrador de ficção interativa em português. 
                 Responda em 2 parágrafos. ${prompt}`,
        stream: false
      })
    });
    const data = await response.json();
    return data.response;
  } catch (e) {
    return "*(Conexão com a IA local indisponível. Verifique se o Ollama está rodando.)*";
  }
};

CORS com Ollama

Para chamadas do Twine local ao Ollama, você pode precisar iniciar o Ollama com OLLAMA_ORIGINS=* ollama serve para permitir requisições cross-origin.

Sistema de Personagem NPC com Memória

Um caso de uso poderoso: um NPC que “lembra” das ações do jogador.

:: Taverna — Falar com a Estalajadeira
<<if $falasComInnkeeper is undefined>>
  <<set $falasComInnkeeper to []>>
<</if>>

<<textbox "$mensagemJogador" "O que você diz para a estalajadeira?">>

<<button "Falar">>
  <<script>>
    const msg = State.variables.mensagemJogador;
    const historico = State.variables.falasComInnkeeper;
    
    const contexto = historico.length > 0 
      ? `Histórico: ${historico.slice(-3).join(" | ")}` 
      : "Primeiro encontro";

    chamarIALocal(
      `Você é uma estalajadeira desconfiada em uma fantasia sombria. 
       ${contexto}. O jogador diz: "${msg}". Responda em 1-2 frases curtas e caracteristicamente.`
    ).then(resposta => {
      document.getElementById("npc-fala").innerHTML = 
        `<strong>Estalajadeira:</strong> "${resposta}"`;
      State.variables.falasComInnkeeper.push(`Jogador: ${msg} | NPC: ${resposta}`);
    });
  <</script>>
<</button>>

<div id="npc-fala" style="margin:1em 0;font-style:italic;color:#d4a">
  <em>A estalajadeira aguarda você falar.</em>
</div>

[[Pedir um quarto]]
[[Perguntar sobre rumores]]
[[Sair da taverna]]

Boas Práticas

Prática Motivo
Limite max_tokens Evita respostas longas demais e custos altos
Mostre feedback de carregamento A API pode demorar 1-3 segundos
Sempre tenha fallback A API pode falhar; ofereça conteúdo padrão
Use temperatura 0.7–0.9 Equilibra criatividade e coerência
Forneça contexto no system prompt Quanto mais contexto, melhor a resposta
Teste com Ollama primeiro Evite custos de API durante desenvolvimento

Próximos Passos