AHABKD5VEK5RSTM3CME4XJAHCVTHYV2D2WAWUGSJ6PBUCUI7CB3AC CQHZPQEOQ3W3TPJTJWOZGA3P5AYOZJO67TKNQCPL5FKGHLRJP3AAC PHCL6PQZG5UGDFSXGSG3LVOEQHQWOQQMH2DWBVFN5O3DQ7YK3IWAC YK4G5INACWFTBIEAFL4DVMRYG3MGO7Y5VDT2FVCN7OW3YTHTLC4QC G335UZH5POREKDAOCCRJZYAGY7F4LO7VQ476GF6OWKYUO2GEKXGAC SJGHLCLGIENGSEL3H4EVSW7VJT2WJRGJNIR3BURZJR5RCE2T3YWAC LUYWR2DLKU5HA5LEY5ZW6BKL5WEWJREBN5WL4C7XPYECD7LUP3VQC MUADNESBQPELEGUUCGMPUZKVQZPUSSNALDW3MWCBJ42MRFWMQF7QC GVX7YSQYURPWFSUWVUAORZJTQBJURWWNBNUGEZYFAUMX3X5LSACQC YYVOTWROJFENEAQ4M4VJPWLODTTX7BTAL3NQSTL2UKJ5VSOEH4JQC KCUJIKQ6POLGKI26YHZWTD242B7JOSGI4VO4XBSKAU66ARM3EIBQC LYLOM4SPCNQX64J76E7WDGCXPJOJ2GTSLJCDREJJFZY46HGMCR3QC FKENDSMEJXEZPAT6K5TYJC2KZBVYKMXA7LN7ZVPZMYIBODWIB64AC VZTOJS52BORPI34JWUWQFQEENTMZDLQHCCNZMFBQ6UWFLHE6X5GAC 4IBBQBYGBZDCXQO5C7H6UTWT67YJUJ7R6FH6UNHX4QGXOO47UUBQC TEOLZ25FHVXJ3DF3LRXEBUIAH76FUHA24U7F3FZFKZVIQBSF3VZAC WMYD2MLJHHONHGA2M2K7G5DYZ47A7ZEZ7M3CJAMLFQK7GRG4UE3AC RRYPAAZARIX5ZIF5PDYPUDJP3VFMNYXIEGTUK4XCTZKIJRDPOD5AC TWOCOTZPRAI2JQH6QY2ZBUZGYA53G7MCOSO64LBIO7OCVEHK7AGQC IMDZUX5WFBYCKZPYGVTSJPONI3ORUDNALX5A2BMUFK3JOZ6DHJWQC TRZPDEYAE6S3FP7NKTNKQLECPVV2LRKGC7PJCC2MHPXDLU7CAABQC 73FZRRIBGT2F6MK4C2JIK4M4BXVKLF5YWRWEERBB7BZ7X4XZCWTQC KWMGPU6WTJORKCZAVAFGBM2C5X7NDWR44HHJOZDVLUOBOVULSB2QC -- game logic-- spritesCELL_PLAYER = 0CELL_PLAYER_ON_TARGET = 1CELL_CRATE = 2CELL_CRATE_ON_TARGET = 3CELL_TARGET = 4CELL_WALL = 5CELL_GRASS = 6 -- vestigial?CELL_VACANT = 7function load_level(level)local result = {}for _,row in ipairs(level) dolocal dest = {}for _,pair in ipairs(row) dotable.insert(dest, floor(pair/16))table.insert(dest, pair%16)endtable.insert(result, dest)endreturn resultendfunction player_state(level_state)for r,row in ipairs(level_state) dofor c,cell in ipairs(row) doif cell == CELL_PLAYER or cell == CELL_PLAYER_ON_TARGET thenreturn {x=c, y=r}end end end endfunction draw_level_state()for r,row in ipairs(level_state) dofor c,cell in ipairs(row) dodraw_sprite(cell, left+(c-1)*side, top+(r-1)*side)end end endfunction draw_sprite(cell, x,y)if cell >= CELL_VACANT thencolor(0,0,0)rect('fill', x,y, side,side)returnendlocal sprite = sprites[cell+1]for r,row in ipairs(sprite) dofor c,clr in ipairs(row) docolor(unpack(clr))rect('fill', x+(c-1)*pxside, y+(r-1)*pxside, pxside, pxside)end end endfunction move_down()if player.y >= #level_state then return endlocal src = level_state[player.y][player.x]local dest = level_state[player.y+1][player.x]if dest == CELL_WALL then return endlocal u = {{x=player.x, y=player.y, cell=src},{x=player.x, y=player.y+1, cell=dest}}if dest == CELL_CRATE or dest == CELL_CRATE_ON_TARGET thenif player.y >= #level_state-1 then return endlocal dest2 = level_state[player.y+2][player.x]if dest2 ~= CELL_VACANT and dest2 ~= CELL_TARGET then return endtable.insert(u, {x=player.x, y=player.y+2, cell=dest2})move_crate_to(player.y+2, player.x)remove_crate_from(player.y+1, player.x)endmove_player_to(player.y+1, player.x)remove_player_from(player.y, player.x)player.y = player.y+1table.insert(undo_history, u)endfunction move_up()if player.y <= 1 then return endlocal src = level_state[player.y][player.x]local dest = level_state[player.y-1][player.x]if dest == CELL_WALL then return endlocal u = {{x=player.x, y=player.y, cell=src},{x=player.x, y=player.y-1, cell=dest}}if dest == CELL_CRATE or dest == CELL_CRATE_ON_TARGET thenif player.y <= 2 then return endlocal dest2 = level_state[player.y-2][player.x]if dest2 ~= CELL_VACANT and dest2 ~= CELL_TARGET then return endtable.insert(u, {x=player.x, y=player.y-2, cell=dest2})move_crate_to(player.y-2, player.x)remove_crate_from(player.y-1, player.x)endmove_player_to(player.y-1, player.x)remove_player_from(player.y, player.x)player.y = player.y-1table.insert(undo_history, u)endfunction move_left()if player.x <= 1 then return endlocal src = level_state[player.y][player.x]local dest = level_state[player.y][player.x-1]if dest == CELL_WALL then return endlocal u = {{x=player.x, y=player.y, cell=src},{x=player.x-1, y=player.y, cell=dest}}if dest == CELL_CRATE or dest == CELL_CRATE_ON_TARGET thenif player.x <= 2 then return endlocal dest2 = level_state[player.y][player.x-2]if dest2 ~= CELL_VACANT and dest2 ~= CELL_TARGET then return endtable.insert(u, {x=player.x-2, y=player.y, cell=dest2})move_crate_to(player.y, player.x-2)remove_crate_from(player.y, player.x-1)endmove_player_to(player.y, player.x-1)remove_player_from(player.y, player.x)player.x = player.x-1table.insert(undo_history, u)endfunction move_right()if player.x >= #level_state[1] then return endlocal src = level_state[player.y][player.x]local dest = level_state[player.y][player.x+1]if dest == CELL_WALL then return endlocal u = {{x=player.x, y=player.y, cell=src},{x=player.x+1, y=player.y, cell=dest}}if dest == CELL_CRATE or dest == CELL_CRATE_ON_TARGET thenif player.x >= #level_state[1]-1 then return endlocal dest2 = level_state[player.y][player.x+2]if dest2 ~= CELL_VACANT and dest2 ~= CELL_TARGET then return endtable.insert(u, {x=player.x+2, y=player.y, cell=dest2})move_crate_to(player.y, player.x+2)remove_crate_from(player.y, player.x+1)endmove_player_to(player.y, player.x+1)remove_player_from(player.y, player.x)player.x = player.x+1table.insert(undo_history, u)endfunction move_player_to(y, x)local old = level_state[y][x]if old == CELL_VACANT thenlevel_state[y][x] = CELL_PLAYERelseif old == CELL_TARGET thenlevel_state[y][x] = CELL_PLAYER_ON_TARGETelseerror('cannot move player to '..old)endendfunction remove_player_from(y, x)local src = level_state[y][x]if src == CELL_PLAYER thenlevel_state[y][x] = CELL_VACANTelseif src == CELL_PLAYER_ON_TARGET thenlevel_state[y][x] = CELL_TARGETendendfunction move_crate_to(y, x)local old = level_state[y][x]if old == CELL_VACANT thenlevel_state[y][x] = CELL_CRATEelseif old == CELL_TARGET thenlevel_state[y][x] = CELL_CRATE_ON_TARGETelseerror('cannot move crate to '..old)endendfunction remove_crate_from(y, x)local src = level_state[y][x]if src == CELL_CRATE thenlevel_state[y][x] = CELL_VACANTelseif src == CELL_CRATE_ON_TARGET thenlevel_state[y][x] = CELL_TARGETendendfunction undo_move()if undo_state == nil then return endfor _,sq in ipairs(undo_state) dolevel_state[sq.y][sq.x] = sq.cellendplayer = player_state(level_state)endfunction any_state_in_level(needle)for r,row in ipairs(level_state) dofor c,cell in ipairs(row) doif cell == needle thenreturn trueend end end endlocal undo_state = table.remove(undo_history)function move_to_empty_space(y, x)local path = unwind_path(find_path(level_state, player, {y=y, x=x}))if #path == 0 then return endlocal src = level_state[player.y][player.x]local dest = level_state[y][x]if dest == CELL_WALL or dest == CELL_CRATE or dest == CELL_CRATE_ON_TARGET thenreturnendlocal u = {{x=player.x, y=player.y, cell=src},{x=x, y=y, cell=dest}}move_player_to(y, x)remove_player_from(player.y, player.x)player.x, player.y = x, ytable.insert(undo_history, u)end
function move_down()if player.y >= #level_state then return endlocal src = level_state[player.y][player.x]local dest = level_state[player.y+1][player.x]if dest == CELL_WALL then return endlocal u = {{x=player.x, y=player.y, cell=src},{x=player.x, y=player.y+1, cell=dest}}if dest == CELL_CRATE or dest == CELL_CRATE_ON_TARGET thenif player.y >= #level_state-1 then return endlocal dest2 = level_state[player.y+2][player.x]if dest2 ~= CELL_VACANT and dest2 ~= CELL_TARGET then return endtable.insert(u, {x=player.x, y=player.y+2, cell=dest2})move_crate_to(player.y+2, player.x)remove_crate_from(player.y+1, player.x)endmove_player_to(player.y+1, player.x)remove_player_from(player.y, player.x)player.y = player.y+1table.insert(undo_history, u)endfunction move_up()if player.y <= 1 then return endlocal src = level_state[player.y][player.x]local dest = level_state[player.y-1][player.x]if dest == CELL_WALL then return endlocal u = {{x=player.x, y=player.y, cell=src},{x=player.x, y=player.y-1, cell=dest}}if dest == CELL_CRATE or dest == CELL_CRATE_ON_TARGET thenif player.y <= 2 then return endlocal dest2 = level_state[player.y-2][player.x]if dest2 ~= CELL_VACANT and dest2 ~= CELL_TARGET then return endtable.insert(u, {x=player.x, y=player.y-2, cell=dest2})move_crate_to(player.y-2, player.x)remove_crate_from(player.y-1, player.x)endmove_player_to(player.y-1, player.x)remove_player_from(player.y, player.x)player.y = player.y-1table.insert(undo_history, u)endfunction move_left()if player.x <= 1 then return endlocal src = level_state[player.y][player.x]local dest = level_state[player.y][player.x-1]if dest == CELL_WALL then return endlocal u = {{x=player.x, y=player.y, cell=src},{x=player.x-1, y=player.y, cell=dest}}if dest == CELL_CRATE or dest == CELL_CRATE_ON_TARGET thenif player.x <= 2 then return endlocal dest2 = level_state[player.y][player.x-2]if dest2 ~= CELL_VACANT and dest2 ~= CELL_TARGET then return endtable.insert(u, {x=player.x-2, y=player.y, cell=dest2})move_crate_to(player.y, player.x-2)remove_crate_from(player.y, player.x-1)endmove_player_to(player.y, player.x-1)remove_player_from(player.y, player.x)player.x = player.x-1table.insert(undo_history, u)endfunction move_right()if player.x >= #level_state[1] then return endlocal src = level_state[player.y][player.x]local dest = level_state[player.y][player.x+1]if dest == CELL_WALL then return endlocal u = {{x=player.x, y=player.y, cell=src},{x=player.x+1, y=player.y, cell=dest}}if dest == CELL_CRATE or dest == CELL_CRATE_ON_TARGET thenif player.x >= #level_state[1]-1 then return endlocal dest2 = level_state[player.y][player.x+2]if dest2 ~= CELL_VACANT and dest2 ~= CELL_TARGET then return endtable.insert(u, {x=player.x+2, y=player.y, cell=dest2})move_crate_to(player.y, player.x+2)remove_crate_from(player.y, player.x+1)endmove_player_to(player.y, player.x+1)remove_player_from(player.y, player.x)player.x = player.x+1table.insert(undo_history, u)endfunction move_to_empty_space(y, x)local path = unwind_path(find_path(level_state, player, {y=y, x=x}))if #path == 0 then return endlocal src = level_state[player.y][player.x]local dest = level_state[y][x]if dest == CELL_WALL or dest == CELL_CRATE or dest == CELL_CRATE_ON_TARGET thenreturnendlocal u = {{x=player.x, y=player.y, cell=src},{x=x, y=y, cell=dest}}move_player_to(y, x)remove_player_from(player.y, player.x)player.x, player.y = x, ytable.insert(undo_history, u)endfunction move_player_to(y, x)local old = level_state[y][x]if old == CELL_VACANT thenlevel_state[y][x] = CELL_PLAYERelseif old == CELL_TARGET thenlevel_state[y][x] = CELL_PLAYER_ON_TARGETelseerror('cannot move player to '..old)endendfunction remove_player_from(y, x)local src = level_state[y][x]if src == CELL_PLAYER thenlevel_state[y][x] = CELL_VACANTelseif src == CELL_PLAYER_ON_TARGET thenlevel_state[y][x] = CELL_TARGETendendfunction move_crate_to(y, x)local old = level_state[y][x]if old == CELL_VACANT thenlevel_state[y][x] = CELL_CRATEelseif old == CELL_TARGET thenlevel_state[y][x] = CELL_CRATE_ON_TARGETelseerror('cannot move crate to '..old)endendfunction remove_crate_from(y, x)local src = level_state[y][x]if src == CELL_CRATE thenlevel_state[y][x] = CELL_VACANTelseif src == CELL_CRATE_ON_TARGET thenlevel_state[y][x] = CELL_TARGETendendfunction undo_move()local undo_state = table.remove(undo_history)if undo_state == nil then return endfor _,sq in ipairs(undo_state) dolevel_state[sq.y][sq.x] = sq.cellendplayer = player_state(level_state)end
endfunction car.draw()-- the sprites are pretty dark; a dark background accentuates contrast between their colorsg.setBackgroundColor(0.4,0.4, 0.4)ui_state.button_handlers = {}draw_level_state()draw_buttons()draw_level_number()draw_win_state()draw_hud()end-- some constants for draw_level_numberlevel_color = {0.8,0.8,0.8}level_width = App.width('MMM')+10function draw_level_number()local lx, ly = left-level_width, top+10if Safe_height > Safe_width thenlx, ly = (Safe_width - level_width)/2, top-side-Line_height-10endcolor(unpack(level_color))g.print(curr_level, lx,ly)endfunction draw_win_state()if not any_state_in_level(CELL_CRATE) thencolor(0,1,0)g.print('success!', left-2*side, top+side)end
function draw_buttons()local bg = {r=0.4,g=0.2,b=0.4}button(ui_state, 'left', {x=0, y=bside, w=bside, h=Safe_height-bside*2,bg=bg, onpress1=move_left})button(ui_state, 'right', {x=Safe_width-bside, y=bside, w=bside, h=Safe_height-bside*2,bg=bg, onpress1=move_right})button(ui_state, 'up', {x=bside, y=0, w=Safe_width-bside*2, h=bside,bg=bg, onpress1=move_up})button(ui_state, 'down', {x=bside, y=Safe_height-bside, w=Safe_width-bside*2, h=bside,bg=bg, onpress1=move_down})
-- level navigationlocal ns, ps = tostring('>>'), tostring('<<')local nw, pw = App.width(ns)+10, App.width(ps)+10local h = Line_height+10local nx,px, yif Safe_width > Safe_height thenpx, nx = left-side-pw-10, left+lw*side+side+10y = top + lh*side/2elsepx, nx = left+side+10, left+lw*side-side-nw-10y = top-side-hendif curr_level > 1 thenbutton(ui_state, 'previous level', {x=px, y=y, w=pw, h=h,bg=bg, onpress1=previous_level,icon=function(p)color(unpack(level_color))g.print(ps, p.x+5, p.y+5)end,})endif curr_level < #levels thenbutton(ui_state, 'next level', {x=nx, y=y, w=nw, h=h,bg=bg, onpress1=next_level,icon=function(p)color(unpack(level_color))g.print(ns, p.x+5, p.y+5)end,})endif #undo_history > 0 thenlocal w = App.width('undo')+10local ux, uy = left+lw*side+10, top+10if Safe_width < Safe_height thenux, uy = left+lw*side-w-10, top+lh*side+10
function load_level(level)local result = {}for _,row in ipairs(level) dolocal dest = {}for _,pair in ipairs(row) dotable.insert(dest, floor(pair/16))table.insert(dest, pair%16)
button(ui_state, 'undo', {x=ux, y=uy, w=w, h=Line_height+10,bg=bg, onpress1=undo_move,icon=function(p)color(0,0,0)g.print('undo', p.x+5, p.y+5)end,})endend-- ensure we can fit the above layout into the available window area-- leave 6 tiles worth of margin on all sides for the nav buttonsfunction car.resize()local w,h = Safe_width, Safe_heightlocal w1, w2 = landscape_button_offsets()local h1, h2 = portrait_button_offsets()if Safe_width > Safe_height thenside = min((w-w1-w2)/(1+lw+1+6), h/(lh+6)) -- leave 1 side between board and buttonselseside = min(w/(lw+6), (h-h1-h2)/(1+lh+1+6)) -- leave 1 side between board and buttonsendpxside = side/num_tile_pxleft = (w-side*lw)/2top = (h-side*lh)/2if Safe_width > Safe_height thenbside = min((w-w1-w2-side*(1+lw+1))/2, (h-side*lh)/2)elsebside = min((h-h1-h2-side*(1+lh+1))/2, (w-side*lw)/2)
table.insert(result, dest)
end-- space needed for buttons to the left and rightfunction landscape_button_offsets()local ns, ps = tostring('>>'), tostring('<<')local nw, pw = App.width(ns)+10, App.width(ps)+10return 10+pw+10, 10+nw+10-- we assume the level name and undo button will be within this widthend-- space needed for buttons to the top and bottomfunction portrait_button_offsets()return 10+Line_height+10, 10+Line_height+10
return result
function player_state(level_state)for r,row in ipairs(level_state) dofor c,cell in ipairs(row) doif cell == CELL_PLAYER or cell == CELL_PLAYER_ON_TARGET thenreturn {x=c, y=r}end end end end
-- routines for drawing and sizing to available spacefunction car.draw()-- the sprites are pretty dark; a dark background accentuates contrast between their colorsg.setBackgroundColor(0.4,0.4, 0.4)ui_state.button_handlers = {}draw_level_state()draw_buttons()draw_level_number()draw_win_state()draw_hud()endfunction draw_level_state()for r,row in ipairs(level_state) dofor c,cell in ipairs(row) dodraw_sprite(cell, left+(c-1)*side, top+(r-1)*side)end end endfunction draw_sprite(cell, x,y)if cell >= CELL_VACANT thencolor(0,0,0)rect('fill', x,y, side,side)returnendlocal sprite = sprites[cell+1]for r,row in ipairs(sprite) dofor c,clr in ipairs(row) docolor(unpack(clr))rect('fill', x+(c-1)*pxside, y+(r-1)*pxside, pxside, pxside)end end end-- some constants for draw_level_numberlevel_color = {0.8,0.8,0.8}level_width = App.width('MMM')+10function draw_level_number()local lx, ly = left-level_width, top+10if Safe_height > Safe_width thenlx, ly = (Safe_width - level_width)/2, top-side-Line_height-10endcolor(unpack(level_color))g.print(curr_level, lx,ly)endfunction draw_win_state()if not any_state_in_level(CELL_CRATE) thencolor(0,1,0)g.print('success!', left-2*side, top+side)endendfunction any_state_in_level(needle)for r,row in ipairs(level_state) dofor c,cell in ipairs(row) doif cell == needle thenreturn trueend end end endfunction draw_buttons()local bg = {r=0.4,g=0.2,b=0.4}button(ui_state, 'left', {x=0, y=bside, w=bside, h=Safe_height-bside*2,bg=bg, onpress1=move_left})button(ui_state, 'right', {x=Safe_width-bside, y=bside, w=bside, h=Safe_height-bside*2,bg=bg, onpress1=move_right})button(ui_state, 'up', {x=bside, y=0, w=Safe_width-bside*2, h=bside,bg=bg, onpress1=move_up})button(ui_state, 'down', {x=bside, y=Safe_height-bside, w=Safe_width-bside*2, h=bside,bg=bg, onpress1=move_down})-- level navigationlocal ns, ps = tostring('>>'), tostring('<<')local nw, pw = App.width(ns)+10, App.width(ps)+10local h = Line_height+10local nx,px, yif Safe_width > Safe_height thenpx, nx = left-side-pw-10, left+lw*side+side+10y = top + lh*side/2elsepx, nx = left+side+10, left+lw*side-side-nw-10y = top-side-hendif curr_level > 1 thenbutton(ui_state, 'previous level', {x=px, y=y, w=pw, h=h,bg=bg, onpress1=previous_level,icon=function(p)color(unpack(level_color))g.print(ps, p.x+5, p.y+5)end,})endif curr_level < #levels thenbutton(ui_state, 'next level', {x=nx, y=y, w=nw, h=h,bg=bg, onpress1=next_level,icon=function(p)color(unpack(level_color))g.print(ns, p.x+5, p.y+5)end,})endif #undo_history > 0 thenlocal w = App.width('undo')+10local ux, uy = left+lw*side+10, top+10if Safe_width < Safe_height thenux, uy = left+lw*side-w-10, top+lh*side+10endbutton(ui_state, 'undo', {x=ux, y=uy, w=w, h=Line_height+10,bg=bg, onpress1=undo_move,icon=function(p)color(0,0,0)g.print('undo', p.x+5, p.y+5)end,})endend-- leave 6 tiles worth of margin on all sides for the nav buttonsfunction car.resize()local w,h = Safe_width, Safe_heightlocal w1, w2 = landscape_button_offsets()local h1, h2 = portrait_button_offsets()if Safe_width > Safe_height thenside = min((w-w1-w2)/(1+lw+1+6), h/(lh+6)) -- leave 1 side between board and buttonselseside = min(w/(lw+6), (h-h1-h2)/(1+lh+1+6)) -- leave 1 side between board and buttonsendpxside = side/num_tile_pxleft = (w-side*lw)/2top = (h-side*lh)/2if Safe_width > Safe_height thenbside = min((w-w1-w2-side*(1+lw+1))/2, (h-side*lh)/2)elsebside = min((h-h1-h2-side*(1+lh+1))/2, (w-side*lw)/2)endend-- space needed for buttons to the left and rightfunction landscape_button_offsets()local ns, ps = tostring('>>'), tostring('<<')local nw, pw = App.width(ns)+10, App.width(ps)+10return 10+pw+10, 10+nw+10-- we assume the level name and undo button will be within this widthend-- space needed for buttons to the top and bottomfunction portrait_button_offsets()return 10+Line_height+10, 10+Line_height+10end