"use client"; import { useState, useRef, useCallback, useEffect } from "react"; import { Button } from "@/components/ui/Button"; import { Dropdown } from "@/components/ui/Dropdown"; import { Slider } from "@/components/ui/Slider"; import { SidePanel } from "@/components/ui/SidePanel"; import { AgentSelector } from "@/components/ui/AgentSelector"; import { Editor } from "@/components/Editor"; import { Terminal, TerminalRef } from "@/components/Terminal"; import { sendDataToScript, startScript, startScriptBulk, stopScript, } from "@/lib/runner"; import styles from "@/styles/Playground.module.css"; const initialCode = `fn random_action(board) { let symb = rand_symb(); let pos = rand_int(0, 10); let action = Action(symb, pos); // If this action is invalid, randomly select a new one. while !board.can_play(action) { let symb = rand_symb(); let pos = rand_int(0, 10); action = Action(symb, pos); } return action; } fn step_min(board) { return random_action(board); } fn step_max(board) { return random_action(board); }`; const AGENTS = { // special-cased below Self: undefined, Random: `fn random_action(board) { let symb = rand_symb(); let pos = rand_int(0, 10); let action = Action(symb, pos); while !board.can_play(action) { let symb = rand_symb(); let pos = rand_int(0, 10); action = Action(symb, pos); } return action; } fn step_min(board) { return random_action(board); } fn step_max(board) { return random_action(board); }`, }; export default function Playground() { const [isScriptRunning, setIsScriptRunning] = useState(false); const [isEditorReady, setIsEditorReady] = useState(false); const [fontSize, setFontSize] = useState(14); const [bulkRounds, setBulkRounds] = useState(1000); const [selectedAgent, setSelectedAgent] = useState("Random"); const [isHelpOpen, setIsHelpOpen] = useState(false); const [scriptName, setScriptName] = useState(""); const [saveSecret, setSaveSecret] = useState(""); const [isSaving, setIsSaving] = useState(false); const [availableAgents, setAvailableAgents] = useState([]); const [savedScripts, setSavedScripts] = useState>( {} ); const editorRef = useRef(null); const resultRef = useRef(null); const terminalRef = useRef(null); const runDisabled = isScriptRunning || !isEditorReady; const stopDisabled = !isScriptRunning; // Fetch saved scripts and update available agents const loadSavedScripts = useCallback(async () => { try { const response = await fetch("/api/list-scripts"); const data = await response.json(); if (response.ok) { const scripts = data.scripts || []; const scriptContents: Record = {}; // Fetch content for each saved script await Promise.all( scripts.map(async (scriptName: string) => { try { const scriptResponse = await fetch( `/api/get-script?name=${encodeURIComponent( scriptName )}` ); const scriptData = await scriptResponse.json(); if (scriptResponse.ok) { scriptContents[scriptName] = scriptData.content; } } catch (error) { console.error( `Failed to load script ${scriptName}:`, error ); } }) ); setSavedScripts(scriptContents); // Combine hardcoded agents with saved scripts, ensuring Self and Random are first const combinedAgents = [ "Self", "Random", ...Object.keys(AGENTS).filter( (key) => key !== "Self" && key !== "Random" ), ...scripts, ]; setAvailableAgents(combinedAgents); } } catch (error) { console.error("Failed to load saved scripts:", error); // Fallback to hardcoded agents only setAvailableAgents(Object.keys(AGENTS)); } }, []); // Load saved scripts on component mount useEffect(() => { loadSavedScripts(); }, [loadSavedScripts]); const runHuman = useCallback(async () => { if (resultRef.current) { resultRef.current.value = ""; } if (runDisabled || !editorRef.current) return; setIsScriptRunning(true); try { terminalRef.current?.clear(); terminalRef.current?.focus(); await startScript( editorRef.current.getValue(), (line: string) => { if (resultRef.current) { let v = resultRef.current.value + line + "\n"; if (v.length > 10000) { v = v.substring(v.length - 10000); } resultRef.current.value = v; resultRef.current.scrollTop = resultRef.current.scrollHeight - resultRef.current.clientHeight; } }, (line: string) => { terminalRef.current?.write(line); } ); } catch (ex) { console.error(ex); if (resultRef.current) { resultRef.current.value += `\nScript exited with error:\n${ex}\n`; } terminalRef.current?.write( "\r\n\x1B[1;31mScript exited with error:\x1B[0m\n\r" + String(ex).replace("\n", "\n\r") + "\r\n" ); } setIsScriptRunning(false); }, [runDisabled]); const runBulk = useCallback(async () => { if (resultRef.current) { resultRef.current.value = ""; } if (runDisabled) return; setIsScriptRunning(true); try { terminalRef.current?.clear(); terminalRef.current?.focus(); // Get script content from either hardcoded agents or saved scripts const hardcodedAgent = AGENTS[selectedAgent as keyof typeof AGENTS]; const savedScript = savedScripts[selectedAgent]; const agentScript = hardcodedAgent || savedScript; const blueScript = agentScript || (editorRef.current?.getValue() ?? ""); const redScript = editorRef.current?.getValue() ?? ""; const opponentName = agentScript ? selectedAgent : "script"; await startScriptBulk( redScript, blueScript, opponentName, (line: string) => { if (resultRef.current) { let v = resultRef.current.value + line + "\n"; if (v.length > 10000) { v = v.substring(v.length - 10000); } resultRef.current.value = v; resultRef.current.scrollTop = resultRef.current.scrollHeight - resultRef.current.clientHeight; } }, (line: string) => { terminalRef.current?.write(line); }, bulkRounds ); } catch (ex) { console.error(ex); if (resultRef.current) { resultRef.current.value += `\nScript exited with error:\n${ex}\n`; } terminalRef.current?.write( "\r\n\x1B[1;31mScript exited with error:\x1B[0m\n\r" + String(ex).replace("\n", "\n\r") + "\r\n" ); } setIsScriptRunning(false); }, [runDisabled, bulkRounds, selectedAgent, savedScripts]); const stopScriptHandler = useCallback(() => { stopScript(); }, []); const saveScript = useCallback(async () => { if (!scriptName.trim()) { alert("Please enter a script name"); return; } if (!saveSecret.trim()) { alert("Please enter the save secret"); return; } if (!editorRef.current) { alert("No script content to save"); return; } setIsSaving(true); try { const response = await fetch("/api/save-script", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ name: scriptName.trim(), content: editorRef.current.getValue(), secret: saveSecret.trim(), }), }); const result = await response.json(); if (response.ok) { alert(result.message); setScriptName(""); // Reload saved scripts to include the new one loadSavedScripts(); } else { alert(result.error || "Failed to save script"); } } catch (error) { console.error("Save error:", error); alert("Network error: Failed to save script"); } setIsSaving(false); }, [scriptName, saveSecret, loadSavedScripts]); const copyScriptToEditor = useCallback(() => { if (!selectedAgent || selectedAgent === "Self") { alert("Please select a script to copy to the editor"); return; } // Get the script content const hardcodedAgent = AGENTS[selectedAgent as keyof typeof AGENTS]; const savedScript = savedScripts[selectedAgent]; const scriptContent = hardcodedAgent || savedScript; if (!scriptContent) { alert("No script content available for the selected agent"); return; } // Warn user about losing current content const confirmed = confirm( `This will replace your current script with "${selectedAgent}". Your current work will be lost. Continue?` ); if (confirmed && editorRef.current) { editorRef.current.setValue(scriptContent); } }, [selectedAgent, savedScripts]); return (
setScriptName( e.target.value ) } maxLength={32} />
setSaveSecret( e.target.value ) } />
} />
{}} onReady={() => setIsEditorReady(true)} fontSize={fontSize} />
Terminal
Output