Persist code

This commit is contained in:
2025-11-03 13:01:39 -08:00
parent 07aeda5e07
commit bfbd9d35bc
2 changed files with 231 additions and 66 deletions

View File

@@ -50,13 +50,24 @@ interface EditorProps {
fontSize?: number; fontSize?: number;
} }
const STORAGE_KEY = "minimax-editor-content";
export const Editor = forwardRef<any, EditorProps>(function Editor( export const Editor = forwardRef<any, EditorProps>(function Editor(
{ initialValue = "", onChange, onReady, fontSize = 14 }, { initialValue = "", onChange, onReady, fontSize = 14 },
ref ref
) { ) {
const textareaRef = useRef<HTMLTextAreaElement>(null); const textareaRef = useRef<HTMLTextAreaElement>(null);
const editorRef = useRef<any>(null); const editorRef = useRef<any>(null);
const [content, setContent] = useState(initialValue);
const getInitialContent = () => {
if (typeof window !== "undefined") {
const saved = localStorage.getItem(STORAGE_KEY);
return saved || initialValue;
}
return initialValue;
};
const [content, setContent] = useState(getInitialContent);
useImperativeHandle(ref, () => editorRef.current); useImperativeHandle(ref, () => editorRef.current);
@@ -100,6 +111,11 @@ export const Editor = forwardRef<any, EditorProps>(function Editor(
editor.on("change", (instance: any, changes: any) => { editor.on("change", (instance: any, changes: any) => {
const newContent = instance.getValue(); const newContent = instance.getValue();
setContent(newContent); setContent(newContent);
if (typeof window !== "undefined") {
localStorage.setItem(STORAGE_KEY, newContent);
}
onChange?.(instance, changes); onChange?.(instance, changes);
}); });
@@ -129,7 +145,7 @@ export const Editor = forwardRef<any, EditorProps>(function Editor(
<div className={styles.editorContainer}> <div className={styles.editorContainer}>
<textarea <textarea
ref={textareaRef} ref={textareaRef}
defaultValue={initialValue} defaultValue={content}
placeholder="Code goes here" placeholder="Code goes here"
/> />
</div> </div>

View File

@@ -85,11 +85,14 @@ export default function Playground() {
} }
); );
} catch (ex) { } catch (ex) {
console.error(ex);
if (resultRef.current) { if (resultRef.current) {
resultRef.current.value += `\nScript exited with error:\n${ex}\n`; resultRef.current.value += `\nScript exited with error:\n${ex}\n`;
} }
terminalRef.current?.write( terminalRef.current?.write(
"\r\n\x1B[1;31mScript exited with error:\x1B[0m\n\r" + String(ex).replace("\n", "\n\r") + "\r\n" "\r\n\x1B[1;31mScript exited with error:\x1B[0m\n\r" +
String(ex).replace("\n", "\n\r") +
"\r\n"
); );
} }
@@ -130,11 +133,14 @@ export default function Playground() {
bulkRounds bulkRounds
); );
} catch (ex) { } catch (ex) {
console.error(ex);
if (resultRef.current) { if (resultRef.current) {
resultRef.current.value += `\nScript exited with error:\n${ex}\n`; resultRef.current.value += `\nScript exited with error:\n${ex}\n`;
} }
terminalRef.current?.write( terminalRef.current?.write(
"\r\n\x1B[1;31mScript exited with error:\x1B[0m\n\r" + String(ex).replace("\n", "\n\r") + "\r\n" "\r\n\x1B[1;31mScript exited with error:\x1B[0m\n\r" +
String(ex).replace("\n", "\n\r") +
"\r\n"
); );
} }
@@ -257,100 +263,243 @@ export default function Playground() {
<h2>Game Rules</h2> <h2>Game Rules</h2>
<p> <p>
This game is played in two rounds, This game is played in two rounds, on an empty eleven-space
on an empty eleven-space board. board. The first round is played on {"Red's"} board, the
The first round is played on {"Red's"} board, the second is played on {"Blue's"}. second is played on {"Blue's"}.
</p> </p>
<p> <p>
On {"Red's"} board, {"Red's"} goal is to maximize the value of the expression. On {"Red's"} board, {"Red's"} goal is to maximize the value
{" Blue's"} goal is to minimize it. of the expression.
{" Blue's"} goal is to minimize it. Players take turns
Players take turns placing the fourteen symbols <code>0123456789+-×÷</code> placing the fourteen symbols <code>0123456789+-×÷</code>
on the board, with the maximizing player taking the first move. on the board, with the maximizing player taking the first
move.
</p> </p>
<p> <p>
A {"board's"} syntax must always be valid, and A {"board's"} syntax must always be valid, and the following
the following rules are enforced: rules are enforced:
</p> </p>
<ol> <ol>
<li>Each symbol may only be used once</li> <li>Each symbol may only be used once</li>
<li>The binary operators <code>+-×÷</code> may not be next to one another, and may not be at the end slots.</li> <li>
<li>The unary operator <code>-</code> (negative) must have a number as an argument. Therefore, it cannot be left of an operator (like <code>-×</code>), and it may not be in the rightmost slot.</li> The binary operators <code>+-×÷</code> may not be next
<li>Unary <code>+</code> may not be used.</li> to one another, and may not be at the end slots.
<li> <code>0</code> may not follow <code>÷</code>. </li>
This prevents most cases of zero-division, but {"isn't perfect"}. <li>
<code>÷-0</code> will result in an invalid board (causing a draw), The unary operator <code>-</code> (negative) must have a
and <code>÷0_+</code> is forbidden despite being valid syntax once the empty slot is filled. number as an argument. Therefore, it cannot be left of
This is done to simplyify game logic, and might be improved later. an operator (like <code>-×</code>), and it may not be in
the rightmost slot.
</li>
<li>
Unary <code>+</code> may not be used.
</li>
<li>
{" "}
<code>0</code> may not follow <code>÷</code>. This
prevents most cases of zero-division, but{" "}
{"isn't perfect"}.<code>÷-0</code> will result in an
invalid board (causing a draw), and <code>÷0_+</code> is
forbidden despite being valid syntax once the empty slot
is filled. This is done to simplyify game logic, and
might be improved later.
</li> </li>
<li>Division by zero results in a draw.</li> <li>Division by zero results in a draw.</li>
<li>An incomplete board with no valid moves results in a draw.</li> <li>
An incomplete board with no valid moves results in a
draw.
</li>
</ol> </ol>
<h2>How to Play</h2> <h2>How to Play</h2>
<ol> <ol>
<li>Click <strong>Run</strong> to start a single game. Play against your agent in the terminal. Use your arrow keys (up, down, left, right) to select a symbol. Use enter or space to make a move.</li> <li>
<li>Click <strong>Bulk Run</strong> to collect statistics from a many games.</li> Click <strong>Run</strong> to start a single game. Play
against your agent in the terminal. Use your arrow keys
(up, down, left, right) to select a symbol. Use enter or
space to make a move.
</li>
<li>
Click <strong>Bulk Run</strong> to collect statistics
from a many games.
</li>
</ol> </ol>
<h2>Overview</h2> <h2>Overview</h2>
<ul> <ul>
<li><code>step_min()</code> is called once per turn with the {"board's"} current state. This function must return an <code>Action</code> that aims to minimize the total value of the board.</li> <li>
<li><code>step_max()</code> is just like <code>step_min</code>, but should aim to maximize the value of the board. </li> <code>step_min()</code> is called once per turn with the{" "}
<li>Agent code may not be edited between games. Start a new game to use new code.</li> {"board's"} current state. This function must return an{" "}
<li>If your agent takes more than 5 seconds to compute a move, the script will exit with an error.</li> <code>Action</code> that aims to minimize the total
value of the board.
</li>
<li>
<code>step_max()</code> is just like{" "}
<code>step_min</code>, but should aim to maximize the
value of the board.{" "}
</li>
<li>
Agent code may not be edited between games. Start a new
game to use new code.
</li>
<li>
If your agent takes more than 5 seconds to compute a
move, the script will exit with an error.
</li>
</ul> </ul>
<h2>Rhai basics</h2> <h2>Rhai basics</h2>
<p> <p>
Agents are written in <a href="https://rhai.rs">Rhai</a>, a wonderful embedded scripting language powered by Rust. Agents are written in <a href="https://rhai.rs">Rhai</a>, a
Basic language features are outlined below. wonderful embedded scripting language powered by Rust. Basic
language features are outlined below.
</p> </p>
<ul> <ul>
<li>All statements must be followed by a <code>;</code></li> <li>
<li>Use <code>return</code> to return a value from a function.</li> All statements must be followed by a <code>;</code>
<li><code>print(anything)</code> - Prints to the output panel. Prefer this over <code>debug</code>.</li> </li>
<li><code>debug(anything)</code> - Prints to the output panel. Includes extra debug info.</li> <li>
<li><code>()</code> is the {"\"none\""} type, returned by some methods above.</li> Use <code>return</code> to return a value from a
<li><code>for i in 0..5 {"{}"}</code> will iterate five times, with <code>i = 0, 1, 2, 3, 4</code></li> function.
<li><code>for i in 0..=5 {"{}"}</code> will iterate six times, with <code>i = 0, 1, 2, 3, 4, 5</code></li> </li>
<li><code>let a = [];</code> initializes an empty array.</li> <li>
<li><code>a.push(value)</code> adds a value to the end of an array</li> <code>print(anything)</code> - Prints to the output
<li><code>a.pop()</code> removes a value from the end of an array and returns it</li> panel. Prefer this over <code>debug</code>.
<li><code>a[0]</code> returns the first item of an array</li> </li>
<li><code>a[1]</code> returns the second item of an array</li> <li>
<li>Refer to <a href="https://rhai.rs/book/language/values-and-types.html">the Rhai book</a> for more details.</li> <code>debug(anything)</code> - Prints to the output
panel. Includes extra debug info.
</li>
<li>
<code>()</code> is the {'"none"'} type, returned by some
methods above.
</li>
<li>
<code>for i in 0..5 {"{}"}</code> will iterate five
times, with <code>i = 0, 1, 2, 3, 4</code>
</li>
<li>
<code>for i in 0..=5 {"{}"}</code> will iterate six
times, with <code>i = 0, 1, 2, 3, 4, 5</code>
</li>
<li>
<code>let a = [];</code> initializes an empty array.
</li>
<li>
<code>a.push(value)</code> adds a value to the end of an
array
</li>
<li>
<code>a.pop()</code> removes a value from the end of an
array and returns it
</li>
<li>
<code>a[0]</code> returns the first item of an array
</li>
<li>
<code>a[1]</code> returns the second item of an array
</li>
<li>
Refer to{" "}
<a href="https://rhai.rs/book/language/values-and-types.html">
the Rhai book
</a>{" "}
for more details.
</li>
</ul> </ul>
<h2>Notable Functions</h2> <h2>Notable Functions</h2>
<ul> <ul>
<li><code>Action(symbol, position)</code> - Creates a new action that places <code>symbol</code> at <code>position</code>. Valid symbols are <code>01234567890+-/*</code>. Both <code>0</code> and <code>{"\"0\""}</code> are valid symbols.</li> <li>
<li><code>board.can_play(action)</code> - Checks if an action is valid. Returns a boolean.</li> <code>Action(symbol, position)</code> - Creates a new
<li><code>board.size()</code> - Return the total number of spots on this board.</li> action that places <code>symbol</code> at{" "}
<li><code>board.free_spots()</code> - Count the number of free spots on the board.</li> <code>position</code>. Valid symbols are{" "}
<li><code>board.play(action)</code> - Apply the given action on this board. This mutates the <code>board</code>, but does NOT make the move in the game. The only way to commit to an action is to return it from <code>step_min</code> or <code>step_max</code>. <code>01234567890+-/*</code>. Both <code>0</code> and{" "}
This method lets you compute potential values of a board when used with <code>board.evaluate()</code>. <code>{'"0"'}</code> are valid symbols.
</li>
<li>
<code>board.can_play(action)</code> - Checks if an
action is valid. Returns a boolean.
</li>
<li>
<code>board.size()</code> - Return the total number of
spots on this board.
</li>
<li>
<code>board.free_spots()</code> - Count the number of
free spots on the board.
</li>
<li>
<code>board.play(action)</code> - Apply the given action
on this board. This mutates the <code>board</code>, but
does NOT make the move in the game. The only way to
commit to an action is to return it from{" "}
<code>step_min</code> or <code>step_max</code>. This
method lets you compute potential values of a board when
used with <code>board.evaluate()</code>.
</li>
<li>
<code>board.ith_free_slot(idx)</code> - Returns the
index of the <code>n</code>th free slot on this board.
Returns <code>-1</code> if no such slot exists.
</li>
<li>
<code>board.contains(symbol)</code> - Checks if this
board contains the given symbol. Returns a boolean.
</li>
<li>
<code>board.evaluate()</code> - Return the value of a
board if it can be computed. Returns <code>()</code>{" "}
otherwise.
</li>
<li>
<code>board.free_spots_idx(action)</code> - Checks if an
action is valid. Returns a boolean.
</li>
<li>
<code>for i in board {"{ ... }"}</code> - Iterate over
all slots on this board. Items are returned as strings,
empty slots are the empty string (<code>{'""'}</code>)
</li>
<li>
<code>is_op(symbol)</code> - Returns <code>true</code>{" "}
if <code>symbol</code> is one of <code>+-*/</code>
</li> </li>
<li><code>board.ith_free_slot(idx)</code> - Returns the index of the <code>n</code>th free slot on this board. Returns <code>-1</code> if no such slot exists.</li>
<li><code>board.contains(symbol)</code> - Checks if this board contains the given symbol. Returns a boolean.</li>
<li><code>board.evaluate()</code> - Return the value of a board if it can be computed. Returns <code>()</code> otherwise.</li>
<li><code>board.free_spots_idx(action)</code> - Checks if an action is valid. Returns a boolean.</li>
<li><code>for i in board {"{ ... }"}</code> - Iterate over all slots on this board. Items are returned as strings, empty slots are the empty string (<code>{"\"\""}</code>)</li>
<li><code>is_op(symbol)</code> - Returns <code>true</code> if <code>symbol</code> is one of <code>+-*/</code></li>
<li><code>rand_symb()</code> - Returns a random symbol (number or operation)</li> <li>
<li><code>rand_op()</code> - Returns a random operator symbol (one of <code>+-*/</code>)</li> <code>rand_symb()</code> - Returns a random symbol
<li><code>rand_action()</code> - Returns a random <code>Action</code></li> (number or operation)
<li><code>rand_int(min, max)</code> - Returns a random integer between min and max, including both endpoints.</li> </li>
<li><code>rand_bool(probability)</code> - Return <code>true</code> with the given probability. Otherwise return <code>false</code>.</li> <li>
<li><code>rand_shuffle(array)</code> - Shuffle the given array</li> <code>rand_op()</code> - Returns a random operator
<li><code>for p in permutations(array, 5) {"{}"}</code> - Iterate over all permutations of 5 elements of the given array.</li> symbol (one of <code>+-*/</code>)
</li>
<li>
<code>rand_action()</code> - Returns a random{" "}
<code>Action</code>
</li>
<li>
<code>rand_int(min, max)</code> - Returns a random
integer between min and max, including both endpoints.
</li>
<li>
<code>rand_bool(probability)</code> - Return{" "}
<code>true</code> with the given probability. Otherwise
return <code>false</code>.
</li>
<li>
<code>rand_shuffle(array)</code> - Shuffle the given
array
</li>
<li>
<code>for p in permutations(array, 5) {"{}"}</code> -
Iterate over all permutations of 5 elements of the given
array.
</li>
</ul> </ul>
</SidePanel> </SidePanel>
</div> </div>
); );