Skip to content

Instantly share code, notes, and snippets.

@akkartik
Last active May 9, 2022 23:45
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save akkartik/f7ec2dce2900857d761df25a6a08f352 to your computer and use it in GitHub Desktop.
Save akkartik/f7ec2dce2900857d761df25a6a08f352 to your computer and use it in GitHub Desktop.
Stack blocks with different effects to transform one sequence of shapes into another
-- To run:
-- Download LÖVE from https://love2d.org
-- Download this file to a directory and rename it to `main.lua`
-- Run the program from that directory.
-- * on Linux (using the appimage binary): `chmod +x path/to/love-11.4-x86_64.AppImage; path/to/love-11.4-x86_64.AppImage .`
-- * on Mac: `path/to/love.app/Contents/MacOS/love .`
--
-- Click on rounded rectangles to see what they do.
-- Use ctrl-n and ctrl-p to jump between puzzles.
-- ### a small evaluator for a few operators over a few values
operators = {'dup', 'fill', 'swap'}
values = {'square', 'circle', 'triangle', 'fill-square', 'fill-circle', 'fill-triangle'}
function eval(program, input)
local x = input
for _, op in ipairs(program) do
local out = {}
if op == 'dup' then
for _, a in ipairs(x) do
table.insert(out, a)
table.insert(out, a)
end
elseif op == 'fill' then
for _, a in ipairs(x) do
if a:match('fill-') then
table.insert(out, a)
else
table.insert(out, 'fill-'..a)
end
end
elseif op == 'swap' then
for i=1,#x,2 do
if i < #x then
table.insert(out, x[i+1])
end
table.insert(out, x[i])
end
end
x = out
end
return x
end
function eval_print(program, input)
local out = eval(program, input)
for _, x in ipairs(out) do
print(x)
end
end
--? eval_print({'swap'}, {'square'})
-- helpers to draw values (20x20 squares) and operators (100x60 buttons)
function draw_value(x,y, value)
if value == 'square' then
rect('line', x,y, 20,20)
elseif value == 'circle' then
circle('line', x+10,y+10, 10)
elseif value == 'triangle' then
polygon('line', x+10,y, x,y+20, x+20,y+20)
elseif value == 'fill-square' then
rect('fill', x,y, 20,20)
elseif value == 'fill-circle' then
circle('fill', x+10,y+10, 10)
elseif value == 'fill-triangle' then
polygon('fill', x+10,y, x,y+20, x+20,y+20)
end
end
draw = {}
function draw.dup(x, y)
color(0.5, 0.5, 0.5)
rect('fill', x,y, 100,60, 5,5)
color(1, 1, 1)
draw_value(x+25,y+5, 'square')
draw_value(x+55,y+5, 'square')
color(0.75, 0.75, 0.75)
line(x+5,y+30, x+95,y+30)
color(1, 1, 1)
draw_value(x+40,y+35, 'square')
end
function draw.fill(x, y)
color(0.5, 0.5, 0.5)
rect('fill', x,y, 100,60, 5,5)
color(1, 1, 1)
draw_value(x+40,y+5, 'fill-square')
color(0.75, 0.75, 0.75)
line(x+5,y+30, x+95,y+30)
color(1, 1, 1)
draw_value(x+40,y+35, 'square')
end
function draw.swap(x, y)
color(0.5, 0.5, 0.5)
rect('fill', x,y, 100,60, 5,5)
color(1, 1, 1)
draw_value(x+25,y+5, 'square')
draw_value(x+55,y+5, 'circle')
color(0.75, 0.75, 0.75)
line(x+5,y+30, x+95,y+30)
color(1, 1, 1)
draw_value(x+25,y+35, 'circle')
draw_value(x+55,y+35, 'square')
end
-- ### UI
width = 640
height = 480
love.window.setMode(width, height)
love.window.setTitle('Love Stacking')
-- the window has 2 parts: the tray of tools and the stacking area
toolx, tooly = 5, 5
toolw, toolh = 150, height-10
stackx, stacky = 5+toolw+5, 5
stackw, stackh = width-stackx-5, height-10
-- app data
exercises = {
{
input = {'square'},
expected_output = {'square', 'square'},
},
{
input = {'square'},
expected_output = {'fill-square'},
},
{
input = {'circle'},
expected_output = {'circle', 'circle'},
},
{
input = {'circle'},
expected_output = {'fill-circle'},
},
{
input = {'triangle'},
expected_output = {'triangle', 'triangle'},
},
{
input = {'triangle'},
expected_output = {'fill-triangle'},
},
{
input = {'circle', 'square'},
expected_output = {'square', 'circle'},
},
{
input = {'circle', 'square', 'triangle'},
expected_output = {'circle', 'circle', 'square', 'square', 'triangle', 'triangle'},
},
{
input = {'circle', 'square', 'triangle'},
expected_output = {'square', 'circle', 'triangle'},
},
{
input = {'triangle'},
expected_output = {'fill-triangle', 'fill-triangle'},
},
{
input = {'circle', 'square', 'triangle', 'circle'},
expected_output = {'square', 'circle', 'circle', 'triangle'},
},
{
input = {'triangle', 'square'},
expected_output = {'fill-square', 'fill-triangle'},
},
}
stack = {}
exercise_index = 1
input = exercises[exercise_index].input
expected_output = exercises[exercise_index].expected_output
function love.keychord_pressed(key)
if key == 'C-n' then
if exercise_index < #exercises then
exercise_index = exercise_index+1
input = exercises[exercise_index].input
expected_output = exercises[exercise_index].expected_output
stack = {}
end
elseif key == 'C-p' then
if exercise_index > 1 then
exercise_index = exercise_index-1
input = exercises[exercise_index].input
expected_output = exercises[exercise_index].expected_output
stack = {}
end
elseif key == 'C-r' then
stack = {}
end
end
button_handlers = {}
function love.draw()
button_handlers = {}
draw_tools()
draw_stack()
end
function draw_tools()
color(0.2, 0.2, 0.2)
rect('fill', toolx,tooly, toolw,toolh)
button_to_push_operator_on_stack('dup', 25)
button_to_push_operator_on_stack('fill', 100)
button_to_push_operator_on_stack('swap', 175)
end
function draw_stack()
-- background
color(0.25,0.25,0.25)
rect('fill', stackx,stacky, stackw,stackh)
-- input and expected output values
color(1,1,1)
local output = eval(stack, input)
if table_equal(output, expected_output) then
color(0,1,0)
end
draw_values(50, expected_output)
color(1,1,1)
draw_values(445, input)
-- stack of operators
color(1,1,1)
local y = 425
for i,op in ipairs(stack) do
y = y-75
button_to_remove_operator_from_stack(op, i, y)
if i < #stack then
y = y-50
local out = eval(slice(stack, 1,i), input)
draw_values(y, out)
end
end
-- color final output differently
y = y-50
local output = eval(stack, input)
if table_equal(output, expected_output) then
color(0,1,0)
draw_values(y, output)
else
color(1,0,0)
draw_values(y, output)
end
end
function button_to_remove_operator_from_stack(name, i, y)
button(name, {x=225,y=y, w=100,h=60, color={0.50,0.50,0.50},
icon = draw[name],
onpress1 = function() table.remove(stack, i) end})
end
function button_to_push_operator_on_stack(name, y)
button(name, {x=25,y=y, w=100,h=60, color={0.50,0.50,0.50},
icon = draw[name],
onpress1 = function() table.insert(stack, name) end})
end
function draw_values(y, values)
local x = 350
for _, value in ipairs(values) do
draw_value(x,y, value)
x = x+25
end
end
-- draw button and queue up event handlers
function button(name, params)
color(params.color[1], params.color[2], params.color[3])
rect('fill', params.x,params.y, params.w,params.h, 5,5)
if params.icon then params.icon(params.x, params.y) end
table.insert(button_handlers, params)
end
-- process button event handlers
function love.mousepressed(x, y, button)
for _,ev in ipairs(button_handlers) do
if x>ev.x and x<ev.x+ev.w and y>ev.y and y<ev.y+ev.h then
if ev.onpress1 and button == 1 then ev.onpress1() end
end
end
end
-- misc helpers
line = love.graphics.line
color = love.graphics.setColor
rect = love.graphics.rectangle
circle = love.graphics.circle
polygon = love.graphics.polygon
function table_equal(a, b)
for k, v in pairs(a) do
if b[k] ~= v then
return false
end
end
for k, v in pairs(b) do
if a[k] ~= v then
return false
end
end
return true
end
function slice(h, s,e)
local result = {}
for i=s,e do
table.insert(result, h[i])
end
return result
end
-- Keyboard driver
function love.keypressed(key, scancode, isrepeat)
if key == 'lctrl' or key == 'rctrl' or key == 'lalt' or key == 'ralt' or key == 'lshift' or key == 'rshift' or key == 'lgui' or key == 'rgui' then
-- do nothing when the modifier is pressed
end
-- include the modifier(s) when the non-modifer is pressed
love.keychord_pressed(combine_modifiers(key))
end
function combine_modifiers(key)
local result = ''
local down = love.keyboard.isDown
if down('lctrl') or down('rctrl') then
result = result..'C-'
end
if down('lalt') or down('ralt') then
result = result..'M-'
end
if down('lgui') or down('rgui') then
result = result..'S-'
end
result = result..key
return result
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment