Zamiel

Cycle Finder in Lua

Oct 18th, 2018
336
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 11.62 KB | None | 0 0
  1. local RPCheckLoop = {}
  2.  
  3. -- Includes
  4. local RPShapes = require("src/rpshapes")
  5.  
  6. -- Reseed the floor if there is a loop
  7. function RPCheckLoop:Main()
  8.   -- Local variables
  9.   local game = Game()
  10.   local level = game:GetLevel()
  11.   local stage = level:GetStage()
  12.   local stageType = level:GetStageType()
  13.   local startingRoomIndex = level:GetStartingRoomIndex()
  14.   local rooms = level:GetRooms()
  15.  
  16.   if stage == LevelStage.STAGE1_1 or -- 1 (Basement 1)
  17.      stage == LevelStage.STAGE4_3 or -- 9 (Blue Womb)
  18.      stage == LevelStage.STAGE7 then -- 12 (The Void)
  19.  
  20.     -- It is probably not possible to have a loop in Basement 1,
  21.     -- so don't bother checking to make resetting faster on potato computers
  22.     -- There are no loops in the Blue Womb
  23.     -- Don't bother checking for loops in The Void, as the mixing of the floors makes it more complex to detect a loop
  24.     return
  25.   end
  26.  
  27.   -- Make an empty 13x13 grid and initialize all elements to the value that represents an obstacle
  28.   -- The game uses a 0-indexed grid, but we will use a 1-indexed grid
  29.   local grid = {}
  30.   for i = 1, 13 do
  31.     grid[i] = {}
  32.     for j = 1, 13 do
  33.       grid[i][j] = -1
  34.     end
  35.   end
  36.  
  37.   -- Get the floor string, i.e. "F1_1"
  38.   -- (this is the index for the RPShapes table)
  39.   local floorNum
  40.   if stage == 1 or stage == 2 then
  41.     floorNum = 1
  42.   elseif stage == 3 or stage == 4 then
  43.     floorNum = 2
  44.   elseif stage == 5 or stage == 6 then
  45.     floorNum = 3
  46.   elseif stage == 7 or stage == 8 then
  47.     floorNum = 4
  48.   elseif stage == 10 then
  49.     floorNum = 5
  50.   elseif stage == 11 then
  51.     floorNum = 6
  52.   end
  53.   local floorString = "F" .. tostring(floorNum) .. "_" .. tostring(stageType)
  54.  
  55.   -- Also, keep track of basic information about each room
  56.   local roomsData = {}
  57.  
  58.   -- Make an entry for each room on the floor
  59.   -- (to both the grid and the roomData)
  60.   local startingRoomNum
  61.   for i = 0, rooms.Size - 1 do -- This is 0 indexed
  62.     local roomDesc = rooms:Get(i)
  63.     local roomIndex = roomDesc.SafeGridIndex -- This is always the top-left index
  64.     local roomData = roomDesc.Data
  65.     local roomType = roomData.Type
  66.  
  67.     -- There will never be a special room in a loop, so we can ignore them to save CPU cycles
  68.     -- Furthermore, we don't want to account for the Secret Room / moon strats
  69.     if roomType == RoomType.ROOM_DEFAULT then -- 5
  70.       local roomDataVariant = roomData.Variant
  71.       while roomDataVariant > 10000 do
  72.         -- The 3 flipped versions of room #1 would be #10001, #20001, and #30001
  73.         roomDataVariant = roomDataVariant - 10000
  74.       end
  75.  
  76.       local roomShape = RPShapes[floorString][roomDataVariant]
  77.       local x, y = RPCheckLoop:GetXYFromGridIndex(roomIndex)
  78.  
  79.       -- Fill in the grid with values corresponding to this room index
  80.       grid[y][x] = i
  81.       if roomShape == RoomShape.ROOMSHAPE_1x2 or -- 4 (1 wide x 2 tall)
  82.          roomShape == RoomShape.ROOMSHAPE_IIV then -- 5 (1 wide x 2 tall, narrow)
  83.  
  84.         grid[y + 1][x] = i -- The square below
  85.  
  86.       elseif roomShape == RoomShape.ROOMSHAPE_2x1 or -- 6 (2 wide x 1 tall)
  87.              roomShape == RoomShape.ROOMSHAPE_IIH then -- 7 (2 wide x 1 tall, narrow)
  88.  
  89.         grid[y][x + 1] = i -- The square to the right
  90.  
  91.       elseif roomShape == RoomShape.ROOMSHAPE_2x2 then -- 8 (2 wide x 2 tall)
  92.         grid[y][x + 1] = i -- The square to the right
  93.         grid[y + 1][x] = i -- The square below
  94.         grid[y + 1][x + 1] = i -- The square to the bottom-right
  95.  
  96.       elseif roomShape == RoomShape.ROOMSHAPE_LTL then -- 9 (L room, top-left is missing)
  97.         grid[y + 1][x] = i -- The square below
  98.         grid[y + 1][x - 1] = i -- The square to the bottom-left
  99.  
  100.       elseif roomShape == RoomShape.ROOMSHAPE_LTR then -- 10 (L room, top-right is missing)
  101.         grid[y + 1][x] = i -- The square below
  102.         grid[y + 1][x + 1] = i -- The square to the bottom-right
  103.  
  104.       elseif roomShape == RoomShape.ROOMSHAPE_LBL then -- 11 (L room, bottom-left is missing)
  105.         grid[y][x + 1] = i -- The square to the right
  106.         grid[y + 1][x + 1] = i -- The square to the bottom-right
  107.  
  108.       elseif roomShape == RoomShape.ROOMSHAPE_LBR then -- 12 (L room, bottom-right is missing)
  109.         grid[y][x + 1] = i -- The square to the right
  110.         grid[y + 1][x] = i -- The square below
  111.       end
  112.  
  113.       -- Also, fill in the roomsData with values corresponding to this room index
  114.       roomsData[i] = {
  115.         x = x,
  116.         y = y,
  117.         roomShape = roomShape,
  118.       }
  119.  
  120.       -- Keep track of the starting room for later
  121.       if roomIndex == startingRoomIndex then
  122.         startingRoomNum = i
  123.       end
  124.  
  125.       --[[
  126.       Isaac.DebugString("Plotted room " .. tostring(i) .. ":")
  127.       Isaac.DebugString("  ID: " .. tostring(roomData.Variant))
  128.       Isaac.DebugString("  Index: " .. tostring(roomIndex))
  129.       Isaac.DebugString("  Coordinates: (" .. tostring(x) .. ", " .. tostring(y) .. ")")
  130.       Isaac.DebugString("  Shape: " .. tostring(roomShape))
  131.       --]]
  132.     end
  133.   end
  134.  
  135.   -- Print out a graphic representing the grid
  136.   --[[
  137.   Isaac.DebugString("Grid:")
  138.   for i = 1, #grid do
  139.     local rowString = "  " .. tostring(i) .. ": "
  140.     if i < 10 then
  141.       rowString = rowString .. " "
  142.     end
  143.     for j = 1, #grid[i] do
  144.       if grid[i][j] == -1 then
  145.         -- No room is here
  146.         rowString = rowString .. "  "
  147.       else
  148.         -- A room is here
  149.         rowString = rowString .. grid[i][j]
  150.         if i == roomsData[startingRoomNum].y and
  151.            j == roomsData[startingRoomNum].x then
  152.  
  153.           rowString = rowString .. "!"
  154.  
  155.         elseif grid[i][j] < 10 then
  156.           rowString = rowString .. " "
  157.         end
  158.       end
  159.       rowString = rowString .. " "
  160.     end
  161.     Isaac.DebugString(rowString)
  162.   end
  163.   --]]
  164.  
  165.   -- We have created a grid, so now we need to create a node connection table to feed to the cycle checker algorithm
  166.   Isaac.DebugString("Creating connection table...")
  167.   local RPCheckLoop.nodes = {}
  168.   for i, roomData in pairs(roomsData) do
  169.     local connectedRooms = {}
  170.     local adjacentSquares = RPCheckLoop:GetAdjacentSquares(roomData.roomShape)
  171.     for j = 1, #adjacentSquares do
  172.       local mod = adjacentSquares[j]
  173.       local adjacentRoomID = grid[roomData.y + mod.y][roomData.x + mod.x]
  174.       local alreadyConnected = false
  175.       for k = 1, #connectedRooms do
  176.         if connectedRooms[k] == adjacentRoomID then
  177.           alreadyConnected = true
  178.           break
  179.         end
  180.       end
  181.       if alreadyConnected == false and
  182.          adjacentRoomID ~= -1 then -- We initialized every square to -1 when we created the grid
  183.  
  184.         connectedRooms[#connectedRooms + 1] = adjacentRoomID
  185.       end
  186.     end
  187.  
  188.     -- Keep track of the connected rooms for every room
  189.     RPCheckLoop.nodes[i] = connectedRooms
  190.   end
  191.  
  192.   --[[
  193.   -- Print out the connection list
  194.   Isaac.DebugString("Room connection list:")
  195.   for i, node in pairs(RPCheckLoop.nodes) do
  196.     local debugString = "  " .. tostring(i) .. " - (" .. table.concat(node) .. ")"
  197.     Isaac.DebugString(debugString)
  198.   end
  199.   --]]
  200.  
  201.   -- Do a Depth First Search (DFS) to find a loop
  202.   RPCheckLoop.visited = {}
  203.   return RPCheckLoop:HasCycle(startingRoomNum, RPCheckLoop.nodes)
  204. end
  205.  
  206. -- Get the grid coordinates on a 13x13 grid
  207. function RPCheckLoop:GetXYFromGridIndex(idx)
  208.   -- 0 --> (0, 0)
  209.   -- 1 --> (1, 0)
  210.   -- 13 --> (0, 1)
  211.   -- 14 --> (1, 1)
  212.   -- etc.
  213.   local y = math.floor(idx / 13)
  214.   local x = idx - (y * 13)
  215.  
  216.   -- Now, we add 1 to each x and y because the game uses a 0-indexed grid and Lua's tables are 1-indexed
  217.   return x + 1, y + 1
  218. end
  219.  
  220. function RPCheckLoop:GetAdjacentSquares(roomShape)
  221.   -- Adjacent tiles for each room shape are listed clockwise, starting at the top
  222.   -- The starting square is always the top-left square
  223.   if roomShape == RoomShape.ROOMSHAPE_1x1 then -- 1
  224.     return {
  225.       {x = 0, y = -1}, -- Up
  226.       {x = 1, y = 0}, -- Right
  227.       {x = 0, y = 1}, -- Down
  228.       {x = -1, y = 0}, -- Left
  229.     }
  230.  
  231.   elseif roomShape == RoomShape.ROOMSHAPE_IH then -- 2
  232.     return {
  233.       {x = 1, y = 0}, -- Right
  234.       {x = -1, y = 0}, -- Left
  235.     }
  236.  
  237.   elseif roomShape == RoomShape.ROOMSHAPE_IV then -- 3
  238.     return {
  239.       {x = 0, y = -1}, -- Up
  240.       {x = 0, y = 1}, -- Down
  241.     }
  242.  
  243.   elseif roomShape == RoomShape.ROOMSHAPE_1x2 then -- 4 (1 wide x 2 tall)
  244.     return {
  245.       {x = 0, y = -1}, -- Up
  246.       {x = 1, y = 0}, -- Right-top
  247.       {x = 1, y = 1}, -- Right-bottom
  248.       {x = 0, y = 2}, -- Down
  249.       {x = -1, y = 1}, -- Left-bottom
  250.       {x = -1, y = 0}, -- Left-top
  251.     }
  252.  
  253.   elseif roomShape == RoomShape.ROOMSHAPE_IIV then -- 5 (1 wide x 2 tall, narrow)
  254.     return {
  255.       {x = 0, y = -1}, -- Up
  256.       {x = 0, y = 2}, -- Down
  257.     }
  258.  
  259.   elseif roomShape == RoomShape.ROOMSHAPE_2x1 then -- 6 (2 wide x 1 tall)
  260.     return {
  261.       {x = 0, y = -1}, -- Up-left
  262.       {x = 1, y = -1}, -- Up-right
  263.       {x = 2, y = 0}, -- Right
  264.       {x = 1, y = 1}, -- Down-right
  265.       {x = 0, y = 1}, -- Down-left
  266.       {x = -1, y = 0}, -- Left
  267.     }
  268.  
  269.   elseif roomShape == RoomShape.ROOMSHAPE_IIH then -- 7 (2 wide x 1 tall, narrow)
  270.     return {
  271.       {x = 2, y = 0}, -- Right
  272.       {x = -1, y = 0}, -- Left
  273.     }
  274.  
  275.   elseif roomShape == RoomShape.ROOMSHAPE_2x2 then -- 8 (2 wide x 2 tall)
  276.     return {
  277.       {x = 0, y = -1}, -- Up-left
  278.       {x = 1, y = -1}, -- Up-right
  279.       {x = 2, y = 0}, -- Right-top
  280.       {x = 2, y = 1}, -- Right-bottom
  281.       {x = 1, y = 2}, -- Down-right
  282.       {x = 0, y = 2}, -- Down-left
  283.       {x = -1, y = 1}, -- Left-bottom
  284.       {x = -1, y = 0}, -- Left-top
  285.     }
  286.  
  287.   elseif roomShape == RoomShape.ROOMSHAPE_LTL then -- 9 (L room, top-left is missing)
  288.     return {
  289.       {x = 0, y = -1}, -- Up
  290.       {x = 1, y = 0}, -- Right-top
  291.       {x = 1, y = 1}, -- Right-bottom
  292.       {x = 0, y = 2}, -- Down-right
  293.       {x = -1, y = 2}, -- Down-left
  294.       {x = -2, y = 1}, -- Left-bottom
  295.       {x = -1, y = 0}, -- Left-top
  296.     }
  297.  
  298.   elseif roomShape == RoomShape.ROOMSHAPE_LTR then -- 10 (L room, top-right is missing)
  299.     return {
  300.       {x = 0, y = -1}, -- Up
  301.       {x = 1, y = 0}, -- Right-top
  302.       {x = 2, y = 1}, -- Right-bottom
  303.       {x = 1, y = 2}, -- Down-right
  304.       {x = 0, y = 2}, -- Down-left
  305.       {x = -1, y = 1}, -- Left-bottom
  306.       {x = -1, y = 0}, -- Left-top
  307.     }
  308.  
  309.   elseif roomShape == RoomShape.ROOMSHAPE_LBL then -- 11 (L room, bottom-left is missing)
  310.     return {
  311.       {x = 0, y = -1}, -- Up-left
  312.       {x = 1, y = -1}, -- Up-right
  313.       {x = 2, y = 0}, -- Right-top
  314.       {x = 2, y = 1}, -- Right-bottom
  315.       {x = 1, y = 2}, -- Down
  316.       {x = 0, y = 1}, -- Left-bottom
  317.       {x = -1, y = 0}, -- Left-top
  318.     }
  319.  
  320.   elseif roomShape == RoomShape.ROOMSHAPE_LBR then -- 12 (L room, bottom-right is missing)
  321.     return {
  322.       {x = 0, y = -1}, -- Up-left
  323.       {x = 1, y = -1}, -- Up-right
  324.       {x = 2, y = 0}, -- Right-top
  325.       {x = 1, y = 1}, -- Right-bottom
  326.       {x = 0, y = 2}, -- Down
  327.       {x = -1, y = 1}, -- Left-bottom
  328.       {x = -1, y = 0}, -- Left-top
  329.     }
  330.   end
  331. end
  332.  
  333. -- A recursive function that does a Depth First Search (DFS) to see if there is a cycle (loop) in the node connection list
  334. function RPCheckLoop:HasCycle(node, cameFrom)
  335.   -- If we found this node already, there is a cycle
  336.   for i = 1, #RPCheckLoop.visited do
  337.     if RPCheckLoop.visited[i] == node then
  338.       return true
  339.     end
  340.   end
  341.  
  342.   -- Mark that we have visited this node
  343.   RPCheckLoop.visited[#RPCheckLoop.visited + 1] = node
  344.  
  345.   -- Go through all the nodes that are connected to this node
  346.   for _, n in ipairs(RPCheckLoop.nodes[node]) do
  347.     if n ~= cameFrom then
  348.       if RPCheckLoop:HasCycle(n, node) then
  349.         return true
  350.       end
  351.     end
  352.   end
  353.  
  354.   return false
  355. end
  356.  
  357. return RPCheckLoop
Advertisement
Add Comment
Please, Sign In to add comment