Last active
May 9, 2022 23:45
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
-- 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