-- This is the library used by all Sokoban levels -- Filename: ralf_sokoban.lua -- Copyright: (C) Mar 2003 Ralf Westram -- Contact: amgine@reallysoft.de -- License: GPL v2.0 or above dofile(enigma.FindDataFile("levels/ralf.lua")) --debug_mode() show_restricted = 0 -- set to 1 to use different floors for restricted areas (for debugging) start_solved = 0 -- set to 1 to start solved (for debugging) test=0 --if not difficult then -- test=1 -- set this to 1 for st-brake testing --end -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- characters allowed in level: -- -- '!' space outside level -- ' ' normalfloor -- '_' normalfloor (unreachable after pushing all boxes onto targets) -- '=' normalfloor (very likely a bad position for any adjacent oxyd) -- 'x' normalfloor (completely unreachable) -- '#' walls -- 'o' boxes -- 'O' boxes (on '_') -- '0' boxes (on '=') -- '.' targets -- '*' box on target -- '@' player -- '+' player (on target) -- 'a' player (on '_') -- 'A' player (on '=') -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- space_faces = { "fl-space", "fl-leaves", "fl-abyss", "fl-water", "fl-sahara" } space_faces2 = { "", "", "", "", "fl-rough-blue" } spacewalk = { 1, 1, 0, 0, 1, } -- do NOT change the number of elements! floor_faces = { "fl-bluegray", "fl-tigris", "fl-rough", "fl-samba", "fl-wood", "fl-himalaya", "fl-leaves" } wall_faces = { "st-bluegray", "st-rock6", "st-rock1", "st-rock5", "st-glass", "st-rock4", "st-rock3" } floor_faces2= { "fl-white", "fl-acblack", "fl-light", "", "", "fl-rough-red", "" } wall_faces2 = { "st-metal", "st-likeoxydc-open", "st-blue-sand", "", "", "st-glass", "" } box_faces = { "st-brownie", "st-marble_move", "st-wood", "st-shogun", "st-glass_move", "st-block" } box_faces2 = { "", "st-rock3_move", "st-wood-growing", "", "st-greenbrown_move", "" } if ((getn(floor_faces) ~= getn(floor_faces2)) or (getn(wall_faces) ~= getn(wall_faces2)) or (getn(box_faces) ~= getn(box_faces2)) or (getn(space_faces) ~= getn(space_faces2))) then error("wrong size in one of the face arrays") end door_faces = { "st-door_a", "st-door_b", "st-door_c" } door_faces2 = { "", "", "st-blocker" } oxyd_flavors = { "a", "b", "c", "d" } shogunLaser = 0 -- if set to 1 then lasers are used to activate oxyds (this only works with shogun stones) swapStyle = 1 -- if set to 0 then swap-stones are never used as boxes (otherwise swapStyle is selected randomly) invisible = 0 -- if set to 1 then boxes are invisible boxlikewall = 0 -- if set to 1 then boxes look like walls useSwapStyle = 1 -- local value of 'swapStyle' improve = 0 -- if set to 1 in level file -> always improve_solvability() scrolling=0 -- if set to 1 in level file -> always use free-scrolling reverse_scrolling=0 -- if set to 1 in level file -> if scrolling use reverse floor -- Symmetry information: xsymm = 0 ysymm = 0 psymm = 0 triggers = 0 doors = 0 lasers = 0 space_count = 0 -- count occurances of space (outside level) triggerstate = "" -- current state of the triggers shuffle = {} -- contains random permutation (to select trigger targets) shuffletrig = {} -- contains random permutation (to select triggers) position = {} -- positions of doors positions = 0 locked_door_triggered = 0 -- one of the doors is locked until ALL targets are covered locked_door = 1 boxx = {} boxy = {} function init_globals() triggers = 0 doors = 0 lasers = 0 space_count = 0 triggerstate = "" shuffle = {} shuffletrig = {} position = {} positions = 0 locked_target_triggered = 0 locked_target = 1 setboxes=0 currtestbox=1 game_lost=0 laser_flickered=0 end ----------------------------------------- function init_shuffle(targets) local set = {} for i=1,targets do set[i] = i end local left = targets for count=1,targets do local sel = random(1,left) shuffle[count] = set[sel] set[sel] = set[left] left = left-1 -- debug("shuffle["..count.."]="..shuffle[count]) end local settrig = {} for i=1,triggers do settrig[i] = i end left = triggers for count=1,triggers do local sel = random(1,left) shuffletrig[count] = settrig[sel] settrig[sel] = settrig[left] left = left-1 -- debug("shuffletrig["..count.."]="..shuffletrig[count]) end end function unflicker_laser() if (laser_flickered~=0) then SendMessage("laser"..laser_flickered, "on") laser_flickered=0 end end flicker_next_tries = {} function flicker_lasers() unflicker_laser() for retries=1,4 do local left = getn(flicker_next_tries) if (left==0) then flicker_next_tries = {} for all=1,lasers do flicker_next_tries[all] = all end left = lasers end local r = random(1,left) local sel = flicker_next_tries[r] flicker_next_tries[r] = flicker_next_tries[left] tremove(flicker_next_tries) if (sel ~= locked_target or not strfind(triggerstate,"0")) then local laser = enigma.GetNamedObject("laser"..sel) local state = GetAttrib(laser,"on") if (state==1) then laser_flickered=sel SendMessage(laser, "off") return end end end end function trig_doorlaser(which,is_door,targets) unflicker_laser() which = shuffletrig[which] local state = strsub(triggerstate,which,which) if (state == "0") then state = "1" else state = "0" end local trig_target = -1 if (targets == triggers) then -- 1 trigger <-> 1 door trig_target = which else local s0,s1 = 0,0 if (state=="1") then s1 = 1 else s0 = 1 end local w2 = which-targets local wlow = which while (w2 > 0) do local state2 = strsub(triggerstate,w2,w2) if (state2=="1") then s1 = s1+1 else s0 = s0+1 end wlow = w2 w2 = w2-targets end w2 = which+targets while (w2 <= triggers) do local state2 = strsub(triggerstate,w2,w2) if (state2=="1") then s1 = s1+1 else s0 = s0+1 end w2 = w2+targets end debug("s0="..s0.." s1="..s1.." wlow="..wlow) if ((s0==0) or (s0==1 and state=="0")) then trig_target = wlow end end triggerstate = strsub(triggerstate,1,which-1)..state..strsub(triggerstate,which+1) local all_covered = not strfind(triggerstate,"0") debug(triggerstate) debug("trig_target="..trig_target); if (trig_target ~= -1) then if (trig_target ~= locked_target) then if (is_door==1) then local msg="open" if (state=="0") then msg="close" end -- state is the NEW value SendMessage("door"..shuffle[trig_target], msg) else local msg="on" if (state=="0") then msg="off" end -- state is the NEW value SendMessage("laser"..shuffle[trig_target], msg) end end end if (all_covered) then if (locked_target_triggered==0) then if (is_door==1) then SendMessage("door"..shuffle[locked_target], "open") else SendMessage("laser"..shuffle[locked_target], "on") end locked_target_triggered = 1 end else if (locked_target_triggered==1) then if (is_door==1) then SendMessage("door"..shuffle[locked_target], "close") else SendMessage("laser"..shuffle[locked_target], "off") end locked_target_triggered = 0 end end debug("locked_target_triggered="..locked_target_triggered) end function trig_door(which) trig_doorlaser(which,1,doors) end function trig_laser(which) trig_doorlaser(which,0,lasers) end -- actors position acx = 0 acy = 0 function set_the_actor(x,y) acx,acy = x+.5,y+.5 end function set_soko_trigger(x,y) triggers = triggers + 1 local funcn = "trig_"..triggers if (shogunLaser==1) then dostring(funcn.." = function() trig_laser("..triggers..") end") else dostring(funcn.." = function() trig_door("..triggers..") end") end set_item(triggerface,x,y,{action="callback", target=funcn}) end function set_actor_on_trigger(x,y) set_the_actor(x,y) set_soko_trigger(x,y) end --spacemod = 2 function set_space_floor(x,y) set_floor(spaceface,x,y) end function set_glasswall(x,y) if (mod(x,2)==mod(y,2)) then set_floor(floorface,x,y) else set_space_floor(x,y) end set_stone("st-glass",x,y) end function set_spacecell(x,y) space_count = space_count+1 set_space_floor(x,y) end function set_box(x,y) setboxes=setboxes+1 local boxname = "box"..setboxes if (test==1) then set_stone("st-brake",x,y,{name=boxname}); elseif (boxface=="st-glass1" or boxface=="sokoban_box") then print("boxface="..boxface); set_stone(boxface,x,y,{movable=1,name=boxname}); else print("boxface="..boxface); set_stone(boxface,x,y,{name=boxname}) end end function init(num) init_globals() local first = 0 if (num == -1) then first = 1 num = 0 end spaceface = space_faces [mod(num, getn(space_faces))+1] spaceface2 = space_faces2[mod(num, getn(space_faces))+1] floorface = floor_faces [mod(num, getn(floor_faces))+1] floorface2 = floor_faces2[mod(num, getn(floor_faces))+1] wallface = wall_faces [mod(num, getn(wall_faces))+1] wallface2 = wall_faces2 [mod(num, getn(wall_faces))+1] boxface = box_faces [mod(num, getn(box_faces))+1] boxface2 = box_faces2 [mod(num, getn(box_faces))+1] doorface = door_faces [mod(num, getn(door_faces))+1] doorface2 = door_faces2 [mod(num, getn(door_faces))+1] if ((spaceface2 ~= "") and (mod(floor(num/getn(space_faces)),2)==1)) then spaceface,spaceface2 = spaceface2,spaceface end if ((floorface2 ~= "") and (mod(floor(num/(getn(floor_faces)*2)),2)==1)) then floorface,floorface2 = floorface2,floorface end if ((wallface2 ~= "") and (mod(floor(num/getn(wall_faces)),2)==1)) then wallface,wallface2 = wallface2,wallface end if ((boxface2 ~= "") and (mod(floor(num/getn(box_faces)),2)==1)) then boxface,boxface2 = boxface2,boxface end if ((doorface2 ~= "") and (mod(floor(num/getn(door_faces)),2)==1)) then doorface,doorface2 = doorface2,doorface end -- select style useSwapStyle=0 if (shogunLaser==1) then -- shogunLaser has been set from level file boxface = "st-shogun" --elseif (swapStyle==1) then -- if swapStyle is not forbidden -- local n=mod(num,17) -- if (n==10 or n==16) then -- 2 of 17 levels use swapStyle -- useSwapStyle=1 -- end elseif (swapStyle==2) then -- swapStyle forced useSwapStyle=1 end if (useSwapStyle==1) then if not difficult then useSwapStyle=0 else boxface="st-swap" end end if (reverse_scrolling==1 and not difficult) then floorface = "fl-inverse" end if (not difficult) then invisible=0 boxlikewall=0 end if (invisible==1) then world.DefineSimpleStoneMovable("sokoban_box", "stone", 0) display.DefineAlias("sokoban_box", "st-invisible") boxface="sokoban_box" end if (boxlikewall==1) then world.DefineSimpleStoneMovable("sokoban_box", "stone", 0) display.DefineAlias("sokoban_box", wallface) boxface="sokoban_box" end oxyd_default_flavor = oxyd_flavors[mod(num, getn(oxyd_flavors))+1] if (spaceface == floorface) then if (spaceface == "fl-leaves") then spaceface = "fl-water" else print("warning: [ralf_sokoban.lua]: no replacement texture defined for "..spaceface) end end if (boxface == "st-shogun") then triggerface = "it-shogun-s" else triggerface = "it-trigger" end cells={} stonecells={} if (useSwapStyle==1) then local grate_faces = { "st-grate1", "st-grate2", } cells[" "] = cell{floor={face=floorface},stone={face=grate_faces[mod(floor(num/8), getn(grate_faces))+1]}} if (show_restricted==1) then cells["_"] = cell{floor={face="fl-sand"},stone={face=grate_faces[mod(floor(num/8), getn(grate_faces))+1]}} cells["x"] = cell{floor={face="fl-ice"},stone={face=grate_faces[mod(floor(num/8), getn(grate_faces))+1]}} cells["="] = cell{floor={face="fl-red"},stone={face=grate_faces[mod(floor(num/8), getn(grate_faces))+1]}} end cells["#"] = cell{floor="fl-water"} else cells[" "] = cell{floor={face=floorface}} if (show_restricted==1) then cells["_"] = cell{floor={face="fl-sand"}} cells["x"] = cell{floor={face="fl-ice"}} cells["="] = cell{floor={face="fl-red"}} end if (wallface=="st-glass") then cells["#"] = cell{parent=set_glasswall} else cells["#"] = cell{parent = cells[" "], stone = {face = wallface}} end end cells["!"] = cell{parent = {set_spacecell}} if (show_restricted==0) then cells["_"] = cells[" "] cells["x"] = cells[" "] cells["="] = cells[" "] end cells["o"] = cells[" "] cells["O"] = cells["_"] cells["0"] = cells["="] cells["."] = cell{parent = {cells[" "], set_soko_trigger}} cells["@"] = cell{parent = {cells[" "], set_the_actor}} cells["a"] = cell{parent = {cells["_"], set_the_actor}} cells["A"] = cell{parent = {cells["="], set_the_actor}} cells["*"] = cells["."] cells["+"] = cell{parent = {cells[" "], set_actor_on_trigger}} stonecells["!"] = cell{} stonecells[" "] = cell{} stonecells["_"] = cell{} stonecells["x"] = cell{} stonecells["="] = cell{} stonecells["#"] = cell{} if (show_restricted==1) then stonecells["o"] = cell{floor={face=floorface},parent={set_box}} stonecells["O"] = cell{floor={face="fl-sand"},parent={set_box}} stonecells["0"] = cell{floor={face="fl-red"},parent={set_box}} else stonecells["o"] = cell{floor={face=floorface},parent={set_box}} stonecells["O"] = stonecells["o"] stonecells["0"] = stonecells["o"] end if (start_solved==1) then stonecells["."] = cell{floor={face=floorface},parent={set_box}} stonecells["*"] = stonecells["."] stonecells["o"] = cell{floor={face=floorface}} if (show_restricted==1) then stonecells["O"] = cell{floor={face="fl-sand"}} stonecells["0"] = cell{floor={face="fl-red"}} else stonecells["O"] = stonecells["o"] stonecells["0"] = stonecells["o"] end else stonecells["."] = cell{} stonecells["*"] = stonecells["o"] end stonecells["@"] = cell{} stonecells["a"] = cell{} stonecells["A"] = cell{} stonecells["+"] = cell{} end -- wether there's space outside the level spl = 0 spr = 0 spt = 0 spb = 0 spaceres = {} spaceres[0] = '-' -- means: don't use because it's outside the level spaceres[1] = '!' -- look at a specifix position of the level -- supports 1 position outside level into each direction function peek(x,y) if (x<1) then return spaceres[spl] end if (x>mapw) then return spaceres[spr] end if (y<1) then return spaceres[spt] end if (y>maph) then return spaceres[spb] end return strsub(level[y],x,x) end function rpeek(rx,ry) return peek(rx-xlo+1,ry-ylo+1) end function adjacent(x1,y1,x2,y2) -- returns TRUE if the two positions are adjacent local dx,dy = abs(x1-x2),abs(y1-y2) return (dx==0 and dy==1) or (dx==1 and dy==0) end -- locations of state[] indices: -- -- 3 -- 1 P 2 -- 4 xoff = { -1, 1, 0, 0 } yoff = { 0, 0, -1, 1 } function is_in_restricted_area(p) local xd,yd = position[p][1],position[p][2] local xo,yo = position[p][3],position[p][4] local xf,yf = xd+(xd-xo),yd+(yd-yo) -- debug("xf="..xf.." yf="..yf) local c = rpeek(xf,yf) if (c==' ' or c=='o' or c=='@') then return 0 end -- not restricted if (c=='_' or c=='O' or c=='a') then return 1 end -- restricted -> dont put final oxyd here if (c=='=' or c=='0' or c=='A') then return 1 end -- forbidden! return 2 -- somethings wrong! end --function symmetric_position(x,y,w,h) -- if (xsymm==1) then return w-(x-xlo)+xlo,y end -- if (ysymm==1) then return x,h-(y-ylo)+ylo end -- if (psymm==1) then return h-(y-ylo)+xlo,x-xlo+ylo end -- return x,y --end function symmetric_position(x,y,w,h) if (xsymm==1) then return w-x+1,y end if (ysymm==1) then return x,h-y+1 end if (psymm==1) then return h-y+1,x end return x,y end removed_indices={} removed = 0 function rs_remove_oxyd(p,w,h) removed_indices={} removed = 0 local x1,y1 = position[p][1],position[p][2] if (xsymm+ysymm+psymm) then local symmcount = 1 if (psymm==1) then symmcount=3 end for c=1,symmcount do local x2,y2 = symmetric_position(x1,y1,w,h) for q=1,positions do if ((q~=p) and (x2==position[q][1]) and (y2==position[q][2])) then tremove(position,q) removed = removed+1 removed_indices[removed] = q positions = positions-1 if (q
0) or (shogunLaser==1)) and (spacehere>0) and (forbidden==0)) then
positions = positions+1
position[positions] = {x,y,x+xoff[spacehere], y+yoff[spacehere]}
end
end
end
end
-- delete adjacent oxyds and adjacent doors
-- [first those with two neighbours or more neighbours, then those with one neighbour]
for delif=2,1,-1 do
local deleted = 1
while (deleted==1) do
deleted = 0
for p=1,positions do
local xd1,yd1 = position[p][1],position[p][2]
local xo1,yo1 = position[p][3],position[p][4]
local oneighbours = 0
local dneighbours = 0
for q=1,positions do
if (p~=q) then
local xd2,yd2 = position[q][1],position[q][2]
local xo2,yo2 = position[q][3],position[q][4]
if (xo1==xo2 and yo1==yo2) then -- oxyds use same position
oneighbours = delif
break
elseif (adjacent(xo1,yo1,xo2,yo2)) then
oneighbours = oneighbours+1
if (oneighbours==delif) then break end
end
if (adjacent(xd1,yd1,xd2,yd2)) then
dneighbours = dneighbours+1
if (dneighbours==delif) then break end
end
end
end
if ((oneighbours>=delif) or (dneighbours>=delif)) then
rs_remove_oxyd(p,w,h)
deleted = 1
break
end
end
end
end
-- delete superfluous oxyds
local want_remove = 0
if (maxoxyds>triggers) then maxoxyds = triggers end
if (maxoxyds>16) then maxoxyds = 16 end
if (positions>maxoxyds) then
want_remove = positions-maxoxyds
debug("maxoxyds="..maxoxyds.." want_remove="..want_remove);
end
if (mod(positions-want_remove,2)==1) then want_remove = want_remove+1 end
debug("positions="..positions.." want_remove="..want_remove);
if ((psymm+xsymm+ysymm)>0) then
local isSymmetric = {}
local sym_count = 0
local sym_count_single = 0
local per_remove = 0
for p=1,positions do isSymmetric[p] = 0 end
if (psymm==1) then -- central symmetry
per_remove=4
for p=1,positions do
if (isSymmetric[p]==0) then
local x1,y1 = position[p][1],position[p][2]
local allFound = 1
local index={}
for r=1,3 do
local x2,y2 = symmetric_position(x1,y1,w,h)
local found = 0
for q=1,positions do
if (p~=q and position[q][1]==x2 and position[q][2]==y2) then
local inUse=0
for r2=1,r-1 do
if (index[r2]==q) then inUse=1 break end
end
if (inUse==0) then
found=1
index[r]=q
break
end
end
end
if (found==0) then allFound = 0 break end
x1,y1 = x2,y2
end
if (allFound==1) then
isSymmetric[p]=1
for r=1,3 do isSymmetric[index[r]]=1 end
sym_count = sym_count+4
local array=""
for p=1,positions do array=array..isSymmetric[p] end
debug("isSymmetric="..array);
end
end
end
else
per_remove=2
for p=1,positions do
if (isSymmetric[p]==0) then
local x1,y1 = position[p][1],position[p][2]
local x2,y2 = symmetric_position(x1,y1,w,h)
if (x1==x2 and y1==y2) then
isSymmetric[p] = 2
sym_count_single = sym_count_single+1
else
for q=1,positions do
if (p~=q and position[q][1]==x2 and position[q][2]==y2) then
isSymmetric[p] = 1
isSymmetric[q] = 1
sym_count = sym_count+2
break
end
end
end
end
end
end
local assym_count = positions-(sym_count+sym_count_single)
-- debug("positions="..positions.." sym_count="..sym_count.." sym_count_single="..sym_count_single.." assym_count="..assym_count);
-- first remove assymetric positions
if (assym_count>0 and want_remove>0) then
for p=positions,1,-1 do
if (isSymmetric[p]==0) then
tremove(position,p);
tremove(isSymmetric,p);
want_remove = want_remove-1
positions = positions-1
assym_count = assym_count-1
if (want_remove==0) then break end
end
end
end
-- then remove some symmetric positions
while (want_remove>=per_remove) do
if (sym_count