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 do
local cand = table.remove(cands, 1)
local x,y = cand.x, cand.y
if x == dst.x and y == dst.y then return cand end
if y >= 1 and y <= lh and x >= 1 and x <= lw and not done[y][x] then
done[y][x] = true
local curr = level[y][x]
if curr ~= CELL_WALL and curr ~= CELL_CRATE and curr ~= CELL_CRATE_ON_TARGET then
end end end end
function is_empty(level, x,y)
local curr = level[y][x]
return curr ~= CELL_WALL and curr ~= CELL_CRATE and curr ~= CELL_CRATE_ON_TARGET
end
function unwind_path(c)
local result = {}
while c do
table.insert(result, {x=c.x, y=c.y})
c = c.prev
end
return result
end
function draw_path(path)
color(1,0,0)
for _,cell in ipairs(path) do
rect('line', left+(cell.x-1)*side, top+(cell.y-1)*side, side, side)
end end
function initialize_array(val, dim, ...)
local result = {}
local other_dims = {...}
if #other_dims == 0 then
for i=1,dim do
table.insert(result, val)
end
else
for i=1,dim do
table.insert(result, initialize_array(val, ...))
end
end
return result
end
function shortest_path(cands)
local min, minscore = 1, cands[1].path_length
for i=2,#cands do
local iscore = cands[i].path_length
if iscore < minscore then
min, minscore = i, iscore
end
end
return min
end
--? Real_print('', i, iscore, '<=>', min,minscore)
--? Real_print('', #cands)
function unwind_steps(c, out)
-- insert steps in reverse order
table.insert(out, c.dir)
end
return out
end
c = c.prev
while c do
table.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 says
local done = initialize_array(false, lh, lw)
local cands = {}
while #cands > 0 do
local cand = table.remove(cands, 1)
if x == dst.x and y == dst.y then return cand end
if y >= 1 and y <= lh and x >= 1 and x <= lw and not done[y][x] then
done[y][x] = true
end end end end
if empty.y ~= occupied.y or empty.x ~= occupied.x then
if x == empty.x and y == empty.y then
return true
end
end
if x == occupied.x and y == occupied.y then
return false
end
end
return is_empty(level, x,y)
function unwind_steps_to_move_crate(c, out)
-- insert steps in reverse order
while c do
table.insert(out, c.dir)
unwind_steps(c.moves, out)
c = c.prev
end
return out
end
function vdump_path(c)
if c == nil then return end
if not c.dir then
assert(not c.moves)
assert(not c.prev)
return
end
Real_print('p'..c.dir)
local c2 = c.moves
while c2 do
if c2.dir then
Real_print('m'..c2.dir)
end
c2 = c2.prev
end
vdump_path(c.prev)
end
end
function dump_path(c)
if c == nil then return end
if not c.dir then
assert(not c.moves)
assert(not c.prev)
io.stdout:write('\n')
return
end
local c2 = c.moves
while c2 do
if c2.dir then
end
c2 = c2.prev
end
dump_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) then
local x,y, p = cand.x, cand.y, cand.path_length
table.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) then
return
end
function draw_crate_to_move()
if crate_to_move == nil then return end
color(1,0,0)
local x,y = crate_to_move.x, crate_to_move.y
rect('line', left+(x-1)*side, top+(y-1)*side, side, side)
end
-- moving player without moving any crates
function find_empty_path(level, src, dst)
if not is_empty(level, dst.x, dst.y) then
return
end
-- moving crate without moving any other crates
function 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 do
if c.x == dst.x and c.y == dst.y then return cand end
if c.y >= 1 and c.y <= lh and c.x >= 1 and c.x <= lw and not done[c.y][c.x] then
done[c.y][c.x] = true
local curr = level[c.y][c.x]
-- try to push crate left, by first getting to the right of it
if moves or (p.x == c.x+1 and p.y == c.y) then
-- after the push, both player and crate have moved
end
-- try to push crate right
if moves or (p.x == c.x-1 and p.y == c.y) then
end
-- try to push crate up
if moves or (p.x == c.x and p.y == c.y+1) then
end
-- try to push crate down
if moves or (p.x == c.x and p.y == c.y-1) then
end
end end end end
table.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)) then
local 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 crates
function draw_crate_to_move()
if crate_to_move == nil then return end
color(1,0,0)
local x,y = crate_to_move.x, crate_to_move.y
rect('line', left+(x-1)*side, top+(y-1)*side, side, side)
end
function car.update()
if next_pending_move and Current_time >= next_pending_move then
local dir = table.remove(pending_moves)
move(dir, --[[add to undo]] false)
if #pending_moves > 0 then
next_pending_move = Current_time + 0.1
end
end
end
function make_all_pending_moves()
while #pending_moves > 0 do
local dir = table.remove(pending_moves)
move(dir, --[[add to undo]] false)
end
end
function plan_move_to_empty_space(y, x)
local path = find_empty_path(level_state, player, {y=y, x=x})
if path == nil then return end
pending_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 yet
next_pending_move = Current_time
end
function find_empty_path(level, src, dst)
if not is_empty(level, dst.x, dst.y) then
return
end
local done = initialize_array(false, lh, lw)
local cands = {}
table.insert(cands, {x=src.x, y=src.y})
while #cands > 0 do
local cand = table.remove(cands, 1)
local x,y = cand.x, cand.y
if x == dst.x and y == dst.y then return cand end
if y >= 1 and y <= lh and x >= 1 and x <= lw and not done[y][x] then
done[y][x] = true
local curr = level[y][x]
if curr ~= CELL_WALL and curr ~= CELL_CRATE and curr ~= CELL_CRATE_ON_TARGET then
table.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 end
function is_empty(level, x,y)
local curr = level[y][x]
return curr ~= CELL_WALL and curr ~= CELL_CRATE and curr ~= CELL_CRATE_ON_TARGET
end
function unwind_path(c)
local result = {}
while c do
table.insert(result, {x=c.x, y=c.y})
c = c.prev
end
return result
end
function unwind_steps(c, out)
-- insert steps in reverse order
while c do
table.insert(out, c.dir)
c = c.prev
end
return out
end
function initialize_array(val, dim, ...)
local result = {}
local other_dims = {...}
if #other_dims == 0 then
for i=1,dim do
table.insert(result, val)
end
else
for i=1,dim do
table.insert(result, initialize_array(val, ...))
end
end
return result
end
function dump_path(c)
if c == nil then return end
if not c.dir then
assert(not c.moves)
assert(not c.prev)
io.stdout:write('\n')
return
end
io.stdout:write('p'..c.dir..' ')
local c2 = c.moves
while c2 do
if c2.dir then
io.stdout:write('m'..c2.dir..' ')
end
c2 = c2.prev
end
dump_path(c.prev)
end
-- moving crate without moving any other crates
function 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]) end
local 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 player
local 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 yet
next_pending_move = Current_time
end
function find_path_for_crate(level, player, crate, dst)
local done = initialize_array(false, lh, lw)
local cands = {}
--? local i=0
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 do
local 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 end
if c.y >= 1 and c.y <= lh and c.x >= 1 and c.x <= lw and not done[c.y][c.x] then
done[c.y][c.x] = true
local 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 it
local 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 moved
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})
end
-- try to push crate right
moves = 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) then
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})
end
-- try to push crate up
local 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) then
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})
end
-- try to push crate down
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) then
table.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})
end
end 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 says
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) then
return
end
local 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 do
local cand = table.remove(cands, 1)
local x,y, p = cand.x, cand.y, cand.path_length
if x == dst.x and y == dst.y then return cand end
if y >= 1 and y <= lh and x >= 1 and x <= lw and not done[y][x] then
done[y][x] = true
if is_assumed_empty(level, x,y, empty_cell, occupied_cell) then
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})
end end end end
function is_assumed_empty(level, x,y, empty, occupied)
if empty.y ~= occupied.y or empty.x ~= occupied.x then
if x == empty.x and y == empty.y then
return true
end
end
if x == occupied.x and y == occupied.y then
return false
end
return is_empty(level, x,y)
end
function unwind_steps_to_move_crate(c, out)
-- insert steps in reverse order
while c do
table.insert(out, c.dir)
unwind_steps(c.moves, out)
c = c.prev
end
return out
end
function shortest_path(cands)
--? Real_print('', #cands)
local min, minscore = 1, cands[1].path_length
for i=2,#cands do
local iscore = cands[i].path_length
--? Real_print('', i, iscore, '<=>', min,minscore)
if iscore < minscore then
min, minscore = i, iscore
end
end
return min
end
function compute_cell(y, x, dir)
if dir == 'up' then
return y-1, x
elseif dir == 'down' then
return y+1, x
elseif dir == 'left' then
return y, x-1
elseif dir == 'right' then
return y, x+1
end end
function opposite_dir(dir)
if dir == 'up' then
return 'down'
elseif dir == 'down' then
return 'up'
elseif dir == 'left' then
return 'right'
elseif dir == 'right' then
return 'left'
end end
if add_to_undo then
table.insert(undo_history, u)
end
end
function 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 end
local 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 then
return
end
local 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
end
end
function plan_move_to_empty_space(y, x)
local path = find_empty_path(level_state, player, {y=y, x=x})
if path == nil then return end
pending_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 yet
next_pending_move = Current_time
end
function 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]) end
local 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 player
local 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 yet
next_pending_move = Current_time
end
function car.update()
if next_pending_move and Current_time >= next_pending_move then
local dir = table.remove(pending_moves)
move(dir, --[[add to undo]] false)
if #pending_moves > 0 then
next_pending_move = Current_time + 0.1
end
end
end
function make_all_pending_moves()
while #pending_moves > 0 do
local dir = table.remove(pending_moves)
move(dir, --[[add to undo]] false)
function compute_cell(y, x, dir)
if dir == 'up' then
return y-1, x
elseif dir == 'down' then
return y+1, x
elseif dir == 'left' then
return y, x-1
elseif dir == 'right' then
return y, x+1
end end
function opposite_dir(dir)
if dir == 'up' then
return 'down'
elseif dir == 'down' then
return 'up'
elseif dir == 'left' then
return 'right'
elseif dir == 'right' then
return 'left'
end end