7FNBZUF2PJZ2ZGZRZXQKZQFQGALELKAU2PIGII4745NUMJXGO6YQC 53APNOQ4GXIZOVOAQ7D77NTHX5IR5UEUYKAEEXUMUT2OWZDC2XCAC 6EVAMGQ66R6WRLUOXNXQ4LIL52HHAIA46ZPJGZUO533D4CGPHPDAC 5PAVGVYV2QQ3DQWDXT4NOCZXVVDXYJOMNPLJEDIHRYYSYQV72DEQC AS76UT6HWXX47T2HMNHVPHNFMGC6WY3PWMWLUWFWYPD7XKMMJ2XAC GVX7YSQYURPWFSUWVUAORZJTQBJURWWNBNUGEZYFAUMX3X5LSACQC FKENDSMEJXEZPAT6K5TYJC2KZBVYKMXA7LN7ZVPZMYIBODWIB64AC DGJOMSMTXML2XLFFUGCOI477TB55YZ4ZXVBTLQVAIHEWJWBE3KQAC U32AYLUP4N32MVWH5WFCKP64CW4XO3CCV2AVQ357CMCY7CUBSYJQC 7GQ5TOWHFPXHGR7RFYWHWWMO32MDSVVK7WRLWDEROVTKCQRHRMTAC YR62G4PUZEC2PMXMLTPUKFYTSQ3YTCAXXFGLZOIQTUOM2FABPNFQC AHABKD5VEK5RSTM3CME4XJAHCVTHYV2D2WAWUGSJ6PBUCUI7CB3AC BUPMQLGRZJFGYEY7DI7YV7V3URUE5HVT2AFQHQBG2GORLNSRW7VAC RCEWATRPX3MDVMAIEBEYIRL2MXPQYD47IU7XQUKSUZVFJRU4AN4AC 3CIDHIL2YPGFIPMVPGROBQPUGPGF5OPDQXHSRCQ7EVLNKJ6XDNHQC 3AOSRXSHG5UYYVLV5RIDCPEG2NB7H3K23QF7V6EOJCP24TVP5U6QC DFYYOQMHA7M7WMELX5DHXP5DT5KGQLNJHXYDJZ5P5BIJCTCOQ7LQC local done = initialize_array(false, lh, lw)local cands = {}table.insert(cands, {x=src.x, y=src.y})while #cands > 0 dolocal cand = table.remove(cands, 1)local x,y = cand.x, cand.yif x == dst.x and y == dst.y then return cand endif y >= 1 and y <= lh and x >= 1 and x <= lw and not done[y][x] thendone[y][x] = truelocal curr = level[y][x]if curr ~= CELL_WALL and curr ~= CELL_CRATE and curr ~= CELL_CRATE_ON_TARGET thenend end end endfunction is_empty(level, x,y)local curr = level[y][x]return curr ~= CELL_WALL and curr ~= CELL_CRATE and curr ~= CELL_CRATE_ON_TARGETendfunction unwind_path(c)local result = {}while c dotable.insert(result, {x=c.x, y=c.y})c = c.prevendreturn resultendfunction draw_path(path)color(1,0,0)for _,cell in ipairs(path) dorect('line', left+(cell.x-1)*side, top+(cell.y-1)*side, side, side)end endfunction initialize_array(val, dim, ...)local result = {}local other_dims = {...}if #other_dims == 0 thenfor i=1,dim dotable.insert(result, val)endelsefor i=1,dim dotable.insert(result, initialize_array(val, ...))endendreturn resultendfunction shortest_path(cands)local min, minscore = 1, cands[1].path_lengthfor i=2,#cands dolocal iscore = cands[i].path_lengthif iscore < minscore thenmin, minscore = i, iscoreendendreturn minend--? Real_print('', i, iscore, '<=>', min,minscore)--? Real_print('', #cands)function unwind_steps(c, out)-- insert steps in reverse ordertable.insert(out, c.dir)endreturn outendc = c.prevwhile c dotable.insert(cands, {x=x-1, y=y, dir='left', prev=cand})table.insert(cands, {x=x+1, y=y, dir='right', prev=cand})table.insert(cands, {x=x, y=y-1, dir='up', prev=cand})table.insert(cands, {x=x, y=y+1, dir='down', prev=cand})-- simple solver to remove some drudgery-- like find_empty_path, but always allow going through empty_cell and never allow going through occupied_cell, regardless of what level sayslocal done = initialize_array(false, lh, lw)local cands = {}while #cands > 0 dolocal cand = table.remove(cands, 1)if x == dst.x and y == dst.y then return cand endif y >= 1 and y <= lh and x >= 1 and x <= lw and not done[y][x] thendone[y][x] = trueend end end endif empty.y ~= occupied.y or empty.x ~= occupied.x thenif x == empty.x and y == empty.y thenreturn trueendendif x == occupied.x and y == occupied.y thenreturn falseendendreturn is_empty(level, x,y)function unwind_steps_to_move_crate(c, out)-- insert steps in reverse orderwhile c dotable.insert(out, c.dir)unwind_steps(c.moves, out)c = c.prevendreturn outendfunction vdump_path(c)if c == nil then return endif not c.dir thenassert(not c.moves)assert(not c.prev)returnendReal_print('p'..c.dir)local c2 = c.moveswhile c2 doif c2.dir thenReal_print('m'..c2.dir)endc2 = c2.prevendvdump_path(c.prev)endendfunction dump_path(c)if c == nil then return endif not c.dir thenassert(not c.moves)assert(not c.prev)io.stdout:write('\n')returnendlocal c2 = c.moveswhile c2 doif c2.dir thenendc2 = c2.prevenddump_path(c.prev)io.stdout:write('m'..c2.dir..' ')io.stdout:write('p'..c.dir..' ')function is_assumed_empty(level, x,y, empty, occupied)table.insert(cands, {x=x-1, y=y, dir='left', prev=cand, path_length=p+1})table.insert(cands, {x=x+1, y=y, dir='right', prev=cand, path_length=p+1})table.insert(cands, {x=x, y=y-1, dir='up', prev=cand, path_length=p+1})table.insert(cands, {x=x, y=y+1, dir='down', prev=cand, path_length=p+1})if is_assumed_empty(level, x,y, empty_cell, occupied_cell) thenlocal x,y, p = cand.x, cand.y, cand.path_lengthtable.insert(cands, {x=src.x, y=src.y, path_length=0})--? Real_print('find', src.x, src.y, '-', dst.x, dst.y, '-', empty_cell.x, empty_cell.y, '-', occupied_cell.x, occupied_cell.y)function find_assumed_empty_path_to_empty_cell(level, src, dst, empty_cell, occupied_cell)if not is_assumed_empty(level, dst.x, dst.y, empty_cell, occupied_cell) thenreturnendfunction draw_crate_to_move()if crate_to_move == nil then return endcolor(1,0,0)local x,y = crate_to_move.x, crate_to_move.yrect('line', left+(x-1)*side, top+(y-1)*side, side, side)end-- moving player without moving any cratesfunction find_empty_path(level, src, dst)if not is_empty(level, dst.x, dst.y) thenreturnend-- moving crate without moving any other cratesfunction find_path_for_crate(level, player, crate, dst)local done = initialize_array(false, lh, lw)local cands = {}table.insert(cands, {crate={x=crate.x, y=crate.y}, player={x=player.x, y=player.y}, path_length=0})--? io.stdout:write('---\n')while #cands > 0 doif c.x == dst.x and c.y == dst.y then return cand endif c.y >= 1 and c.y <= lh and c.x >= 1 and c.x <= lw and not done[c.y][c.x] thendone[c.y][c.x] = truelocal curr = level[c.y][c.x]-- try to push crate left, by first getting to the right of itif moves or (p.x == c.x+1 and p.y == c.y) then-- after the push, both player and crate have movedend-- try to push crate rightif moves or (p.x == c.x-1 and p.y == c.y) thenend-- try to push crate upif moves or (p.x == c.x and p.y == c.y+1) thenend-- try to push crate downif moves or (p.x == c.x and p.y == c.y-1) thenendend end end endtable.insert(cands, {player={x=c.x, y=c.y}, crate={x=c.x, y=c.y+1}, dir='down', moves=moves, prev=cand, path_length=cand.path_length+moves.path_length})moves = find_assumed_empty_path_to_empty_cell(level, p, {x=c.x, y=c.y-1}, crate, c)table.insert(cands, {player={x=c.x, y=c.y}, crate={x=c.x, y=c.y-1}, dir='up', moves=moves, prev=cand, path_length=cand.path_length+moves.path_length})local moves = find_assumed_empty_path_to_empty_cell(level, p, {x=c.x, y=c.y+1}, crate, c)table.insert(cands, {player={x=c.x, y=c.y}, crate={x=c.x+1, y=c.y}, dir='right', moves=moves, prev=cand, path_length=cand.path_length+moves.path_length})moves = find_assumed_empty_path_to_empty_cell(level, p, {x=c.x-1, y=c.y}, crate, c)table.insert(cands, {player={x=c.x, y=c.y}, crate={x=c.x-1, y=c.y}, dir='left', moves=moves, prev=cand, path_length=cand.path_length+moves.path_length})local moves = find_assumed_empty_path_to_empty_cell(level, p, {x=c.x+1, y=c.y}, --[[empty]] crate, --[[occupied]] c)if curr ~= CELL_WALL and (curr ~= CELL_CRATE or (c.y == crate.y and c.x == crate.x)) and (curr ~= CELL_CRATE_ON_TARGET or (c.y == crate.y and c.x == crate.x)) thenlocal min_index = shortest_path(cands)local cand = table.remove(cands, min_index)local p, c = cand.player, cand.crate--? i = i+1--? io.stdout:write(tostring(i)..' '..tostring(crate.x)..','..tostring(crate.y)..' '..tostring(crate.id)..' '..tostring(p.x)..','..tostring(p.y)..' '..tostring(c.x)..','..tostring(c.y)..' ')--? dump_path(cand)--? local i=0
-- moving player without moving any cratesfunction draw_crate_to_move()if crate_to_move == nil then return endcolor(1,0,0)local x,y = crate_to_move.x, crate_to_move.yrect('line', left+(x-1)*side, top+(y-1)*side, side, side)endfunction car.update()if next_pending_move and Current_time >= next_pending_move thenlocal dir = table.remove(pending_moves)move(dir, --[[add to undo]] false)if #pending_moves > 0 thennext_pending_move = Current_time + 0.1endendendfunction make_all_pending_moves()while #pending_moves > 0 dolocal dir = table.remove(pending_moves)move(dir, --[[add to undo]] false)endendfunction plan_move_to_empty_space(y, x)local path = find_empty_path(level_state, player, {y=y, x=x})if path == nil then return endpending_moves = unwind_steps(path, {})local src = level_state[player.y][player.x]local dest = level_state[y][x]local u = {{x=player.x, y=player.y, cell=src},{x=x, y=y, cell=dest}}table.insert(undo_history, u) -- add to undo without making move yetnext_pending_move = Current_timeendfunction find_empty_path(level, src, dst)if not is_empty(level, dst.x, dst.y) thenreturnendlocal done = initialize_array(false, lh, lw)local cands = {}table.insert(cands, {x=src.x, y=src.y})while #cands > 0 dolocal cand = table.remove(cands, 1)local x,y = cand.x, cand.yif x == dst.x and y == dst.y then return cand endif y >= 1 and y <= lh and x >= 1 and x <= lw and not done[y][x] thendone[y][x] = truelocal curr = level[y][x]if curr ~= CELL_WALL and curr ~= CELL_CRATE and curr ~= CELL_CRATE_ON_TARGET thentable.insert(cands, {x=x-1, y=y, dir='left', prev=cand})table.insert(cands, {x=x+1, y=y, dir='right', prev=cand})table.insert(cands, {x=x, y=y-1, dir='up', prev=cand})table.insert(cands, {x=x, y=y+1, dir='down', prev=cand})end end end endfunction is_empty(level, x,y)local curr = level[y][x]return curr ~= CELL_WALL and curr ~= CELL_CRATE and curr ~= CELL_CRATE_ON_TARGETendfunction unwind_path(c)local result = {}while c dotable.insert(result, {x=c.x, y=c.y})c = c.prevendreturn resultendfunction unwind_steps(c, out)-- insert steps in reverse orderwhile c dotable.insert(out, c.dir)c = c.prevendreturn outendfunction initialize_array(val, dim, ...)local result = {}local other_dims = {...}if #other_dims == 0 thenfor i=1,dim dotable.insert(result, val)endelsefor i=1,dim dotable.insert(result, initialize_array(val, ...))endendreturn resultendfunction dump_path(c)if c == nil then return endif not c.dir thenassert(not c.moves)assert(not c.prev)io.stdout:write('\n')returnendio.stdout:write('p'..c.dir..' ')local c2 = c.moveswhile c2 doif c2.dir thenio.stdout:write('m'..c2.dir..' ')endc2 = c2.prevenddump_path(c.prev)end
-- moving crate without moving any other cratesfunction plan_move_crate(y, x)local path = find_path_for_crate(level_state, player, crate_to_move, {y=y, x=x})if path == nil then return end--? Real_print('--')--? vdump_path(path)pending_moves = unwind_steps_to_move_crate(path, {})for i=#pending_moves,1,-1 do print(pending_moves[i]) endlocal src = level_state[player.y][player.x]local crate_cell = level_state[crate_to_move.y][crate_to_move.x]local dest = level_state[y][x]local u = {{x=player.x, y=player.y, cell=src},{x=crate_to_move.x, y=crate_to_move.y, cell=crate_cell},{x=x, y=y, cell=dest}}print('player initial', player.x, player.y, src)print('crate initial', crate_to_move.x, crate_to_move.y, crate_cell)print('crate final', x, y, dest)-- we also need to save the initial state of the final position of the playerlocal fpy,fpx = compute_cell(y,x, opposite_dir(pending_moves[1]))print('player final', fpx, fpy, level_state[fpy][fpx])table.insert(u, {x=fpx, y=fpy, cell=level_state[fpy][fpx]})table.insert(undo_history, u) -- add to undo without making move yetnext_pending_move = Current_timeendfunction find_path_for_crate(level, player, crate, dst)local done = initialize_array(false, lh, lw)local cands = {}--? local i=0table.insert(cands, {crate={x=crate.x, y=crate.y}, player={x=player.x, y=player.y}, path_length=0})--? io.stdout:write('---\n')while #cands > 0 dolocal min_index = shortest_path(cands)local cand = table.remove(cands, min_index)local p, c = cand.player, cand.crate--? i = i+1--? io.stdout:write(tostring(i)..' '..tostring(crate.x)..','..tostring(crate.y)..' '..tostring(crate.id)..' '..tostring(p.x)..','..tostring(p.y)..' '..tostring(c.x)..','..tostring(c.y)..' ')--? dump_path(cand)if c.x == dst.x and c.y == dst.y then return cand endif c.y >= 1 and c.y <= lh and c.x >= 1 and c.x <= lw and not done[c.y][c.x] thendone[c.y][c.x] = truelocal curr = level[c.y][c.x]if curr ~= CELL_WALL and (curr ~= CELL_CRATE or (c.y == crate.y and c.x == crate.x)) and (curr ~= CELL_CRATE_ON_TARGET or (c.y == crate.y and c.x == crate.x)) then-- try to push crate left, by first getting to the right of itlocal moves = find_assumed_empty_path_to_empty_cell(level, p, {x=c.x+1, y=c.y}, --[[empty]] crate, --[[occupied]] c)if moves or (p.x == c.x+1 and p.y == c.y) then-- after the push, both player and crate have movedtable.insert(cands, {player={x=c.x, y=c.y}, crate={x=c.x-1, y=c.y}, dir='left', moves=moves, prev=cand, path_length=cand.path_length+moves.path_length})end-- try to push crate rightmoves = find_assumed_empty_path_to_empty_cell(level, p, {x=c.x-1, y=c.y}, crate, c)if moves or (p.x == c.x-1 and p.y == c.y) thentable.insert(cands, {player={x=c.x, y=c.y}, crate={x=c.x+1, y=c.y}, dir='right', moves=moves, prev=cand, path_length=cand.path_length+moves.path_length})end-- try to push crate uplocal moves = find_assumed_empty_path_to_empty_cell(level, p, {x=c.x, y=c.y+1}, crate, c)if moves or (p.x == c.x and p.y == c.y+1) thentable.insert(cands, {player={x=c.x, y=c.y}, crate={x=c.x, y=c.y-1}, dir='up', moves=moves, prev=cand, path_length=cand.path_length+moves.path_length})end-- try to push crate downmoves = find_assumed_empty_path_to_empty_cell(level, p, {x=c.x, y=c.y-1}, crate, c)if moves or (p.x == c.x and p.y == c.y-1) thentable.insert(cands, {player={x=c.x, y=c.y}, crate={x=c.x, y=c.y+1}, dir='down', moves=moves, prev=cand, path_length=cand.path_length+moves.path_length})endend end end end-- like find_empty_path, but always allow going through empty_cell and never allow going through occupied_cell, regardless of what level saysfunction find_assumed_empty_path_to_empty_cell(level, src, dst, empty_cell, occupied_cell)if not is_assumed_empty(level, dst.x, dst.y, empty_cell, occupied_cell) thenreturnendlocal done = initialize_array(false, lh, lw)local cands = {}--? Real_print('find', src.x, src.y, '-', dst.x, dst.y, '-', empty_cell.x, empty_cell.y, '-', occupied_cell.x, occupied_cell.y)table.insert(cands, {x=src.x, y=src.y, path_length=0})while #cands > 0 dolocal cand = table.remove(cands, 1)local x,y, p = cand.x, cand.y, cand.path_lengthif x == dst.x and y == dst.y then return cand endif y >= 1 and y <= lh and x >= 1 and x <= lw and not done[y][x] thendone[y][x] = trueif is_assumed_empty(level, x,y, empty_cell, occupied_cell) thentable.insert(cands, {x=x-1, y=y, dir='left', prev=cand, path_length=p+1})table.insert(cands, {x=x+1, y=y, dir='right', prev=cand, path_length=p+1})table.insert(cands, {x=x, y=y-1, dir='up', prev=cand, path_length=p+1})table.insert(cands, {x=x, y=y+1, dir='down', prev=cand, path_length=p+1})end end end endfunction is_assumed_empty(level, x,y, empty, occupied)if empty.y ~= occupied.y or empty.x ~= occupied.x thenif x == empty.x and y == empty.y thenreturn trueendendif x == occupied.x and y == occupied.y thenreturn falseendreturn is_empty(level, x,y)endfunction unwind_steps_to_move_crate(c, out)-- insert steps in reverse orderwhile c dotable.insert(out, c.dir)unwind_steps(c.moves, out)c = c.prevendreturn outendfunction shortest_path(cands)--? Real_print('', #cands)local min, minscore = 1, cands[1].path_lengthfor i=2,#cands dolocal iscore = cands[i].path_length--? Real_print('', i, iscore, '<=>', min,minscore)if iscore < minscore thenmin, minscore = i, iscoreendendreturn minendfunction compute_cell(y, x, dir)if dir == 'up' thenreturn y-1, xelseif dir == 'down' thenreturn y+1, xelseif dir == 'left' thenreturn y, x-1elseif dir == 'right' thenreturn y, x+1end endfunction opposite_dir(dir)if dir == 'up' thenreturn 'down'elseif dir == 'down' thenreturn 'up'elseif dir == 'left' thenreturn 'right'elseif dir == 'right' thenreturn 'left'end end
if add_to_undo thentable.insert(undo_history, u)endendfunction move_to_empty_space(y, x, add_to_undo)local path = unwind_path(find_empty_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, y
endendfunction plan_move_to_empty_space(y, x)local path = find_empty_path(level_state, player, {y=y, x=x})if path == nil then return endpending_moves = unwind_steps(path, {})local src = level_state[player.y][player.x]local dest = level_state[y][x]local u = {{x=player.x, y=player.y, cell=src},{x=x, y=y, cell=dest}}table.insert(undo_history, u) -- add to undo without making move yetnext_pending_move = Current_timeendfunction plan_move_crate(y, x)local path = find_path_for_crate(level_state, player, crate_to_move, {y=y, x=x})if path == nil then return end--? Real_print('--')--? vdump_path(path)pending_moves = unwind_steps_to_move_crate(path, {})for i=#pending_moves,1,-1 do print(pending_moves[i]) endlocal src = level_state[player.y][player.x]local crate_cell = level_state[crate_to_move.y][crate_to_move.x]local dest = level_state[y][x]local u = {{x=player.x, y=player.y, cell=src},{x=crate_to_move.x, y=crate_to_move.y, cell=crate_cell},{x=x, y=y, cell=dest}}print('player initial', player.x, player.y, src)print('crate initial', crate_to_move.x, crate_to_move.y, crate_cell)print('crate final', x, y, dest)-- we also need to save the initial state of the final position of the playerlocal fpy,fpx = compute_cell(y,x, opposite_dir(pending_moves[1]))print('player final', fpx, fpy, level_state[fpy][fpx])table.insert(u, {x=fpx, y=fpy, cell=level_state[fpy][fpx]})table.insert(undo_history, u) -- add to undo without making move yetnext_pending_move = Current_timeendfunction car.update()if next_pending_move and Current_time >= next_pending_move thenlocal dir = table.remove(pending_moves)move(dir, --[[add to undo]] false)if #pending_moves > 0 thennext_pending_move = Current_time + 0.1endendendfunction make_all_pending_moves()while #pending_moves > 0 dolocal dir = table.remove(pending_moves)move(dir, --[[add to undo]] false)
function compute_cell(y, x, dir)if dir == 'up' thenreturn y-1, xelseif dir == 'down' thenreturn y+1, xelseif dir == 'left' thenreturn y, x-1elseif dir == 'right' thenreturn y, x+1end endfunction opposite_dir(dir)if dir == 'up' thenreturn 'down'elseif dir == 'down' thenreturn 'up'elseif dir == 'left' thenreturn 'right'elseif dir == 'right' thenreturn 'left'end end