Compare commits
3 Commits
2c9f6f8279
...
d8f152998e
Author | SHA1 | Date | |
---|---|---|---|
|
d8f152998e | ||
|
af125ffbbb | ||
|
657ff3dedb |
179
build/ludus.mjs
179
build/ludus.mjs
|
@ -6,25 +6,22 @@ let result = null
|
||||||
|
|
||||||
export function run (source) {
|
export function run (source) {
|
||||||
const output = mod.ludus(source).value
|
const output = mod.ludus(source).value
|
||||||
// console.log(output)
|
|
||||||
result = JSON.parse(output)
|
result = JSON.parse(output)
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
export function stdout () {
|
export function stdout () {
|
||||||
|
if (!result) return ""
|
||||||
return result.io.console.data
|
return result.io.console.data
|
||||||
}
|
}
|
||||||
|
|
||||||
export function turtle_commands () {
|
export function turtle_commands () {
|
||||||
|
if (!result) return []
|
||||||
return result.io.turtle.data
|
return result.io.turtle.data
|
||||||
}
|
}
|
||||||
|
|
||||||
export function p5_calls () {
|
|
||||||
return p5_call_list
|
|
||||||
}
|
|
||||||
|
|
||||||
export function svg () {
|
export function svg () {
|
||||||
|
// TODO
|
||||||
}
|
}
|
||||||
|
|
||||||
const turtle_init = {
|
const turtle_init = {
|
||||||
|
@ -55,13 +52,14 @@ const colors = {
|
||||||
aqua: [0, 255, 25, 255],
|
aqua: [0, 255, 25, 255],
|
||||||
}
|
}
|
||||||
|
|
||||||
let turtle_states = [turtle_init]
|
function resolve_color (color) {
|
||||||
let background_color = "black"
|
if (typeof color === 'string') return colors[color]
|
||||||
|
if (typeof color === 'number') return [color, color, color, 255]
|
||||||
function last_state () {
|
if (Array.isArray(color)) return color
|
||||||
return turtle_states[turtle_states.length - 1]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let background_color = "black"
|
||||||
|
|
||||||
function add (v1, v2) {
|
function add (v1, v2) {
|
||||||
const [x1, y1] = v1
|
const [x1, y1] = v1
|
||||||
const [x2, y2] = v2
|
const [x2, y2] = v2
|
||||||
|
@ -76,83 +74,84 @@ function mult (vector, scalar) {
|
||||||
function unit_of (heading) {
|
function unit_of (heading) {
|
||||||
const turns = -heading + 0.25
|
const turns = -heading + 0.25
|
||||||
const radians = turn_to_rad(turns)
|
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) {
|
function command_to_state (prev_state, curr_command) {
|
||||||
const verb = command[0]
|
const verb = curr_command[0]
|
||||||
switch (verb) {
|
switch (verb) {
|
||||||
case "goto": {
|
case "goto": {
|
||||||
const [_, x, y] = command
|
const [_, x, y] = curr_command
|
||||||
return {...previous, position: [x, y]}
|
return {...prev_state, position: [x, y]}
|
||||||
}
|
}
|
||||||
case "home": {
|
case "home": {
|
||||||
return {...previous, position: [0, 0], heading: 0}
|
return {...prev_state, position: [0, 0], heading: 0}
|
||||||
}
|
}
|
||||||
case "right": {
|
case "right": {
|
||||||
const [_, angle] = command
|
const [_, angle] = curr_command
|
||||||
const {heading} = previous
|
const {heading} = prev_state
|
||||||
return {...previous, heading: heading + angle}
|
return {...prev_state, heading: heading + angle}
|
||||||
}
|
}
|
||||||
case "left": {
|
case "left": {
|
||||||
const [_, angle] = command
|
const [_, angle] = curr_command
|
||||||
const {heading} = previous
|
const {heading} = prev_state
|
||||||
return {...previous, heading: heading - angle}
|
return {...prev_state, heading: heading - angle}
|
||||||
}
|
}
|
||||||
case "forward": {
|
case "forward": {
|
||||||
const [_, steps] = command
|
const [_, steps] = curr_command
|
||||||
const {heading, position} = previous
|
const {heading, position} = prev_state
|
||||||
const unit = unit_of(heading)
|
const unit = unit_of(heading)
|
||||||
const move = mult(unit, steps)
|
const move = mult(unit, steps)
|
||||||
return {...previous, position: add(position, move)}
|
return {...prev_state, position: add(position, move)}
|
||||||
}
|
}
|
||||||
case "back": {
|
case "back": {
|
||||||
const [_, steps] = command
|
const [_, steps] = curr_command
|
||||||
const {heading, position} = previous
|
const {heading, position} = prev_state
|
||||||
const unit = unit_of(heading)
|
const unit = unit_of(heading)
|
||||||
const move = mult(unit, -steps)
|
const move = mult(unit, -steps)
|
||||||
return {...previous, position: add(position, move)}
|
return {...prev_state, position: add(position, move)}
|
||||||
}
|
}
|
||||||
case "penup": {
|
case "penup": {
|
||||||
return {...previous, pendown: false}
|
return {...prev_state, pendown: false}
|
||||||
}
|
}
|
||||||
case "pendown": {
|
case "pendown": {
|
||||||
return {...previous, pendown: true}
|
return {...prev_state, pendown: true}
|
||||||
}
|
}
|
||||||
case "pendwith": {
|
case "pendwith": {
|
||||||
const [_, width] = command
|
const [_, width] = curr_command
|
||||||
return {...previous, penwidth: width}
|
return {...prev_state, penwidth: width}
|
||||||
}
|
}
|
||||||
case "pencolor": {
|
case "pencolor": {
|
||||||
if (command.length = 2) {
|
console.log(curr_command)
|
||||||
const [_, color] = command
|
if (curr_command.length = 2) {
|
||||||
return {...previous, pencolor: color}
|
const [_, color] = curr_command
|
||||||
|
return {...prev_state, pencolor: color}
|
||||||
} else {
|
} else {
|
||||||
const [_, r, g, b, a] = command
|
const [_, r, g, b, a] = curr_command
|
||||||
return {...previous, pencolor: [r, g, b, a]}
|
return {...prev_state, pencolor: [r, g, b, a]}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case "setheading": {
|
case "setheading": {
|
||||||
const [_, heading] = command
|
const [_, heading] = curr_command
|
||||||
return {...previous, heading: heading}
|
return {...prev_state, heading: heading}
|
||||||
}
|
}
|
||||||
case "loadstate": {
|
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
|
// there are 7 fixed-arity arguments
|
||||||
// color will either be the last one or four
|
// 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 there's one, it's a string, unpack it
|
||||||
if (pencolor.length = 1) pencolor = pencolor[0]
|
if (pencolor.length = 1) pencolor = pencolor[0]
|
||||||
return {position: [x, y], heading, visible, pendown, penwidth, pencolor}
|
return {position: [x, y], heading, visible, pendown, penwidth, pencolor}
|
||||||
}
|
}
|
||||||
case "show": {
|
case "show": {
|
||||||
return {...position, visible: true}
|
return {...prev_state, visible: true}
|
||||||
}
|
}
|
||||||
case "hide": {
|
case "hide": {
|
||||||
return {...position, visible: false}
|
return {...prev_state, visible: false}
|
||||||
}
|
}
|
||||||
case "background": {
|
case "background": {
|
||||||
let color = command.slice(1)
|
let color = curr_command.slice(1)
|
||||||
if (color.lengh = 1) color = color[0]
|
if (color.lengh = 1) color = color[0]
|
||||||
background_color = color
|
background_color = color
|
||||||
return
|
return
|
||||||
|
@ -166,8 +165,26 @@ function are_eq (v1, v2) {
|
||||||
return (x1 === x2) && (y1 === y2)
|
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
|
const turtle_radius = 20
|
||||||
|
@ -176,47 +193,65 @@ const turtle_angle = 0.385
|
||||||
|
|
||||||
const turtle_color = [255, 255, 255, 150]
|
const turtle_color = [255, 255, 255, 150]
|
||||||
|
|
||||||
let p5_call_list = []
|
const p5_call_root = [
|
||||||
|
["background", ...resolve_color(background_color)],
|
||||||
function add_p5_call (call) {
|
["stroke", ...resolve_color(turtle_init.pencolor)]
|
||||||
p5_call_list.push(call)
|
]
|
||||||
}
|
|
||||||
|
|
||||||
function rotate (vector, heading) {
|
function rotate (vector, heading) {
|
||||||
const radians = turn_to_rad(heading)
|
const radians = turn_to_rad(heading)
|
||||||
const [x, y] = vector
|
const [x, y] = vector
|
||||||
return [
|
return [
|
||||||
(x * cos (radians)) - (y * sin (radians)),
|
(x * Math.cos (radians)) - (y * Math.sin (radians)),
|
||||||
(x * sin (radians)) + (y * cos (radians))
|
(x * Math.sin (radians)) + (y * Math.cos (radians))
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
function turn_to_rad (heading) {
|
function turn_to_rad (heading) {
|
||||||
heading * 2 * Math.PI
|
return heading * 2 * Math.PI
|
||||||
}
|
}
|
||||||
|
|
||||||
function render_turtle () {
|
function render_turtle (state, calls) {
|
||||||
const state = last_state ()
|
|
||||||
if (!state.visible) return
|
if (!state.visible) return
|
||||||
|
calls.push(["push"])
|
||||||
const [r, g, b, a] = turtle_color
|
const [r, g, b, a] = turtle_color
|
||||||
add_call("fill", r, g, b, a)
|
calls.push(["fill", r, g, b, a])
|
||||||
const {heading, pencolor: [pen_r, pen_g, pen_b, pen_a], position: [x, y], pendown} = state
|
const {heading, pencolor, position: [x, y], pendown} = state
|
||||||
const origin = [0, turtle_radius]
|
const origin = [0, turtle_radius]
|
||||||
const [x1, y1] = origin
|
const [x1, y1] = origin
|
||||||
const [x2, y2] = rotate(origin, turtle_angle)
|
const [x2, y2] = rotate(origin, turtle_angle)
|
||||||
const [x3, y3] = rotate(origin, -turtle_angle)
|
const [x3, y3] = rotate(origin, -turtle_angle)
|
||||||
add_p5_call(["push"])
|
calls.push(["translate", x, y])
|
||||||
add_p5_call(["translate", x, y])
|
calls.push(["rotate", turn_to_rad(heading)])
|
||||||
add_p5_call(["rotate", turn_to_rad(heading)])
|
calls.push(["noStroke"])
|
||||||
add_p5_call(["noStroke"])
|
calls.push(["beginShape"])
|
||||||
add_p5_call(["beginShape"])
|
calls.push(["vertex", x1, y1])
|
||||||
add_p5_call(["vertex", x1, y1])
|
calls.push(["vertex", x2, y2])
|
||||||
add_p5_call(["vertex", x2, y2])
|
calls.push(["vertex", x3, y3])
|
||||||
add_p5_call(["vertex", x3, y3])
|
calls.push(["endShape"])
|
||||||
add_p5_call(["endShape"])
|
calls.push(["stroke", ...resolve_color(pencolor)])
|
||||||
add_p5_call(["stroke", pen_r, pen_g, pen_b, pen_a])
|
if (pendown) calls.push(["line", 0, 0, x1, y1])
|
||||||
if (pendown) add_p5_call(["line", 0, 0, x1, y1])
|
calls.push(["pop"])
|
||||||
add_p5_call(["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
|
||||||
|
}
|
||||||
|
|
|
@ -3,7 +3,5 @@
|
||||||
& the goal is to output any global state held in Ludus
|
& the goal is to output any global state held in Ludus
|
||||||
& this does not have base loaded into it, only prelude: must be pure Ludus
|
& this does not have base loaded into it, only prelude: must be pure Ludus
|
||||||
|
|
||||||
print! ("running postlude")
|
|
||||||
|
|
||||||
store! (turtle_state, turtle_init)
|
store! (turtle_state, turtle_init)
|
||||||
store! (turtle_commands, [])
|
store! (turtle_commands, [])
|
||||||
|
|
79
prelude.ld
79
prelude.ld
|
@ -1106,90 +1106,14 @@ let turtle_init = #{
|
||||||
box turtle_commands = []
|
box turtle_commands = []
|
||||||
box turtle_state = turtle_init
|
box turtle_state = turtle_init
|
||||||
|
|
||||||
& fn reset_turtle! {
|
|
||||||
& "Resets the turtle to its original state."
|
|
||||||
& () -> store! (turtle_states, [turtle_init])
|
|
||||||
& }
|
|
||||||
|
|
||||||
fn add_command! (command) -> {
|
fn add_command! (command) -> {
|
||||||
update! (turtle_commands, append! (_, command))
|
update! (turtle_commands, append! (_, command))
|
||||||
let prev = unbox (turtle_state)
|
let prev = unbox (turtle_state)
|
||||||
let curr = apply_command (prev, command)
|
let curr = apply_command (prev, command)
|
||||||
store! (turtle_state, curr)
|
store! (turtle_state, curr)
|
||||||
& let call = state/call ()
|
|
||||||
& if call then { add_call! (call); :ok } else :ok
|
|
||||||
:ok
|
:ok
|
||||||
}
|
}
|
||||||
|
|
||||||
& fn make_line ((x1, y1), (x2, y2)) -> (:line, x1, y1, x2, y2)
|
|
||||||
|
|
||||||
& let turtle_radius = 20
|
|
||||||
|
|
||||||
& let turtle_angle = 0.385
|
|
||||||
|
|
||||||
& let turtle_color = (255, 255, 255, 150)
|
|
||||||
|
|
||||||
& fn render_turtle! () -> {
|
|
||||||
& let state = do turtle_states > unbox > last
|
|
||||||
& if state :visible?
|
|
||||||
& then {
|
|
||||||
& let (r, g, b, a) = turtle_color
|
|
||||||
& add_call! ((:fill, r, g, b, a))
|
|
||||||
& let #{heading
|
|
||||||
& :pencolor (pen_r, pen_g, pen_b, pen_a)
|
|
||||||
& :position (x, y)
|
|
||||||
& pendown?
|
|
||||||
& ...} = state
|
|
||||||
& let origin = mult ((0, 1), turtle_radius)
|
|
||||||
& let (x1, y1) = origin
|
|
||||||
& let (x2, y2) = rotate (origin, turtle_angle)
|
|
||||||
& let (x3, y3) = rotate (origin, neg (turtle_angle))
|
|
||||||
& add_call! ((:push))
|
|
||||||
& add_call! ((:translate, x, y))
|
|
||||||
& add_call! ((:rotate, turn/rad (heading)))
|
|
||||||
& add_call! ((:noStroke))
|
|
||||||
& add_call! ((:beginShape))
|
|
||||||
& add_call! ((:vertex, x1, y1))
|
|
||||||
& add_call! ((:vertex, x2, y2))
|
|
||||||
& add_call! ((:vertex, x3, y3))
|
|
||||||
& add_call! ((:endShape))
|
|
||||||
& & there's a happy bug here: the stroke will be the same width as the pen width. Keep this for now. Consider also showing the pen colour here?
|
|
||||||
& add_call! ((:stroke, pen_r, pen_g, pen_b, pen_a))
|
|
||||||
& if pendown? then add_call! ((:line, 0, 0, x1, y1)) else nil
|
|
||||||
& add_call! ((:pop))
|
|
||||||
& :ok
|
|
||||||
& }
|
|
||||||
& else :ok
|
|
||||||
& }
|
|
||||||
|
|
||||||
& fn state/call () -> {
|
|
||||||
& let cmd = do turtle_commands > unbox > last > first
|
|
||||||
& let states = unbox (turtle_states)
|
|
||||||
& let curr = last (states)
|
|
||||||
& let prev = at (states, sub (count (states), 2))
|
|
||||||
& match cmd with {
|
|
||||||
& :forward -> if curr :pendown?
|
|
||||||
& then make_line (prev :position, curr :position)
|
|
||||||
& else nil
|
|
||||||
& :back -> if curr :pendown?
|
|
||||||
& then make_line (prev :position, curr :position)
|
|
||||||
& else nil
|
|
||||||
& :home -> if curr :pendown?
|
|
||||||
& then make_line (prev :position, curr :position)
|
|
||||||
& else nil
|
|
||||||
& :goto -> if curr :pendown?
|
|
||||||
& then make_line (prev :position, curr :position)
|
|
||||||
& else nil
|
|
||||||
& :penwidth -> (:strokeWeight, curr :penwidth)
|
|
||||||
& :pencolor -> {
|
|
||||||
& let (r, g, b, a) = curr :pencolor
|
|
||||||
& (:stroke, r, g, b, a)
|
|
||||||
& }
|
|
||||||
& :clear -> (:background, 0, 0, 0, 255)
|
|
||||||
& _ -> nil
|
|
||||||
& }
|
|
||||||
& }
|
|
||||||
|
|
||||||
fn forward! {
|
fn forward! {
|
||||||
"Moves the turtle forward by a number of steps. Alias: fd!"
|
"Moves the turtle forward by a number of steps. Alias: fd!"
|
||||||
(steps as :number) -> add_command! ((:forward, steps))
|
(steps as :number) -> add_command! ((:forward, steps))
|
||||||
|
@ -1251,7 +1175,7 @@ let pw! = penwidth!
|
||||||
|
|
||||||
fn background! {
|
fn background! {
|
||||||
"Sets the background color behind the turtle and path. Alias: bg!"
|
"Sets the background color behind the turtle and path. Alias: bg!"
|
||||||
(color as :keyword) -> add_command! ((:background, :color))
|
(color as :keyword) -> add_command! ((:background, color))
|
||||||
(gray as :number) -> add_command! ((:background, gray, gray, gray, 255))
|
(gray as :number) -> add_command! ((:background, gray, gray, gray, 255))
|
||||||
((r as :number, g as :number, b as :number)) -> add_command! ((:background, r, g, b, 255))
|
((r as :number, g as :number, b as :number)) -> add_command! ((:background, r, g, b, 255))
|
||||||
((r as :number, g as :number, b as :number, a as :number)) -> add_command! ((:background, r, g, b, a))
|
((r as :number, g as :number, b as :number, a as :number)) -> add_command! ((:background, r, g, b, a))
|
||||||
|
@ -1338,6 +1262,7 @@ fn apply_command {
|
||||||
(:loadstate, x, y, heading, visible?, pendown?, penwidth, r, g, b, a) -> #{:position (x, y), heading, visible?, pendown?, penwidth, :pencolor (r, g, b, a)}
|
(:loadstate, x, y, heading, visible?, pendown?, penwidth, r, g, b, a) -> #{:position (x, y), heading, visible?, pendown?, penwidth, :pencolor (r, g, b, a)}
|
||||||
(:show) -> assoc (state, :visible?, true)
|
(:show) -> assoc (state, :visible?, true)
|
||||||
(:hide) -> assoc (state, :visible?, false)
|
(:hide) -> assoc (state, :visible?, false)
|
||||||
|
(:background, _) -> state
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user