home *** CD-ROM | disk | FTP | other *** search
/ GameStar Special 2004 August / GSSH0804.iso / Geschicklichkeit / Enigma / Enigma-081.exe / data / levels / ralf_sokoban.lua < prev    next >
Text File  |  2003-08-16  |  34KB  |  1,139 lines

  1. -- This is the library used by all Sokoban levels
  2. -- Filename:     ralf_sokoban.lua
  3. -- Copyright:     (C) Mar 2003 Ralf Westram
  4. -- Contact:     amgine@reallysoft.de
  5. -- License:     GPL v2.0 or above
  6.  
  7. dofile(enigma.FindDataFile("levels/ralf.lua"))
  8.  
  9. --debug_mode()
  10.  
  11. test=0
  12. --if (options.Difficulty==1) then
  13. --   test=1 -- set this to 1 for st-brake testing
  14. --end
  15.  
  16. -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
  17. -- characters allowed in level:
  18. --
  19. -- '!'      space outside level
  20. -- ' '      normalfloor
  21. -- '_'      normalfloor (unreachable after pushing all boxes onto targets)
  22. -- 'x'      normalfloor (completely unreachable)
  23. -- '#'      walls
  24. -- 'o'      boxes
  25. -- '.'      targets
  26. -- '*'      box on target
  27. -- '@'      player
  28. -- '+'      player (on target)
  29.  
  30. -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
  31.  
  32. space_faces  = { "fl-space", "fl-leaves", "fl-abyss", "fl-water",  "fl-sahara" }
  33. space_faces2 = { "",         "",          "",         "",          "fl-rough-blue" }
  34. spacewalk    = {  1,          1,           0,          0,           1,         }
  35.  
  36. -- do NOT change the number of elements!
  37. floor_faces = { "fl-bluegray",  "fl-tigris",         "fl-rough",     "fl-samba", "fl-wood",  "fl-himalaya",  "fl-leaves" }
  38. wall_faces  = { "st-bluegray",  "st-rock6",          "st-rock1",     "st-rock5", "st-glass", "st-rock4",     "st-rock3"  }
  39. floor_faces2= { "fl-white",  "fl-acblack",        "fl-light",     "",         "",         "fl-rough-red", ""          }
  40. wall_faces2 = { "st-metal",     "st-likeoxydc-open", "st-blue-sand", "",         "",         "st-glass",     ""          }
  41.  
  42. box_faces  = { "st-brownie",  "st-marble_move", "st-wood",         "st-shogun", "st-glass_move",      "st-block" }
  43. box_faces2 = { "",            "st-rock3_move",  "st-wood-growing", "",          "st-greenbrown_move", ""         }
  44.  
  45. if ((getn(floor_faces) ~= getn(floor_faces2))
  46.     or (getn(wall_faces) ~= getn(wall_faces2))
  47.     or (getn(box_faces) ~= getn(box_faces2))
  48.     or (getn(space_faces) ~= getn(space_faces2)))
  49. then
  50.    error("wrong size in one of the face arrays")
  51. end
  52.  
  53. door_faces = { "st-door_a", "st-door_b", "st-door_c" }
  54. door_faces2 = { "", "", "st-blocker" }
  55. oxyd_flavors = { "a", "b", "c", "d" }
  56.  
  57. shogunLaser = 0 -- if set to 1 then lasers are used to activate oxyds (this only works with shogun stones)
  58. swapStyle = 1 -- if set to 0 then  swap-stones are never used as boxes (otherwise swapStyle is selected randomly)
  59. useSwapStyle = 1 -- local value of 'swapStyle'
  60.  
  61. -- Symmetry information:
  62. xsymm = 0
  63. ysymm = 0
  64. psymm = 0
  65.  
  66. triggers = 0
  67. doors = 0
  68. lasers = 0
  69.  
  70. space_count = 0 -- count occurances of space (outside level)
  71.  
  72. triggerstate = "" -- current state of the triggers
  73. shuffle = {} -- contains random permutation (to select trigger targets)
  74. shuffletrig = {} -- contains random permutation (to select triggers)
  75.  
  76. position = {} -- positions of doors
  77. positions = 0
  78.  
  79. locked_door_triggered = 0 -- one of the doors is locked until ALL targets are covered
  80. locked_door = 1
  81.  
  82. boxx = {}
  83. boxy = {}
  84.  
  85. function init_globals()
  86.    triggers = 0
  87.    doors = 0
  88.    lasers = 0
  89.    space_count = 0
  90.    triggerstate = ""
  91.    shuffle = {}
  92.    shuffletrig = {}
  93.    position = {}
  94.    positions = 0
  95.    locked_target_triggered = 0
  96.    locked_target = 1
  97.    setboxes=0
  98.    currtestbox=1
  99.    game_lost=0
  100.    laser_flickered=0
  101. end
  102.  
  103. -----------------------------------------
  104.  
  105. function init_shuffle(targets)
  106.    local set = {}
  107.    for i=1,targets do set[i] = i end
  108.  
  109.    local left = targets
  110.    for count=1,targets do
  111.       local sel = random(1,left)
  112.       shuffle[count] = set[sel]
  113.       set[sel] = set[left]
  114.       left = left-1
  115. --      debug("shuffle["..count.."]="..shuffle[count])
  116.    end
  117.  
  118.    local settrig = {}
  119.    for i=1,triggers do settrig[i] = i end
  120.  
  121.    left = triggers
  122.    for count=1,triggers do
  123.       local sel = random(1,left)
  124.       shuffletrig[count] = settrig[sel]
  125.       settrig[sel] = settrig[left]
  126.       left = left-1
  127. --      debug("shuffletrig["..count.."]="..shuffletrig[count])
  128.    end
  129. end
  130.  
  131. function unflicker_laser()
  132.    if (laser_flickered~=0) then
  133.       enigma.SendMessage(enigma.GetNamedObject("laser"..laser_flickered), "on", nil)
  134.       laser_flickered=0
  135.    end
  136. end
  137. function flicker_lasers()
  138.    unflicker_laser()
  139.    local sel = random(1,lasers)
  140.    if (sel ~= locked_target or not strfind(triggerstate,"0")) then
  141.       local laser = enigma.GetNamedObject("laser"..sel)
  142.       local state = GetAttrib(laser,"on")
  143.  
  144.       if (state==1) then
  145.          laser_flickered=sel
  146.          enigma.SendMessage(laser, "off", nil)
  147.       end
  148.    end
  149. end
  150.  
  151.  
  152. function trig_doorlaser(which,is_door,targets)
  153.    unflicker_laser()
  154.    which = shuffletrig[which]
  155.    local state = strsub(triggerstate,which,which)
  156.    if (state == "0") then state = "1" else state = "0" end
  157.  
  158.    local trig_target = -1
  159.    if (targets == triggers) then -- 1 trigger <-> 1 door
  160.       trig_target = which
  161.    else
  162.       local s0,s1 = 0,0
  163.       if (state=="1") then s1 = 1 else s0 = 1 end
  164.  
  165.       local w2 = which-targets
  166.       local wlow = which
  167.       while (w2 > 0) do
  168.          local state2 = strsub(triggerstate,w2,w2)
  169.          if (state2=="1") then s1 = s1+1 else s0 = s0+1 end
  170.          wlow = w2
  171.          w2 = w2-targets
  172.       end
  173.       w2 = which+targets
  174.       while (w2 <= triggers) do
  175.          local state2 = strsub(triggerstate,w2,w2)
  176.          if (state2=="1") then s1 = s1+1 else s0 = s0+1 end
  177.          w2 = w2+targets
  178.       end
  179.  
  180.       debug("s0="..s0.." s1="..s1.." wlow="..wlow)
  181.  
  182.       if ((s0==0) or (s0==1 and state=="0")) then
  183.          trig_target = wlow
  184.       end
  185.    end
  186.  
  187.    triggerstate = strsub(triggerstate,1,which-1)..state..strsub(triggerstate,which+1)
  188.    local all_covered = not strfind(triggerstate,"0")
  189.    debug(triggerstate)
  190.    debug("trig_target="..trig_target);
  191.  
  192.    if (trig_target ~= -1) then
  193.       if (trig_target ~= locked_target) then
  194.          if (is_door==1) then
  195.             local msg="open"
  196.             if (state=="0") then msg="close" end -- state is the NEW value
  197.             enigma.SendMessage(enigma.GetNamedObject("door"..shuffle[trig_target]), msg, nil)
  198.          else
  199.             local msg="on"
  200.             if (state=="0") then msg="off" end -- state is the NEW value
  201.             enigma.SendMessage(enigma.GetNamedObject("laser"..shuffle[trig_target]), msg, nil)
  202.          end
  203.       end
  204.    end
  205.  
  206.    if (all_covered) then
  207.       if (locked_target_triggered==0) then
  208.          if (is_door==1) then
  209.             enigma.SendMessage(enigma.GetNamedObject("door"..shuffle[locked_target]), "open", nil)
  210.          else
  211.             enigma.SendMessage(enigma.GetNamedObject("laser"..shuffle[locked_target]), "on", nil)
  212.          end
  213.          locked_target_triggered = 1
  214.       end
  215.    else
  216.       if (locked_target_triggered==1) then
  217.          if (is_door==1) then
  218.             enigma.SendMessage(enigma.GetNamedObject("door"..shuffle[locked_target]), "close", nil)
  219.          else
  220.             enigma.SendMessage(enigma.GetNamedObject("laser"..shuffle[locked_target]), "off", nil)
  221.          end
  222.          locked_target_triggered = 0
  223.       end
  224.    end
  225.    debug("locked_target_triggered="..locked_target_triggered)
  226. end
  227.  
  228. function trig_door(which)
  229.    trig_doorlaser(which,1,doors)
  230. end
  231. function trig_laser(which)
  232.    trig_doorlaser(which,0,lasers)
  233. end
  234.  
  235. -- actors position
  236. acx = 0
  237. acy = 0
  238.  
  239. function set_the_actor(x,y)
  240.    acx,acy = x+.5,y+.5
  241. end
  242.  
  243. function set_soko_trigger(x,y)
  244.    triggers = triggers + 1
  245.    local funcn = "trig_"..triggers
  246.    if (shogunLaser==1) then
  247.       dostring(funcn.." = function() trig_laser("..triggers..") end")
  248.    else
  249.       dostring(funcn.." = function() trig_door("..triggers..") end")
  250.    end
  251.    set_item(triggerface,x,y,{action="callback", target=funcn})
  252. end
  253.  
  254. function set_actor_on_trigger(x,y)
  255.    set_the_actor(x,y)
  256.    set_soko_trigger(x,y)
  257. end
  258.  
  259. --spacemod = 2
  260.  
  261. function set_space_floor(x,y)
  262.    set_floor(spaceface,x,y)
  263. end
  264.  
  265. function set_glasswall(x,y)
  266.    if (mod(x,2)==mod(y,2)) then
  267.       set_floor(floorface,x,y)
  268.    else
  269.       set_space_floor(x,y)
  270.    end
  271.    set_stone("st-glass",x,y)
  272. end
  273.  
  274. function set_spacecell(x,y)
  275.    space_count = space_count+1
  276.    set_space_floor(x,y)
  277. end
  278.  
  279. function set_box(x,y)
  280.    setboxes=setboxes+1
  281.    local boxname = "box"..setboxes
  282.    if (test==1) then
  283.       set_stone("st-brake",x,y,{name=boxname});
  284.    elseif (boxface=="st-glass1") then
  285.       set_stone(boxface,x,y,{movable=1,name=boxname});
  286.    else
  287.       set_stone(boxface,x,y,{name=boxname});
  288.    end
  289. end
  290.  
  291. function init(num)
  292.    init_globals()
  293.  
  294.    local first = 0
  295.    if (num == -1) then
  296.       first = 1
  297.       num = 0
  298.    end
  299.  
  300.    spaceface  = space_faces [mod(num, getn(space_faces))+1]
  301.    spaceface2 = space_faces2[mod(num, getn(space_faces))+1]
  302.    floorface  = floor_faces [mod(num, getn(floor_faces))+1]
  303.    floorface2 = floor_faces2[mod(num, getn(floor_faces))+1]
  304.    wallface   = wall_faces  [mod(num, getn(wall_faces))+1]
  305.    wallface2  = wall_faces2 [mod(num, getn(wall_faces))+1]
  306.    boxface    = box_faces   [mod(num, getn(box_faces))+1]
  307.    boxface2   = box_faces2  [mod(num, getn(box_faces))+1]
  308.    doorface   = door_faces  [mod(num, getn(door_faces))+1]
  309.    doorface2  = door_faces2 [mod(num, getn(door_faces))+1]
  310.  
  311.    if ((spaceface2 ~= "") and (mod(floor(num/getn(space_faces)),2)==1)) then
  312.       spaceface,spaceface2 = spaceface2,spaceface
  313.    end
  314.    if ((floorface2 ~= "") and (mod(floor(num/(getn(floor_faces)*2)),2)==1)) then
  315.       floorface,floorface2 = floorface2,floorface
  316.    end
  317.    if ((wallface2 ~= "") and (mod(floor(num/getn(wall_faces)),2)==1)) then
  318.       wallface,wallface2 = wallface2,wallface
  319.    end
  320.    if ((boxface2 ~= "") and (mod(floor(num/getn(box_faces)),2)==1)) then
  321.       boxface,boxface2 = boxface2,boxface
  322.    end
  323.    if ((doorface2 ~= "") and (mod(floor(num/getn(door_faces)),2)==1)) then
  324.       doorface,doorface2 = doorface2,doorface
  325.    end
  326.  
  327.    -- select style
  328.    useSwapStyle=0
  329.    if (shogunLaser==1) then -- shogunLaser has been set from level file
  330.       boxface = "st-shogun"
  331.    elseif (swapStyle==1) then -- if swapStyle is not forbidden
  332.       local n=mod(num,17)
  333.       if (n==10 or n==16) then -- 2 of 17 levels use swapStyle
  334.          useSwapStyle=1
  335.       end
  336.    elseif (swapStyle==2) then -- swapStyle forced
  337.       useSwapStyle=1
  338.    end
  339.  
  340.    if (useSwapStyle==1) then
  341.       if (options.Difficulty==1) then -- easy
  342.          useSwapStyle=0
  343.       else
  344.          boxface="st-swap"
  345.       end
  346.    end
  347.  
  348.  
  349.    oxyd_default_flavor = oxyd_flavors[mod(num, getn(oxyd_flavors))+1]
  350.  
  351.    if (spaceface == floorface) then
  352.       if (spaceface == "fl-leaves") then
  353.          spaceface = "fl-water"
  354.       else
  355.          print("warning: [ralf_sokoban.lua]: no replacement texture defined for "..spaceface)
  356.       end
  357.    end
  358.  
  359.    if (boxface == "st-shogun") then triggerface = "it-shogun"
  360.    else                             triggerface = "it-trigger"
  361.    end
  362.  
  363.    cells={}
  364.    stonecells={}
  365.  
  366.    if (useSwapStyle==1) then
  367.       local grate_faces = { "st-grate1", "st-grate2", }
  368.       cells[" "] = cell{floor={face=floorface},stone={face=grate_faces[mod(floor(num/8), getn(grate_faces))+1]}}
  369.       cells["#"] = cell{floor="fl-water"}
  370.    else
  371.       cells[" "] = cell{floor={face=floorface}}
  372.       if (wallface=="st-glass") then
  373.          cells["#"] = cell{parent=set_glasswall}
  374.       else
  375.          cells["#"] = cell{parent = cells[" "], stone = {face = wallface}}
  376.       end
  377.    end
  378.  
  379.    cells["!"] = cell{parent = {set_spacecell}}
  380.    cells["_"] = cells[" "]
  381.    cells["x"] = cells[" "]
  382.  
  383.    cells["o"] = cells[" "]
  384.    cells["."] = cell{parent = {cells[" "], set_soko_trigger}}
  385.    cells["@"] = cell{parent = {cells[" "], set_the_actor}}
  386.  
  387.    cells["*"] = cells["."]
  388.    cells["+"] = cell{parent = {cells[" "], set_actor_on_trigger}}
  389.  
  390.    stonecells["!"] = cell{}
  391.    stonecells[" "] = cell{}
  392.    stonecells["_"] = cell{}
  393.    stonecells["x"] = cell{}
  394.    stonecells["#"] = cell{}
  395.    stonecells["o"] = cell{floor={face=floorface},parent={set_box}}
  396.    stonecells["."] = cell{}
  397.    stonecells["@"] = cell{}
  398.    stonecells["*"] = stonecells["o"]
  399.    stonecells["+"] = cell{}
  400.  
  401. end
  402.  
  403. -- wether there's space outside the level
  404. spl = 0
  405. spr = 0
  406. spt = 0
  407. spb = 0
  408.  
  409. spaceres = {}
  410. spaceres[0] = '-' -- means: don't use because it's outside the level
  411. spaceres[1] = '!'
  412.  
  413. -- look at a specifix position of the level
  414. -- supports 1 position outside level into each direction
  415. function peek(x,y)
  416.    if (x<1)    then return spaceres[spl] end
  417.    if (x>mapw) then return spaceres[spr] end
  418.    if (y<1)    then return spaceres[spt] end
  419.    if (y>maph) then return spaceres[spb] end
  420.  
  421.    return strsub(level[y],x,x)
  422. end
  423.  
  424. function rpeek(rx,ry)
  425.    return peek(rx-xlo+1,ry-ylo+1)
  426. end
  427.  
  428. function adjacent(x1,y1,x2,y2) -- returns TRUE if the two positions are adjacent
  429.    local dx,dy = abs(x1-x2),abs(y1-y2)
  430.    return (dx==0 and dy==1) or (dx==1 and dy==0)
  431. end
  432.  
  433. -- locations of state[] indices:
  434. --
  435. --         3
  436. --       1 P 2
  437. --         4
  438.  
  439. xoff = { -1,  1,  0,  0 }
  440. yoff = {  0,  0, -1,  1 }
  441.  
  442. function is_in_restricted_area(p)
  443.    local xd,yd = position[p][1],position[p][2]
  444.    local xo,yo = position[p][3],position[p][4]
  445.    local xf,yf = xd+(xd-xo),yd+(yd-yo)
  446.  
  447.    debug("xf="..xf.." yf="..yf)
  448.  
  449.    local c = rpeek(xf,yf)
  450.    if (c==' ') then return 0 end
  451.    if (c=='_') then return 1 end
  452.  
  453.    return 2 -- unknown
  454. end
  455.  
  456. --function symmetric_position(x,y,w,h)
  457. --   if (xsymm==1) then return w-(x-xlo)+xlo,y end
  458. --   if (ysymm==1) then return x,h-(y-ylo)+ylo end
  459. --   if (psymm==1) then return h-(y-ylo)+xlo,x-xlo+ylo end
  460. --   return x,y
  461. --end
  462.  
  463. function symmetric_position(x,y,w,h)
  464.    if (xsymm==1) then return w-x+1,y end
  465.    if (ysymm==1) then return x,h-y+1 end
  466.    if (psymm==1) then return h-y+1,x end
  467.    return x,y
  468. end
  469.  
  470. removed_indices={}
  471. removed = 0
  472.  
  473. function rs_remove_oxyd(p,w,h)
  474.    removed_indices={}
  475.    removed = 0
  476.    local x1,y1 = position[p][1],position[p][2]
  477.    if (xsymm+ysymm+psymm) then
  478.       local symmcount = 1
  479.       if (psymm==1) then symmcount=3 end
  480.       for c=1,symmcount do
  481.          local x2,y2 = symmetric_position(x1,y1,w,h)
  482.          for q=1,positions do
  483.             if ((q~=p) and (x2==position[q][1]) and (y2==position[q][2])) then
  484.                tremove(position,q)
  485.                removed = removed+1
  486.                removed_indices[removed] = q
  487.                positions = positions-1
  488.                if (q<p) then p = p-1 end
  489.                break
  490.             end
  491.          end
  492.          x1,y1 = x2,y2
  493.       end
  494.    end
  495.  
  496.    tremove(position,p)
  497.    removed = removed+1
  498.    removed_indices[removed] = p
  499.    positions = positions-1
  500. end
  501.  
  502. maxoxyds = 999
  503.  
  504. function install_oxyds(w,h) -- uses Nat's method (see nat7.lua)
  505.    for x=1,w do
  506.       for y=1,h do
  507.          if (peek(x,y)=='#') then
  508.             local state = {}
  509.             state[1] = peek(x-1,y)
  510.             state[2] = peek(x+1,y)
  511.             state[3] = peek(x,y-1)
  512.             state[4] = peek(x,y+1)
  513.  
  514.             local reachable = 0
  515.             local spacehere = 0
  516.             for n=1,4 do
  517.                if (state[n]=='!') then
  518.                   if (spacehere == 0) then
  519.                      spacehere = n
  520.                   else
  521.                      -- positions with more than 1 adjacent space should never be used as doors
  522.                      -- (because you may escape there)
  523.                      if (shogunLaser==0) then
  524.                         spacehere = -1
  525.                      else -- does not matter in shogunLaser-mode
  526.                         -- ensure symmetry
  527.                         if (xsymm==1 and (x==(w-x+1))) then
  528.                            spacehere = n -- use oxyd in y direction
  529.                         end
  530.                      end
  531.                   end
  532.                end
  533.  
  534.                if (state[n]=='o' or state[n]==' ' or state[n]=='@' or state[n]=='_') then
  535.                   if (reachable == 0) then
  536.                      reachable = n
  537.                   else
  538.                      -- positions with more than 1 adjacent floor should never be used as doors
  539.                      -- (because they change the level logic)
  540.                      reachable = -1
  541.                   end
  542.                end
  543.             end
  544.  
  545.             if (((reachable>0) or (shogunLaser==1)) and (spacehere>0)) then
  546.                positions = positions+1
  547.                position[positions] = {x,y,x+xoff[spacehere], y+yoff[spacehere]}
  548.             end
  549.          end
  550.       end
  551.    end
  552.  
  553.    -- delete adjacent oxyds and adjacent doors
  554.    -- [first those with two neighbours or more neighbours, then those with one neighbour]
  555.    for delif=2,1,-1 do
  556.       local deleted = 1
  557.       while (deleted==1) do
  558.          deleted = 0
  559.          for p=1,positions do
  560.             local xd1,yd1 = position[p][1],position[p][2]
  561.             local xo1,yo1 = position[p][3],position[p][4]
  562.             local oneighbours = 0
  563.             local dneighbours = 0
  564.             for q=1,positions do
  565.                if (p~=q) then
  566.                   local xd2,yd2 = position[q][1],position[q][2]
  567.                   local xo2,yo2 = position[q][3],position[q][4]
  568.  
  569.                   if (xo1==xo2 and yo1==yo2) then -- oxyds use same position
  570.                      oneighbours = delif
  571.                      break
  572.                   elseif (adjacent(xo1,yo1,xo2,yo2)) then
  573.                      oneighbours = oneighbours+1
  574.                      if (oneighbours==delif) then break end
  575.                   end
  576.  
  577.                   if (adjacent(xd1,yd1,xd2,yd2)) then
  578.                      dneighbours = dneighbours+1
  579.                      if (dneighbours==delif) then break end
  580.                   end
  581.                end
  582.             end
  583.             if ((oneighbours>=delif) or (dneighbours>=delif)) then
  584.                rs_remove_oxyd(p,w,h)
  585.                deleted = 1
  586.                break
  587.             end
  588.          end
  589.       end
  590.    end
  591.  
  592.    -- delete superfluous oxyds
  593.    local want_remove = 0
  594.  
  595.    if (maxoxyds>triggers) then maxoxyds = triggers end
  596.  
  597.    if (positions>maxoxyds) then
  598.       want_remove = positions-maxoxyds
  599.       debug("maxoxyds="..maxoxyds.." want_remove="..want_remove);
  600.    end
  601.  
  602.    if (mod(positions-want_remove,2)==1) then want_remove = want_remove+1 end
  603.  
  604.    debug("positions="..positions.." want_remove="..want_remove);
  605.  
  606.    if ((psymm+xsymm+ysymm)>0) then
  607.       local isSymmetric = {}
  608.       local sym_count = 0
  609.       local sym_count_single = 0
  610.       local per_remove = 0
  611.  
  612.       for p=1,positions do isSymmetric[p] = 0 end
  613.       if (psymm==1) then -- central symmetry
  614.          per_remove=4
  615.          for p=1,positions do
  616.             if (isSymmetric[p]==0) then
  617.                local x1,y1 = position[p][1],position[p][2]
  618.                local allFound = 1
  619.                local index={}
  620.                for r=1,3 do
  621.                   local x2,y2 = symmetric_position(x1,y1,w,h)
  622.                   local found = 0
  623.                   for q=1,positions do
  624.                      if (p~=q and position[q][1]==x2 and position[q][2]==y2) then
  625.                         local inUse=0
  626.                         for r2=1,r-1 do
  627.                            if (index[r2]==q) then inUse=1 break end
  628.                         end
  629.                         if (inUse==0) then
  630.                            found=1
  631.                            index[r]=q
  632.                            break
  633.                         end
  634.                      end
  635.                   end
  636.                   if (found==0) then allFound = 0 break end
  637.                   x1,y1 = x2,y2
  638.                end
  639.                if (allFound==1) then
  640.                   isSymmetric[p]=1
  641.                   for r=1,3 do isSymmetric[index[r]]=1 end
  642.                   sym_count = sym_count+4
  643.                   local array=""
  644.                   for p=1,positions do array=array..isSymmetric[p] end
  645.                   debug("isSymmetric="..array);
  646.                end
  647.             end
  648.          end
  649.       else
  650.          per_remove=2
  651.          for p=1,positions do
  652.             if (isSymmetric[p]==0) then
  653.                local x1,y1 = position[p][1],position[p][2]
  654.                local x2,y2 = symmetric_position(x1,y1,w,h)
  655.  
  656.                if (x1==x2 and y1==y2) then
  657.                   isSymmetric[p] = 2
  658.                   sym_count_single = sym_count_single+1
  659.                else
  660.                   for q=1,positions do
  661.                      if (p~=q and position[q][1]==x2 and position[q][2]==y2) then
  662.                         isSymmetric[p] = 1
  663.                         isSymmetric[q] = 1
  664.                         sym_count = sym_count+2
  665.                         break
  666.                      end
  667.                   end
  668.                end
  669.             end
  670.          end
  671.       end
  672.  
  673.       local assym_count = positions-(sym_count+sym_count_single)
  674.  
  675.       -- debug("positions="..positions.." sym_count="..sym_count.." sym_count_single="..sym_count_single.." assym_count="..assym_count);
  676.  
  677.       -- first remove assymetric positions
  678.       if (assym_count>0 and want_remove>0) then
  679.          for p=positions,1,-1 do
  680.             if (isSymmetric[p]==0) then
  681.                tremove(position,p);
  682.                tremove(isSymmetric,p);
  683.                want_remove = want_remove-1
  684.                positions = positions-1
  685.                assym_count = assym_count-1
  686.  
  687.                if (want_remove==0) then break end
  688.             end
  689.          end
  690.       end
  691.  
  692.       -- then remove some symmetric positions
  693.       while (want_remove>=per_remove) do
  694.          if (sym_count<per_remove) then error("internal counter error") end
  695.          local todel = 0
  696.          for p=1,positions do -- try randomly
  697.             local q = random(1,positions)
  698.             if (isSymmetric[q]==1) then todel = q break end
  699.          end
  700.          if (todel==0) then -- if failed try by sequence
  701.             for p=1,positions do
  702.                if (isSymmetric[p]==1) then todel=p break end
  703.             end
  704.          end
  705.          if (todel>0) then -- correct isSymmetric
  706.             rs_remove_oxyd(todel,w,h)
  707.             if (removed ~= per_remove) then error("internal error: removed ~= per_remove") end
  708.             sym_count=sym_count-per_remove
  709.             want_remove = want_remove-per_remove
  710.             for r=1,removed do
  711.                tremove(isSymmetric,removed_indices[r]);
  712.             end
  713.          end
  714.       end
  715.  
  716.       -- now remove single symmetric positions
  717.       while (want_remove>0 and sym_count_single>0) do
  718.          for p=positions,1,-1 do
  719.             if (isSymmetric[p]==2) then
  720.                tremove(position,p);
  721.                tremove(isSymmetric,p);
  722.                want_remove = want_remove-1
  723.                positions = positions-1
  724.                sym_count_single = sym_count_single-1
  725.  
  726.                if (want_remove==0) then break end
  727.             end
  728.          end
  729.       end
  730.    end
  731.  
  732.    -- remove random stones for assymetric levels (or if symmetric remove failed)
  733.  
  734.    while (want_remove>0) do
  735.       local p = random(1,positions)
  736.       tremove(position,p);
  737.       want_remove = want_remove-1
  738.       positions=positions-1
  739.    end
  740.  
  741.    if (mod(positions,2)==1) then -- final check for even oxyd count
  742.       tremove(position,random(1,positions))
  743.       positions = positions-1
  744.    end
  745.  
  746.    -- end of oxyd removal
  747.  
  748.    debug("left oxyds: "..positions);
  749.  
  750.    if (positions == 0) then
  751.       error("No oxyds.")
  752.    end
  753.  
  754. --   if (maxoxyds>positions) then
  755. --      maxoxyds = positions
  756. --   end
  757.  
  758.    -- transform to "real" coordinates
  759.    for p=1,positions do
  760.       position[p][1] = position[p][1]+xlo-1
  761.       position[p][2] = position[p][2]+ylo-1
  762.       position[p][3] = position[p][3]+xlo-1
  763.       position[p][4] = position[p][4]+ylo-1
  764.    end
  765.  
  766.    -- now really set oxyds
  767.    for p=1,positions do
  768.       local xd,yd = position[p][1],position[p][2]
  769.       local xo,yo = position[p][3],position[p][4]
  770.  
  771.       enigma.KillStone(xd,yd)
  772.       enigma.KillStone(xo,yo)
  773.  
  774.       if (shogunLaser==1) then
  775.          lasers = lasers+1
  776.          local d
  777.          if (xd==xo) then
  778.             if (yd>yo) then d = NORTH else d = SOUTH end
  779.          else
  780.             if (xd>xo) then d = WEST else d = EAST end
  781.          end
  782.          set_stone("st-laser",xd,yd,{name="laser"..lasers, dir=d,on=0})
  783.       else
  784.          doors = doors+1
  785.          local d = "v"
  786.          if (xd==xo) then d = "h" end
  787.          set_floor(floorface,xd,yd)
  788.          set_stone(doorface,xd,yd, {name="door"..doors, type=d})
  789.       end
  790.       oxyd(xo,yo)
  791.    end
  792.  
  793.    debug("Oxyds set: "..doors.." Triggers: "..triggers)
  794.  
  795.    return 0
  796. end
  797.  
  798. function correct_locked_target_position()
  799.    if (shogunLaser==1) then
  800.       locked_target=random(1,lasers)
  801.       return 1
  802.    end
  803.  
  804.    while (locked_target <= doors) do
  805.       local ld = shuffle[locked_target]
  806.       local restricted = is_in_restricted_area(ld)
  807.  
  808.       local xd,yd = position[ld][1],position[ld][2]
  809.       debug("[locked door] ld="..ld.." xd="..xd.." yd="..yd.." restricted="..restricted)
  810.  
  811.       if (restricted == 0) then return 1 end
  812.       locked_target = locked_target+1
  813.    end
  814.    debug("Could not correct position of locked target. Not playable!");
  815.    return 0
  816. end
  817.  
  818. function correct_multi_trigger_doors()
  819.    if (doors == triggers) then return 1 end -- each trigger has exactly one door => no problem
  820.  
  821.    local multiTriggeredDoors = triggers-doors
  822.    if (multiTriggeredDoors > doors) then multiTriggeredDoors = doors end
  823.    for mtd=1,multiTriggeredDoors do
  824.       local ld = shuffle[mtd]
  825.       local restricted = is_in_restricted_area(ld)
  826.  
  827.       local xd,yd = position[ld][1],position[ld][2]
  828.       debug("[multit door] ld="..ld.." xd="..xd.." yd="..yd.." restricted="..restricted)
  829.  
  830.       if (restricted ~= 0) then return 0 end
  831.    end
  832.    return 1
  833. end
  834.  
  835. function correct_target_positions()
  836.    local ok = correct_locked_target_position()
  837.    if (ok==1 and shogunLaser==0) then
  838.       ok = correct_multi_trigger_doors()
  839.    end
  840.  
  841.    return ok
  842. end
  843.  
  844. function choose_symmetry()
  845.    if (xsymm==1 and ysymm==1) then ysymm=0 end -- prefer x-symmetry
  846.  
  847.    while ((xsymm+ysymm+psymm) > 1) do
  848.       local r = random(1,3)
  849.       if (r==1) then xsymm = 0
  850.       elseif (r==2) then ysymm = 0
  851.       else psymm = 0
  852.       end
  853.    end
  854.  
  855.    debug("----------------------------------------");
  856.    if (xsymm==1) then debug("Symmetry: X") end
  857.    if (ysymm==1) then debug("Symmetry: Y") end
  858.    if (psymm==1) then debug("Symmetry: central") end
  859. end
  860.  
  861. -- lost the game?
  862.  
  863. function stone_at(x,y)
  864.    -- result==0 -> no stone (or ignored)
  865.    -- result==1 -> yes
  866.    -- result==2 -> yes (but movable)
  867.  
  868.    local stone = enigma.GetStone(x,y)
  869.    if (not stone) then
  870. --      print("no stone at "..x.."/"..y);
  871.       if (useSwapStyle==1) then return 1 end
  872.  
  873.       if (doorface=="st-blocker") then
  874.          local item = enigma.GetItem(x,y)
  875.          if (item and GetAttrib(item,"kind")=="it-blocker") then return 1 end
  876.       end
  877.       return 0
  878.    end
  879.  
  880.    local kind = enigma.GetAttrib(stone,"kind")
  881.    --print("found stone '"..kind.."' at "..x.."/"..y.." boxface="..boxface.." wallface="..wallface)
  882.  
  883.    if (kind == "st-death") then return 1 end
  884.    if (kind == boxface) then return 2 end
  885.    if (kind == doorface) then return 1 end
  886.    if (kind == "borderstone") then return 1 end
  887.  
  888.    if (useSwapStyle==1) then
  889.       if (strsub(kind,1,8) == "st-grate") then return 0 end
  890.    else
  891.       if (kind == wallface) then return 1 end
  892.       if (strsub(kind,1,7)=="st-wood" and boxface=="st-wood") then return 2 end
  893.    end
  894.    print("stone '"..kind.."' was ignored!");
  895.    return 0
  896. end
  897.  
  898. function moveable(st1,st2)
  899.    -- result==0 -> yes
  900.    -- result==1 -> no
  901.    -- result==2 -> maybe
  902.  
  903.    if (st1==0) then
  904.       if (st2==0) then return 0
  905.       else return st2 end
  906.    else
  907.       if (st2==0) then
  908.          return st1
  909.       else
  910.          if (st1==1 or st2==1) then return 1
  911.          else return 2 end
  912.       end
  913.    end
  914. end
  915.  
  916. function lost_game(x,y)
  917.    local w = stone_at(x-1,y)
  918.    local e = stone_at(x+1,y)
  919.    local h = moveable(e,w)
  920.    if (h==0) then return 0 end
  921.  
  922.    local n = stone_at(x,y-1)
  923.    local s = stone_at(x,y+1)
  924.    local v = moveable(n,s)
  925.    if (v==0) then return 0 end
  926.  
  927.    if (h==1 and v==1) then return 1 end
  928.  
  929.    -- none is 0 and not both are 1
  930.    -- do slower but safe check
  931.  
  932. --   print("at "..x.."/"..y.." w="..w.." e="..e.." n="..n.." s="..s.." h="..h.." v="..v);
  933.  
  934.    if (h==1) then -- horizontal move is imposible
  935.       -- check horizontal move for neighbor box
  936.       if (s==2) then -- theres a box south
  937.          local se,sw = stone_at(x+1,y+1),stone_at(x-1,y+1)
  938.          if (moveable(se,sw)==1) then return 1 end -- which cannot be moved east-west
  939.       end
  940.       if (n==2) then -- theres a box north
  941.          local ne,nw = stone_at(x+1,y-1),stone_at(x-1,y-1)
  942.          if (moveable(ne,nw)==1) then return 1 end -- which cannot be moved east-west
  943.       end
  944.    end
  945.    if (v==1) then -- vertical move is imposible
  946.       -- check vertical move for neighbor box
  947.       if (w==2) then -- theres a box west
  948.          local nw,sw = stone_at(x-1,y-1),stone_at(x-1,y+1)
  949.          if (moveable(nw,sw)==1) then return 1 end -- which cannot be moved north-south
  950.       end
  951.       if (e==2) then -- theres a box east
  952.          local ne,se = stone_at(x+1,y-1),stone_at(x+1,y+1)
  953.          if (moveable(ne,se)==1) then return 1 end -- which cannot be moved north-south
  954.       end
  955.    end
  956.  
  957.    -- finally check for a box of 4 stones
  958.    if (w~=0) then
  959.       if (n~=0) then
  960.          if (stone_at(x-1,y-1)~=0) then return 1 end
  961.       end
  962.       if (s~=0) then
  963.          if (stone_at(x-1,y+1)~=0) then return 1 end
  964.       end
  965.    end
  966.    if (e~=0) then
  967.       if (n~=0) then
  968.          if (stone_at(x+1,y-1)~=0) then return 1 end
  969.       end
  970.       if (s~=0) then
  971.          if (stone_at(x+1,y+1)~=0) then return 1 end
  972.       end
  973.    end
  974.  
  975.    return 0
  976. end
  977.  
  978. function recheck_all_boxes()
  979.    for b=1,setboxes do
  980.       local box=enigma.GetNamedObject("box"..b)
  981.       boxx[b]=-1
  982.       boxy[b]=-1
  983.       if (box) then
  984.          local x,y = enigma.GetPos(box)
  985.  
  986.          if (x~=-1 and y~=-1) then
  987.             local item = enigma.GetItem(x,y)
  988.             if (item) then
  989.                if (enigma.GetAttrib(item,"kind")==triggerface) then
  990. --                  print("no recheck for box at "..x.."/"..y);
  991.                   boxx[b]=x
  992.                   boxy[b]=y
  993.                end
  994.             end
  995.          end
  996.       end
  997.    end
  998. end
  999.  
  1000. skip_timer=0
  1001. do_recheck=1
  1002.  
  1003. function timer_cb()
  1004.    if (shogunLaser==1) then
  1005.       flicker_lasers()
  1006.    end
  1007.  
  1008.    if (skip_timer>0) then
  1009.       skip_timer=skip_timer-1
  1010.    else
  1011.       local max_repeat=3 -- to avoid deadlock
  1012.       local rep=1
  1013.       local recheck=0
  1014.  
  1015.       while (rep==1 and max_repeat>0) do
  1016.          rep = 0
  1017.          max_repeat=max_repeat-1
  1018.  
  1019.          for b=1,setboxes do
  1020.             local box=enigma.GetNamedObject("box"..b)
  1021.             if (box) then
  1022.                local x,y = enigma.GetPos(box)
  1023.                if (x~=boxx[b] or y~=boxy[b]) then -- box position has changed
  1024. --                  print("Checking pos "..x.."/"..y.." (boxx="..boxx[b]..",boxy="..boxy[b]..")");
  1025.                   local die = 0;
  1026.                   local item = enigma.GetItem(x,y)
  1027.  
  1028.                   if (not item) then die = 1
  1029.                   elseif (enigma.GetAttrib(item,"kind")~=triggerface) then die = 1
  1030.                   end
  1031.  
  1032.                   if (die==1) then
  1033.                      if (lost_game(x,y)==1) then
  1034.                         game_lost = 1
  1035.                         enigma.PlaySound(box,"st-magic");
  1036.                         enigma.KillStone(x,y);
  1037.                         set_stone("st-death",x,y);
  1038. --                        print("kill stone at "..x.."/"..y);
  1039.                         rep=1
  1040.                         recheck=1
  1041.                      end
  1042.                   else -- box on trigger
  1043.                      if (lost_game(x,y)) then
  1044.                         recheck=1
  1045.                      end
  1046.                   end
  1047.                   boxx[b] = x;
  1048.                   boxy[b] = y;
  1049.                end
  1050. --            else
  1051. --               print("box expected");
  1052.             end
  1053.          end
  1054.  
  1055.          if (recheck==1) then recheck_all_boxes() end
  1056.       end
  1057.    end
  1058. end
  1059.  
  1060. function play_sokoban(level,num)
  1061.    local w,h = get_map_size(level)
  1062.    local tries = 80
  1063.    local ok = 0
  1064.  
  1065.    randomseed(enigma.GetTicks())
  1066.    choose_symmetry()
  1067.  
  1068.    while ((tries > 0) and (maxoxyds > 0) and (ok == 0)) do
  1069.       init(num-1)
  1070.       randomseed(enigma.GetTicks())
  1071.       --   rs_create_world(level,cells,spacecell,spacecell)
  1072.       rs_create_world(level,cells,set_spacecell,set_spacecell)
  1073.       enigma.ConserveLevel = FALSE
  1074.       enigma.ShowMoves = TRUE
  1075.  
  1076.       if (xlo > 0) then
  1077.          spl = 1 -- there's space at the left side
  1078.          space_count = space_count + maph
  1079.       end
  1080.       if (ylo > 0) then
  1081.          spt = 1 -- there's space at the top side
  1082.          space_count = space_count + mapw
  1083.       end
  1084.       if ((xlo+mapw) < 20) then
  1085.          spr = 1 -- there's space at the right side
  1086.          space_count = space_count + maph
  1087.       end
  1088.       if ((ylo+maph) < 13) then
  1089.          spb = 1 -- there's space at the bottom side
  1090.          space_count = space_count + mapw
  1091.       end
  1092.  
  1093.       debug("Space aside: spl="..spl.." spr="..spr.." spt="..spt.." spb="..spb);
  1094.  
  1095.       -- install oxyds
  1096.       install_oxyds(w,h)
  1097.       local targets=doors
  1098.       if (shogunLaser==1) then targets=lasers end
  1099.       init_shuffle(targets) -- mix trigger-door associations
  1100.       ok = correct_target_positions()
  1101. --      ok = 1
  1102.       if (ok == 0) then
  1103.          debug("---------------------------------------- re-initializing..");
  1104.       end
  1105.       tries = tries - 1
  1106. --      if (maxoxyds > 2) then
  1107. --         maxoxyds = maxoxyds - 1
  1108. --      end
  1109.    end
  1110.  
  1111.    if (ok==0) then
  1112.       error("Sorry - couldn't correct door positions. Avoiding deadlock (try again)");
  1113.    end
  1114.  
  1115.    triggerstate = strrep("0",triggers)
  1116.    display.SetFollowMode(display.FOLLOW_SCROLLING)
  1117.  
  1118.    draw_map(xlo,ylo,level,stonecells)
  1119.    set_actor("ac-blackball",acx,acy,{player=0})
  1120.  
  1121.    oxyd_shuffle()
  1122.  
  1123.    -- change some names for game_lost
  1124.    if (boxface=="st-wood-growing") then boxface="st-wood" end
  1125.    if (shogunLaser==1) then doorface="st-laser" end
  1126.  
  1127.    -- activate callback:
  1128.    if (test==0) then
  1129.       skip_timer=3
  1130.       for b=1,setboxes do
  1131.          boxx[b]=-1
  1132.          boxy[b]=-1
  1133.       end
  1134.       set_stone("st-timer", 0,0, {action="callback", target="timer_cb", interval=0.3, invisible=1})
  1135.    end
  1136.  
  1137. end
  1138.  
  1139.