diff --git a/build/ludus.mjs b/build/ludus.mjs index 4c70165..2296127 100644 --- a/build/ludus.mjs +++ b/build/ludus.mjs @@ -6,25 +6,22 @@ let result = null export function run (source) { const output = mod.ludus(source).value - // console.log(output) result = JSON.parse(output) return result } export function stdout () { + if (!result) return "" return result.io.console.data } export function turtle_commands () { + if (!result) return [] return result.io.turtle.data } -export function p5_calls () { - return p5_call_list -} - export function svg () { - + // TODO } const turtle_init = { @@ -55,13 +52,14 @@ const colors = { aqua: [0, 255, 25, 255], } -let turtle_states = [turtle_init] -let background_color = "black" - -function last_state () { - return turtle_states[turtle_states.length - 1] +function resolve_color (color) { + if (typeof color === 'string') return colors[color] + if (typeof color === 'number') return [color, color, color, 255] + if (Array.isArray(color)) return color } +let background_color = "black" + function add (v1, v2) { const [x1, y1] = v1 const [x2, y2] = v2 @@ -76,83 +74,84 @@ function mult (vector, scalar) { function unit_of (heading) { const turns = -heading + 0.25 const radians = turn_to_rad(turns) - [Math.cos (radians), Math.sin(radians)] + return [Math.cos(radians), Math.sin(radians)] } -function command_to_state (previous, command) { - const verb = command[0] +function command_to_state (prev_state, curr_command) { + const verb = curr_command[0] switch (verb) { case "goto": { - const [_, x, y] = command - return {...previous, position: [x, y]} + const [_, x, y] = curr_command + return {...prev_state, position: [x, y]} } case "home": { - return {...previous, position: [0, 0], heading: 0} + return {...prev_state, position: [0, 0], heading: 0} } case "right": { - const [_, angle] = command - const {heading} = previous - return {...previous, heading: heading + angle} + const [_, angle] = curr_command + const {heading} = prev_state + return {...prev_state, heading: heading + angle} } case "left": { - const [_, angle] = command - const {heading} = previous - return {...previous, heading: heading - angle} + const [_, angle] = curr_command + const {heading} = prev_state + return {...prev_state, heading: heading - angle} } case "forward": { - const [_, steps] = command - const {heading, position} = previous + const [_, steps] = curr_command + const {heading, position} = prev_state const unit = unit_of(heading) const move = mult(unit, steps) - return {...previous, position: add(position, move)} + return {...prev_state, position: add(position, move)} } case "back": { - const [_, steps] = command - const {heading, position} = previous + const [_, steps] = curr_command + const {heading, position} = prev_state const unit = unit_of(heading) const move = mult(unit, -steps) - return {...previous, position: add(position, move)} + return {...prev_state, position: add(position, move)} } case "penup": { - return {...previous, pendown: false} + return {...prev_state, pendown: false} } case "pendown": { - return {...previous, pendown: true} + return {...prev_state, pendown: true} } case "pendwith": { - const [_, width] = command - return {...previous, penwidth: width} + const [_, width] = curr_command + return {...prev_state, penwidth: width} } case "pencolor": { - if (command.length = 2) { - const [_, color] = command - return {...previous, pencolor: color} + console.log(curr_command) + if (curr_command.length = 2) { + const [_, color] = curr_command + return {...prev_state, pencolor: color} } else { - const [_, r, g, b, a] = command - return {...previous, pencolor: [r, g, b, a]} + const [_, r, g, b, a] = curr_command + return {...prev_state, pencolor: [r, g, b, a]} } } case "setheading": { - const [_, heading] = command - return {...previous, heading: heading} + const [_, heading] = curr_command + return {...prev_state, heading: heading} } case "loadstate": { - const [_, x, y, heading, visible, pendown, penwidth] = command + const [_, x, y, heading, visible, pendown, penwidth] = curr_command // there are 7 fixed-arity arguments // color will either be the last one or four - let pencolor = command.slice(7) + let pencolor = curr_command.slice(7) // if there's one, it's a string, unpack it if (pencolor.length = 1) pencolor = pencolor[0] return {position: [x, y], heading, visible, pendown, penwidth, pencolor} } case "show": { - return {...position, visible: true} + return {...prev_state, visible: true} } case "hide": { - return {...position, visible: false} + return {...prev_state, visible: false} } case "background": { - let color = command.slice(1) + let color = curr_command.slice(1) if (color.lengh = 1) color = color[0] background_color = color return @@ -166,8 +165,26 @@ function are_eq (v1, v2) { return (x1 === x2) && (y1 === y2) } -function state_to_call (prev, curr) { - +function states_to_call (prev, curr) { + const calls = [] + // whose state should we use? + // pen states will only differ on more than one property + // if we use `loadstate` + // my sense is `prev`, but that may change + if (prev.pendown && !are_eq(prev.position, curr.position)) { + calls.push(["line", prev.position[0], prev.position[1], curr.position[0], curr.position[1]]) + } + if (curr.pencolor !== prev.pencolor) { + if (Array.isArray(curr.pencolor)) { + calls.push(["stroke", ...curr.pencolor]) + } else { + calls.push(["stroke", curr.pencolor]) + } + } + if (curr.penwidth !== prev.penwidth) { + calls.push(["strokeWeight", curr.penwidth]) + } + return calls } const turtle_radius = 20 @@ -176,47 +193,65 @@ const turtle_angle = 0.385 const turtle_color = [255, 255, 255, 150] -let p5_call_list = [] - -function add_p5_call (call) { - p5_call_list.push(call) -} +const p5_call_root = [ + ["background", ...resolve_color(background_color)], + ["stroke", ...resolve_color(turtle_init.pencolor)] +] function rotate (vector, heading) { const radians = turn_to_rad(heading) const [x, y] = vector return [ - (x * cos (radians)) - (y * sin (radians)), - (x * sin (radians)) + (y * cos (radians)) + (x * Math.cos (radians)) - (y * Math.sin (radians)), + (x * Math.sin (radians)) + (y * Math.cos (radians)) ] } function turn_to_rad (heading) { - heading * 2 * Math.PI + return heading * 2 * Math.PI } -function render_turtle () { - const state = last_state () +function render_turtle (state, calls) { if (!state.visible) return + calls.push(["push"]) const [r, g, b, a] = turtle_color - add_call("fill", r, g, b, a) - const {heading, pencolor: [pen_r, pen_g, pen_b, pen_a], position: [x, y], pendown} = state + calls.push(["fill", r, g, b, a]) + const {heading, pencolor, position: [x, y], pendown} = state const origin = [0, turtle_radius] const [x1, y1] = origin const [x2, y2] = rotate(origin, turtle_angle) const [x3, y3] = rotate(origin, -turtle_angle) - add_p5_call(["push"]) - add_p5_call(["translate", x, y]) - add_p5_call(["rotate", turn_to_rad(heading)]) - add_p5_call(["noStroke"]) - add_p5_call(["beginShape"]) - add_p5_call(["vertex", x1, y1]) - add_p5_call(["vertex", x2, y2]) - add_p5_call(["vertex", x3, y3]) - add_p5_call(["endShape"]) - add_p5_call(["stroke", pen_r, pen_g, pen_b, pen_a]) - if (pendown) add_p5_call(["line", 0, 0, x1, y1]) - add_p5_call(["pop"]) + calls.push(["translate", x, y]) + calls.push(["rotate", turn_to_rad(heading)]) + calls.push(["noStroke"]) + calls.push(["beginShape"]) + calls.push(["vertex", x1, y1]) + calls.push(["vertex", x2, y2]) + calls.push(["vertex", x3, y3]) + calls.push(["endShape"]) + calls.push(["stroke", ...resolve_color(pencolor)]) + if (pendown) calls.push(["line", 0, 0, x1, y1]) + calls.push(["pop"]) + return calls } - +export function p5_calls (commands) { + const states = [turtle_init] + commands.reduce((prev_state, command) => { + const new_state = command_to_state(prev_state, command) + states.push(new_state) + return new_state + }, turtle_init) + const p5_calls = [...p5_call_root] + 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)] + render_turtle(states[states.length - 1], p5_calls) + return p5_calls +}