import React, { useState, useEffect, useRef, useCallback } from 'react'; import { Play, Square, Pause, SkipForward, X, Terminal as TerminalIcon, Info, ChevronRight, Save, RefreshCw, LayoutGrid, Cpu } from 'lucide-react'; /** * CodeHarvest - Protótipo de Jogo de Programação * Single File React Component */ // --- ENGINE & INTERPRETER --- // Um mini-interpretador para simular execução de Python de forma segura e controlada (step-by-step). // Isso permite pausar loops infinitos e controlar a velocidade de execução sem travar o navegador. const parseLine = (line) => { const clean = line.trim(); if (!clean || clean.startsWith('#')) return { type: 'noop' }; // Detecção de comandos básicos if (clean.startsWith('harvest()')) return { type: 'call', func: 'harvest' }; if (clean.startsWith('get_resources()')) return { type: 'call', func: 'get_resources' }; if (clean.match(/^sleep\(\s*(\d+(\.\d+)?)\s*\)/)) { const args = clean.match(/^sleep\(\s*(\d+(\.\d+)?)\s*\)/); return { type: 'call', func: 'sleep', args: [parseFloat(args[1])] }; } if (clean.match(/^log\((.*)\)/)) { const args = clean.match(/^log\((.*)\)/); return { type: 'call', func: 'log', args: [args[1].replace(/['"]/g, '')] }; } if (clean.match(/^build_block\(\s*(-?\d+)\s*,\s*(-?\d+)\s*\)/)) { const args = clean.match(/^build_block\(\s*(-?\d+)\s*,\s*(-?\d+)\s*\)/); return { type: 'call', func: 'build_block', args: [parseInt(args[1]), parseInt(args[2])] }; } // Controle de Fluxo Simplificado (Python-like) if (clean.startsWith('while True:')) return { type: 'loop_start', condition: 'true' }; if (clean.match(/^for\s+(\w+)\s+in\s+range\((\d+)\):/)) { const match = clean.match(/^for\s+(\w+)\s+in\s+range\((\d+)\):/); return { type: 'loop_fixed_start', var: match[1], count: parseInt(match[2]) }; } // Atribuição de variáveis simples (ex: x = 10) if (clean.match(/^(\w+)\s*=\s*(\d+)$/)) { const match = clean.match(/^(\w+)\s*=\s*(\d+)$/); return { type: 'assign', var: match[1], val: parseInt(match[2]) }; } // Condicionais simples (if coins > 10:) if (clean.match(/^if\s+(\w+)\s*(>|<|==|>=|<=)\s*(\d+):/)) { const match = clean.match(/^if\s+(\w+)\s*(>|<|==|>=|<=)\s*(\d+):/); return { type: 'if_start', var: match[1], op: match[2], val: parseInt(match[3]) }; } return { type: 'unknown', text: clean }; }; // --- GAME CONSTANTS & DATA --- const LEVEL_CAPS = [0, 100, 300, 600, 1000, 2000]; const BUILD_COST = 50; const TUTORIAL_STEPS = [ { id: 1, title: "Olá, Programador!", text: "Bem-vindo ao CodeHarvest. Sua tarefa é automatizar a coleta de recursos. Para começar, digite `harvest()` no terminal e clique em Run.", check: (logs) => logs.some(l => l.includes("Colheu")) }, { id: 2, title: "O Poder dos Loops", text: "Colher manualmente é chato. Vamos automatizar! Use um loop com sleep para colher continuamente sem travar o sistema.\n\nExemplo:\nwhile True:\n harvest()\n sleep(1)", check: (logs) => logs.filter(l => l.includes("Colheu")).length >= 3 }, { id: 3, title: "Construindo Impérios", text: "Você precisa de mais terras. Use `build_block(x, y)` para expandir sua ilha. Cada bloco custa 50 moedas. Tente construir em (1, 0) quando tiver dinheiro.", check: (grid) => Object.keys(grid).length > 1 } ]; const PRESET_SCRIPTS = { basic: "# Coleta básica\nharvest()\nlog('Trabalho feito!')", loop: "# Automação segura\nwhile True:\n harvest()\n log('Descansando...')\n sleep(2)", builder: "# Construtor Inteligente\nwhile True:\n harvest()\n sleep(1)\n # Se tiver moedas suficientes, construa\n if coins >= 50:\n build_block(1, 0)\n log('Nova terra conquistada!')" }; // --- MAIN COMPONENT --- export default function CodeHarvest() { // Game State const [resources, setResources] = useState(0); const [coins, setCoins] = useState(0); const [xp, setXp] = useState(0); const [level, setLevel] = useState(1); const [grid, setGrid] = useState({ '0,0': { type: 'grass', level: 1 } }); // Terminal State const [code, setCode] = useState(PRESET_SCRIPTS.basic); const [logs, setLogs] = useState([]); const [isRunning, setIsRunning] = useState(false); const [isPaused, setIsPaused] = useState(false); const [currentLine, setCurrentLine] = useState(-1); const [terminalPos, setTerminalPos] = useState({ x: 20, y: 100 }); const [terminalSize, setTerminalSize] = useState({ w: 400, h: 320 }); // Execution Context (Refs para acesso mutável durante o loop do game) const executionRef = useRef({ lines: [], pointer: 0, stack: [], // Para loops vars: {}, waitUntil: 0, harvestCount: 0, lastTick: Date.now(), maxTime: 0 }); const [tutorialStep, setTutorialStep] = useState(0); const [showTutorial, setShowTutorial] = useState(true); // --- GAME LOGIC --- const logMsg = (msg, type = 'info') => { setLogs(prev => [...prev.slice(-49), `[${new Date().toLocaleTimeString()}] ${msg}`]); }; const addXp = (amount) => { setXp(prev => { const newXp = prev + amount; if (newXp >= LEVEL_CAPS[level]) { setLevel(l => l + 1); logMsg(`SUBIU DE NÍVEL! Nível ${level + 1} alcançado!`, 'success'); } return newXp; }); }; const sandboxApi = { harvest: () => { // Verifica quotas const now = Date.now(); if (executionRef.current.harvestCount > 2) { // Max 2 por segundo (se ignorar sleep) // Penalidade leve, apenas falha logMsg("ERRO: Coleta muito rápida! Use sleep().", 'error'); return false; } const amount = Math.floor(Math.random() * 5) + 1; setResources(r => r + amount); setCoins(c => c + amount); // Conversão automática simples para MVP addXp(10); logMsg(`Colheu ${amount} recursos (+${amount} moedas)`); // Efeito visual (simulado via log por enquanto) executionRef.current.harvestCount++; return true; }, sleep: (seconds) => { executionRef.current.waitUntil = Date.now() + (seconds * 1000); }, log: (msg) => { logMsg(msg); }, build_block: (x, y) => { const key = `${x},${y}`; if (grid[key]) { logMsg(`Já existe um bloco em ${x},${y}`, 'warn'); return; } setCoins(currentCoins => { if (currentCoins >= BUILD_COST) { setGrid(prev => ({ ...prev, [key]: { type: 'grass', level: 1 } })); logMsg(`Construiu novo bloco em ${x},${y}`, 'success'); return currentCoins - BUILD_COST; } else { logMsg(`Moedas insuficientes (${currentCoins}/${BUILD_COST})`, 'error'); return currentCoins; } }); } }; // --- INTERPRETER ENGINE --- const startScript = () => { if (code.trim() === '') return; executionRef.current = { lines: code.split('\n'), pointer: 0, stack: [], vars: {}, waitUntil: 0, harvestCount: 0, lastTick: Date.now(), maxTime: Date.now() + (5 * 60 * 1000) // 5 min max run }; setIsRunning(true); setIsPaused(false); setLogs([]); logMsg("Script iniciado..."); }; const stopScript = () => { setIsRunning(false); setCurrentLine(-1); logMsg("Script parado."); }; const stepScript = useCallback(() => { if (!isRunning || isPaused) return; const ctx = executionRef.current; const now = Date.now(); // Verifica Timeout Global if (now > ctx.maxTime) { logMsg("Tempo limite de execução excedido (5 min).", 'error'); stopScript(); return; } // Verifica Sleep if (now < ctx.waitUntil) return; // Reset harvest quota a cada segundo if (now - ctx.lastTick > 1000) { ctx.harvestCount = 0; ctx.lastTick = now; } if (ctx.pointer >= ctx.lines.length) { stopScript(); return; } const lineRaw = ctx.lines[ctx.pointer]; setCurrentLine(ctx.pointer); const instruction = parseLine(lineRaw); // Lógica de indentação simplificada: // Se a linha atual tem indentação menor que o topo da stack, significa que saímos do bloco const currentIndent = lineRaw.search(/\S|$/); // Processamento de loops (verificação de fim de bloco) if (ctx.stack.length > 0) { const activeLoop = ctx.stack[ctx.stack.length - 1]; // Se a indentação da linha atual for menor ou igual à do inicio do loop, e não é a linha do loop em si if (currentIndent <= activeLoop.indent && ctx.pointer > activeLoop.startLine && lineRaw.trim() !== '') { // Fim do bloco do loop. Volta pro inicio. ctx.pointer = activeLoop.startLine; return; // Pula execução desta linha para reavaliar a condição do loop } } // Executa instrução switch (instruction.type) { case 'call': if (sandboxApi[instruction.func]) { sandboxApi[instruction.func](...instruction.args || []); } break; case 'loop_start': // while True ctx.stack.push({ type: 'while', startLine: ctx.pointer, indent: currentIndent }); break; case 'loop_fixed_start': // for i in range // Verifica se já iniciamos este loop const existingLoop = ctx.stack.find(l => l.startLine === ctx.pointer); if (existingLoop) { existingLoop.iter++; if (existingLoop.iter >= existingLoop.max) { // Loop acabou, remove da stack e avança até encontrar indentação menor ctx.stack.pop(); // Avançar ponteiro até sair do bloco (simplificado: deixa o parser normal lidar) // Um parser real pularia linhas aqui. Neste simples, ele vai executar e falhar a indentação // Hack rápido: setar flag para pular execução até indentação voltar } else { ctx.vars[instruction.var] = existingLoop.iter; } } else { ctx.stack.push({ type: 'for', startLine: ctx.pointer, indent: currentIndent, var: instruction.var, max: instruction.count, iter: 0 }); ctx.vars[instruction.var] = 0; } break; case 'if_start': // Avaliação muito simples de condicionais let val1 = ctx.vars[instruction.var]; if (instruction.var === 'coins') val1 = coins; // Hack para acessar state atual, idealmente passaria no contexto if (instruction.var === 'resources') val1 = resources; const val2 = instruction.val; let result = false; if (instruction.op === '>') result = val1 > val2; if (instruction.op === '>=') result = val1 >= val2; if (instruction.op === '<') result = val1 < val2; if (instruction.op === '<=') result = val1 <= val2; if (instruction.op === '==') result = val1 == val2; if (!result) { // Se falso, precisamos pular o bloco // Neste interpretador simples, vamos marcar para pular a próxima linha se ela for indentada // Isso é uma limitação do protótipo, mas funciona para ifs simples de 1 linha ctx.skipBlock = true; ctx.skipIndent = currentIndent; } break; case 'noop': case 'unknown': break; } // Avança linha ctx.pointer++; // Se skipBlock estiver ativo (para IFs falsos) if (ctx.skipBlock && ctx.pointer < ctx.lines.length) { const nextIndent = ctx.lines[ctx.pointer].search(/\S|$/); if (nextIndent > ctx.skipIndent) { ctx.pointer++; // Pula } else { ctx.skipBlock = false; } } }, [isRunning, isPaused, coins, resources, grid]); // Deps do stepScript // Game Loop Ticker useEffect(() => { let interval; if (isRunning && !isPaused) { interval = setInterval(stepScript, 100); // 10 ticks por segundo (execução rápida) } return () => clearInterval(interval); }, [isRunning, isPaused, stepScript]); // Checagem de Tutorial useEffect(() => { if (showTutorial && tutorialStep < TUTORIAL_STEPS.length) { const currentTask = TUTORIAL_STEPS[tutorialStep]; // Passa dados para checagem if (currentTask.check(logs, grid)) { logMsg(`TUTORIAL: ${currentTask.title} completado!`, 'success'); setTutorialStep(s => s + 1); } } }, [logs, grid, tutorialStep, showTutorial]); // --- UI HANDLERS --- // Draggable logic basic const startDrag = (e) => { if (e.target.closest('.no-drag')) return; const startX = e.clientX - terminalPos.x; const startY = e.clientY - terminalPos.y; const onMove = (mv) => { setTerminalPos({ x: mv.clientX - startX, y: mv.clientY - startY }); }; const onUp = () => { window.removeEventListener('mousemove', onMove); window.removeEventListener('mouseup', onUp); }; window.addEventListener('mousemove', onMove); window.addEventListener('mouseup', onUp); }; return (
{/* BACKGROUND GRID */}
{/* GAME WORLD (ISOMETRIC VIEW) */}
{Object.entries(grid).map(([pos, block]) => { const [x, y] = pos.split(',').map(Number); // Isometric Grid Coordinates const left = x * 60; const top = y * 60; return (
{/* Block CSS 3D Fake */}
{/* Top Highlight */}
{/* Side Fake (Depth) */}
{/* Resource Indicator */}
); })}
{/* HUD HEADER */}
{coins} Moedas
{resources} Recursos
Nível {level}
{/* TERMINAL WINDOW */}
{/* Terminal Bar */}
python_script.py
{/* Toolbar */}
{!isRunning ? ( ) : ( <> {isPaused && ( )} )}
{/* Editor Area */}
{/* Line Numbers & Highlight */}
{code.split('\n').map((_, i) => (
{i + 1}
))}
{/* Textarea */}