diff --git a/may_2025_thoughts.md b/may_2025_thoughts.md index 0c3eb36..be540bb 100644 --- a/may_2025_thoughts.md +++ b/may_2025_thoughts.md @@ -1233,9 +1233,21 @@ Anyway, here's the tl;dr for basic process linking: * `monitor/1` means we can monitor exit messages, these will be `(:exit, pid, (:ok, value))` in the case of successful return, or `(:exit, pid, (:err, msg))` in the case of a panic. These are sent as normal messages to the monitoring process. Erlang/Elixir has a lot more, but that's basically it for Ludus for now. +I don't think we need more than the binary: `:ok`/`:err`, or return/panic as how processes exit. `monitor/1` makes it easy to write an `await`. +We would need a way of killing processes remotely if indeed we wanted to write robust distributed applications. +But I'm less interested in that than in the cognitive modelling here. As I'm thinking about this, I actually want to roll back `spawn` as a special form. I think it's actually a good cognitive shift to understand that functions are reified, deferred computations. And that processes take this a long way. We can revisit the idea of a special spawn form later. + +=> done + +And now, we build capacity for building projects. + +* [ ] allow for multiple turtles + - [ ] rework p5 function in `ludus.js` to properly parse commands targeted at different turtles + - [ ] write ludus code to spawn & manipulate turtle actors +* [ ] do some sample multiturtle sketches diff --git a/pkg/ludus.js b/pkg/ludus.js index 0e0b05c..f7fa38f 100644 --- a/pkg/ludus.js +++ b/pkg/ludus.js @@ -1,4 +1,4 @@ -if (window) window.ludus = {run, kill, flush_stdout, stdout, p5, svg, flush_commands, commands, result, flush_result, input, is_running, key_down, key_up, is_starting_up} +if (window) window.ludus = {run, kill, flush_stdout, stdout, p5, new_p5, svg, flush_commands, commands, result, flush_result, input, is_running, key_down, key_up, is_starting_up} const worker_url = new URL("worker.js", import.meta.url) const worker = new Worker(worker_url, {type: "module"}) @@ -51,7 +51,7 @@ async function handle_messages (e) { case "Commands": { console.log("Main: ludus commands => ", msg.data) for (const command of msg.data) { - // attempt to solve out-of-order command bug + // commands will arrive asynchronously; ensure correct ordering ludus_commands[command[1]] = command } break @@ -236,69 +236,71 @@ function unit_of (heading) { return [Math.cos(radians), Math.sin(radians)] } -function command_to_state (prev_state, command) { - const [_target, _id, curr_command] = command +function command_to_state (state, command) { + const [target, _id, curr_command] = command + const state_stack = state[target] ?? [turtle_init] + const prev_state = state_stack[state_stack.length - 1] const [verb] = curr_command switch (verb) { case "goto": { const [_, x, y] = curr_command - return {...prev_state, position: [x, y]} + return [target, {...prev_state, position: [x, y]}] } case "home": { - return {...prev_state, position: [0, 0], heading: 0} + return [target, {...prev_state, position: [0, 0], heading: 0}] } case "right": { const [_, angle] = curr_command const {heading} = prev_state - return {...prev_state, heading: heading + angle} + return [target, {...prev_state, heading: heading + angle}] } case "left": { const [_, angle] = curr_command const {heading} = prev_state - return {...prev_state, heading: heading - angle} + return [target, {...prev_state, heading: heading - angle}] } case "forward": { const [_, steps] = curr_command const {heading, position} = prev_state const unit = unit_of(heading) const move = mult(unit, steps) - return {...prev_state, position: add(position, move)} + return [target, {...prev_state, position: add(position, move)}] } case "back": { const [_, steps] = curr_command const {heading, position} = prev_state const unit = unit_of(heading) const move = mult(unit, -steps) - return {...prev_state, position: add(position, move)} + return [target, {...prev_state, position: add(position, move)}] } case "penup": { - return {...prev_state, pendown: false} + return [target, {...prev_state, pendown: false}] } case "pendown": { - return {...prev_state, pendown: true} + return [target, {...prev_state, pendown: true}] } case "penwidth": { const [_, width] = curr_command - return {...prev_state, penwidth: width} + return [target, {...prev_state, penwidth: width}] } case "pencolor": { const [_, color] = curr_command - return {...prev_state, pencolor: color} + return [target, {...prev_state, pencolor: color}] } case "setheading": { const [_, heading] = curr_command - return {...prev_state, heading: heading} + return [target, {...prev_state, heading: heading}] } case "loadstate": { // console.log("LOADSTATE: ", curr_command) const [_, [x, y], heading, visible, pendown, penwidth, pencolor] = curr_command - return {position: [x, y], heading, visible, pendown, penwidth, pencolor} + return [target, {position: [x, y], heading, visible, pendown, penwidth, pencolor}] } case "show": { - return {...prev_state, visible: true} + return [target, {...prev_state, visible: true}] } case "hide": { - return {...prev_state, visible: false} + return [target, {...prev_state, visible: false}] } case "background": { background_color = curr_command[1] @@ -532,4 +534,32 @@ export function p5 (commands) { return p5_calls } +export function new_p5 (commands) { + const all_states = {} + commands.reduce((prev_state, command) => { + const [turtle_id, new_state] = command_to_state(prev_state, command) + if (!all_states[turtle_id]) all_states[turtle_id] = [turtle_init] + all_states[turtle_id].push(new_state) + return new_state + }, all_states) + const [r, g, b, _] = resolve_color(background_color) + if ((r + g + b)/3 > 128) turtle_color = [0, 0, 0, 150] + const p5_calls = [...p5_call_root()] + for (const states of Object.values(all_states)) { + console.log(states) + for (let i = 1; i < states.length; ++i) { + const prev = states[i - 1] + const curr = states[i] + const calls = states_to_call(prev, curr) + for (const call of calls) { + p5_calls.push(call) + } + p5_calls[0] = ["background", ...resolve_color(background_color)] + p5_render_turtle(states[states.length - 1], p5_calls) + } + } + p5_calls.push(["pop"]) + return p5_calls +} +