编辑代码


-- 一个Board的位置信息 {
--     1 , 2 , 3 , 4 , 5 , 第一行
--     6 , 7 , 8 , 9 , 10, 第二行
--     11, 12, 13, 14, 15, 第三行
--     16, 17, 18, 19, 20, 第四行
--     21, 22, 23, 24, 25  第五行
-- }
--[[
248 24万圣节
- 驱散X只幽灵
- 道具 幽灵1只
- 道具 幽灵2只
- 道具 蜡烛 驱散1个格子内的幽灵
- 道具 南瓜灯 驱散周围一圈的格子 只在中间一圈能刷出 优先级高于蜡烛
- 道具 糖果灯笼 点亮所有南瓜灯 南瓜灯能作用幽灵<=2时 转移
]]

local GRID_STATE = {
   NONE = 0,
   MARK = 1,
}

local PROP_TYPE = {
   NONE = 0,               -- 空
   GHOST_1 = 1,            -- 一只幽灵
   GHOST_2 = 2,            -- 两只幽灵
   CANDLE = 3,             -- 蜡烛
   PUMPKIN_LAMP = 4,       -- 南瓜灯
   CANDY_LAMP = 5,         -- 糖果灯
}
local PROP_BIG_TYPE = {
   GHOST = 1,              -- 幽灵
   CANDLE = 2,             -- 蜡烛
   PUMPKIN_LAMP_1 = 3,     -- 南瓜灯激活
   PUMPKIN_LAMP_2 = 4,     -- 南瓜灯未激活
   CANDY_LAMP = 5,         -- 糖果灯
}

local DefaultClickCellIndex = nil
--[[********************** 可修改参数 start **********************]]
local COLLECT_TARGET = 8    --bingo目标驱散幽灵数
local PROP_COUNT_LIST = {--对应道具数量
   [PROP_TYPE.GHOST_1] = 4,
   [PROP_TYPE.GHOST_2] = 5,
   [PROP_TYPE.CANDLE] = 5,
   [PROP_TYPE.PUMPKIN_LAMP] = 3,
   [PROP_TYPE.CANDY_LAMP] = 2,
}
local PUMPKIN_LAMP_RANG = {7, 8, 9, 12, 14, 17, 18, 19}

local LoopCount = 10000             -- 循环次数。
local DebugOut = false              -- 开启调试。开启后,只会计算一遍,会把每次读球数据打印出来,可以核对是否计算错误。
if DebugOut then
   LoopCount = 1
end
--[[********************** 可修改参数  end  **********************]]
local InvalidNum = 0
local MaxRow = 5
local MaxCol = 5
local CellCount = MaxRow * MaxCol


--格子编号转坐标 返回row col 左->右 上->下
local function grid2Pos(grid)
   return math.ceil(grid / MaxCol), (grid - 1) % MaxRow + 1
end

local function pos2Grid(row, col)
   return (row - 1) * MaxCol + col
end

local function dump_value_(v)
   if type(v) == "string" then
      v = "\"" .. v .. "\""
   end
   return tostring(v)
end
local function dump(value, description, nesting)
   if type(nesting) ~= "number" then nesting = 3 end

   local lookupTable = {}
   local result = {}

   -- local traceback = string.split(debug.traceback("", 2), "\n")
   -- print("dump from: " .. string.trim(traceback[3]))

   local function dump_(value, description, indent, nest, keylen)
      description = description or "<var>"
      local spc = ""
      if type(keylen) == "number" then
         spc = string.rep(" ", keylen - string.len(dump_value_(description)))
      end
      if type(value) ~= "table" then
         result[#result +1 ] = string.format("%s%s%s = %s", indent, dump_value_(description), spc, dump_value_(value))
      elseif lookupTable[tostring(value)] then
         result[#result +1 ] = string.format("%s%s%s = *REF*", indent, dump_value_(description), spc)
      else
         lookupTable[tostring(value)] = true
         if nest > nesting then
            result[#result +1 ] = string.format("%s%s = *MAX NESTING*", indent, dump_value_(description))
         else
            result[#result +1 ] = string.format("%s%s = {", indent, dump_value_(description))
            local indent2 = indent.."    "
            local keys = {}
            local keylen = 0
            local values = {}
            for k, v in pairs(value) do
               keys[#keys + 1] = k
               local vk = dump_value_(k)
               local vkl = string.len(vk)
               if vkl > keylen then keylen = vkl end
               values[k] = v
            end
            table.sort(keys, function(a, b)
               if type(a) == "number" and type(b) == "number" then
                  return a < b
               else
                  return tostring(a) < tostring(b)
               end
            end)
            for i, k in ipairs(keys) do
               dump_(values[k], k, indent2, nest + 1, keylen)
            end
            result[#result +1] = string.format("%s}", indent)
         end
      end
   end
   dump_(value, description, "- ", 1)

   for i, line in ipairs(result) do
      print(line)
   end
end

local function dump_board(value, description)
   print(description or "")
   local str = "\r\n"
   for row = 1, MaxRow, 1 do
      for col = 1, MaxCol, 1 do
         local cellIndex = pos2Grid(row, col)
         local cell = value[cellIndex]
         str = str .. " " .. cell
      end
      if row ~= MaxRow then
         str = str .. "\r\n"
      end
   end
   print(str)
end

local function shuffleTable(t, startIndex, endIndex)
   if startIndex == nil then
      startIndex = 1
   end
   if endIndex == nil then
      endIndex = #t
   end
   for i = startIndex, endIndex do
      local randomIndex = math.random(startIndex, endIndex)
      local tempValue = t[i]
      t[i] = t[randomIndex]
      t[randomIndex] = tempValue
   end
end
function table.nums(t)
   local count = 0
   for k, v in pairs(t) do
      count = count + 1
   end
   return count
end

function table.clone(t, nometa)
   local u = {}

   for i, v in pairs(t) do
      if type(v) == "table" then
         u[i] = table.clone(v)
      else
         u[i] = v
      end
   end
   return u
end

function table.indexof(array, value, begin)
   for i = begin or 1, #array do
      if array[i] == value then return i end
   end
   return false
end

function table.removebyvalue(array, value, removeall)
   local c, i, max = 0, 1, #array
   while i <= max do
      if array[i] == value then
         table.remove(array, i)
         c = c + 1
         i = i - 1
         max = max - 1
         if not removeall then break end
      end
      i = i + 1
   end
   return c
end

local function getOneCell(cellNum, cellType)
   local cell = { }
   cell.mNum_ = cellNum
   cell.mType_ = cellType
   function cell:getDescription()
      local cellNumStr = "  "
      if self.mNum_ ~= InvalidNum then
         cellNumStr = tostring(self.mNum_)
         if string.len(cellNumStr) == 1 then
            cellNumStr = " " .. cellNumStr
         end
      end

      return string.format("{%s, %s}", cellNumStr, self.mType_)
   end

   function cell:beenTagged()
      assert(self.mNum_ ~= InvalidNum, "Cell has been tagged.")
      self.mNum_ = InvalidNum
      return self.mType_
   end

   function cell:setType(type)
      self.mType_ = type
   end

   function cell:getType()
      return self.mType_
   end

   return cell
end

local function PrintCurrentState(board, allScore, ballStr, getPropMsg)
   local str = "\r\n"
   for i = 1, MaxRow, 1 do
      for j = 1, MaxCol, 1 do
         local cellIndex = (i - 1) * MaxCol + j
         local cell = board[cellIndex]
         str = str .. string.format("%s    ", cell:getDescription())
      end
      if i ~= MaxRow then
         str = str .. "\r\n"
      end
   end
   if ballStr ~= InvalidNum then
      print("Read ball: ", ballStr)
   end
   print(str)
   if getPropMsg and getPropMsg ~= "" then
      print(getPropMsg)
   end
   if allScore then
      print("Current total score: ", allScore)
   end
   print()
end

local function getGridRange(gridIndex)
   local row, col = grid2Pos(gridIndex)

   local rangList = {}
   for i = -1, 1, 1 do
      for j = -1, 1, 1 do
         if not (i == 0 and j == 0) then
            local index = pos2Grid(row + i, col + j)
            rangList[index] = index
         end
      end
   end

   return rangList
end

local function getOneBoardConfig()
   local boardConfig = {}

   --先将南瓜灯灯放入(不相邻)
   local pumpkinRange = table.clone(PUMPKIN_LAMP_RANG)
   shuffleTable(pumpkinRange)
   local pumpkinList = {}
   for i = 1, PROP_COUNT_LIST[PROP_TYPE.PUMPKIN_LAMP] do
      for index, pumpkinGrid in ipairs(pumpkinRange) do
         local pRow, pCol = grid2Pos(pumpkinGrid)
         local isAdjoin = false
         for _, selectGrid in ipairs(pumpkinList) do
            local sRow, sCol = grid2Pos(selectGrid)
            local oRow, oCol = math.abs(sRow - pRow), math.abs(pCol - sCol)
            if not (oRow == 1 and oCol == 1) and oRow <= 1 and oCol <= 1 then
               isAdjoin = true
               break
            end
         end
         if not isAdjoin then
            table.insert(pumpkinList, pumpkinGrid)
            boardConfig[pumpkinGrid] = PROP_TYPE.PUMPKIN_LAMP
            table.remove(pumpkinRange, index)
            break
         end
      end
   end

   local emptyList = {}
   for i = 1, MaxRow * MaxCol do
      if not boardConfig[i] then
         table.insert(emptyList, i)
         boardConfig[i] = PROP_TYPE.NONE
      end
   end
   shuffleTable(emptyList)

   local allPropList = {}
   for propType, propCount in pairs(PROP_COUNT_LIST) do
      if propType ~= PROP_TYPE.PUMPKIN_LAMP then
         for i = 1, propCount do
            table.insert(allPropList, propType)
         end
      end
   end

   for index, propType in ipairs(allPropList) do
      boardConfig[emptyList[index]] = propType
   end

   return boardConfig
end

math.randomseed(os.time())
local function simulateReadBall()
   -- generated read balls.
   local balls = { }
   for i = 1, CellCount do
      table.insert(balls, i)
   end
   -- generate board.
   local boardConfig = getOneBoardConfig()
   local board = { }
   for i = 1, CellCount do
      table.insert(board, getOneCell(i, boardConfig[i]))
   end

   -- generate tag.
   local _isBingo = 0
   local _totaleScore = 0
   local _scoreWhen15 = 0
   local _readBallCount = 0

   local _markList = {}
   for i = 1, MaxRow * MaxCol do
      _markList[i] = GRID_STATE.NONE
   end
   local _waitPropList = {
   }
   for _, bigType in pairs(PROP_BIG_TYPE) do
      _waitPropList[bigType] = {}
   end

   -- build map for board.
   local numToBoardIndex = { }
   for cellIndex, cell in ipairs(board) do
      if cell.mNum_ ~= InvalidNum then
         numToBoardIndex[cell.mNum_] = cellIndex
      end
   end
   shuffleTable(balls)

   local function checkBingo()
      if _totaleScore >= COLLECT_TARGET then
         _isBingo = 1
         return true
      end
      return false
   end

   local useProp
   --算法逻辑
   local function updateLogic(gridIndex)
      _markList[gridIndex] = GRID_STATE.MARK
      local gridType = boardConfig[gridIndex]

      --需要检查是否需要转移(可激活南瓜驱散幽灵数<=2 转移)
      -- if gridType == PROP_TYPE.CANDY_LAMP then
      --     local costList = {}
      --     local costCount = 0
      --     for _, index in ipairs(_waitPropList[PROP_BIG_TYPE.PUMPKIN_LAMP_2]) do
      --         local rangList = getGridRange(index)
      --         for _, ghostIndex in ipairs(_waitPropList[PROP_BIG_TYPE.GHOST]) do
      --             if rangList[ghostIndex] and not costList[ghostIndex] then
      --                 costList[ghostIndex] = ghostIndex
      --                 if boardConfig[ghostIndex] == PROP_TYPE.GHOST_1 then
      --                     costCount = costCount + 1
      --                 elseif boardConfig[ghostIndex] == PROP_TYPE.GHOST_2 then
      --                     costCount = costCount + 2
      --                 end
      --             end
      --         end
      --         if costCount > 2 then
      --             break
      --         end
      --     end

      --     --需要转移
      --     if costCount <= 2 then
      --         local emptyList = {}
      --         local isAlowPumpkin = table.indexof(PUMPKIN_LAMP_RANG, gridIndex)
      --         for index, type in ipairs(boardConfig) do
      --             if _markList[index] ~= GRID_STATE.MARK and type ~= PROP_TYPE.CANDY_LAMP and (isAlowPumpkin or type ~= PROP_TYPE.PUMPKIN_LAMP) then
      --                 table.insert(emptyList, index)
      --             end
      --         end
      --         shuffleTable(emptyList)
      --         if emptyList[1] then
      --             gridType = boardConfig[emptyList[1]]
      --             boardConfig[emptyList[1]] = boardConfig[gridIndex]
      --             boardConfig[gridIndex] = gridType
      --         end
      --     end
      -- end
      --油灯数>0转移
      -- if gridType == PROP_TYPE.CANDY_LAMP then
      --     if #_waitPropList[PROP_BIG_TYPE.CANDY_LAMP] > 0 then
      --         local emptyList = {}
      --         local isAlowPumpkin = false--table.indexof(PUMPKIN_LAMP_RANG, gridIndex)
      --         for index, type in ipairs(boardConfig) do
      --             if _markList[index] ~= GRID_STATE.MARK and type ~= PROP_TYPE.CANDY_LAMP and (isAlowPumpkin or type ~= PROP_TYPE.PUMPKIN_LAMP) then
      --                 table.insert(emptyList, index)
      --             end
      --         end
      --         shuffleTable(emptyList)
      --         if emptyList[1] then
      --             gridType = boardConfig[emptyList[1]]
      --             boardConfig[emptyList[1]] = boardConfig[gridIndex]
      --             boardConfig[gridIndex] = gridType
      --         end
      --     end
      -- end
      --油灯数>0消失
      if gridType == PROP_TYPE.CANDY_LAMP then
         if #_waitPropList[PROP_BIG_TYPE.CANDY_LAMP] > 0 then
            boardConfig[gridIndex] = PROP_TYPE.NONE
            gridType = PROP_TYPE.NONE
         end
      end

      if gridType == PROP_TYPE.GHOST_1 or gridType == PROP_TYPE.GHOST_2 then
         table.insert(_waitPropList[PROP_BIG_TYPE.GHOST], gridIndex)
      elseif gridType == PROP_TYPE.CANDLE then
         table.insert(_waitPropList[PROP_BIG_TYPE.CANDLE], gridIndex)
      elseif gridType == PROP_TYPE.PUMPKIN_LAMP then
         table.insert(_waitPropList[PROP_BIG_TYPE.PUMPKIN_LAMP_1], gridIndex)
      elseif gridType == PROP_TYPE.CANDY_LAMP then
         table.insert(_waitPropList[PROP_BIG_TYPE.CANDY_LAMP], gridIndex)
      end

      useProp()

      if DebugOut then
         dump_board(_markList, gridIndex .. " " .. gridType .. " ")
      end
   end

   local function useOnePumpkin(gridIndex)
      local ghostList = {}
      local rangList = getGridRange(gridIndex)
      for _, ghostIndex in ipairs(_waitPropList[PROP_BIG_TYPE.GHOST]) do
         if rangList[ghostIndex] then
            table.insert(ghostList, ghostIndex)
            if boardConfig[ghostIndex] == PROP_TYPE.GHOST_1 then
               _totaleScore = _totaleScore + 1
            elseif boardConfig[ghostIndex] == PROP_TYPE.GHOST_2 then
               _totaleScore = _totaleScore + 2
            end
         end
      end
      if #ghostList > 0 then
         for _, index in ipairs(ghostList) do
            table.removebyvalue(_waitPropList[PROP_BIG_TYPE.GHOST], index)
         end
      end
      return ghostList
   end

   function useProp()
      if checkBingo() then
         return
      end

      --有糖果灯先表现糖果灯
      if #_waitPropList[PROP_BIG_TYPE.CANDY_LAMP] > 0 and #_waitPropList[PROP_BIG_TYPE.PUMPKIN_LAMP_2] > 0 then
         table.remove(_waitPropList[PROP_BIG_TYPE.CANDY_LAMP], 1)
         for _, gridIndex in ipairs(_waitPropList[PROP_BIG_TYPE.PUMPKIN_LAMP_2]) do
            table.insert(_waitPropList[PROP_BIG_TYPE.PUMPKIN_LAMP_1], gridIndex)
         end
         _waitPropList[PROP_BIG_TYPE.PUMPKIN_LAMP_2] = {}
      end

      --检查处理激活状态的南瓜灯是否触发
      if #_waitPropList[PROP_BIG_TYPE.PUMPKIN_LAMP_1] > 0 then
         local isUsePimpkin = false
         for _, gridIndex in ipairs(_waitPropList[PROP_BIG_TYPE.PUMPKIN_LAMP_1]) do
            local ghostList = useOnePumpkin(gridIndex)
            if #ghostList > 0 then
               table.removebyvalue(_waitPropList[PROP_BIG_TYPE.PUMPKIN_LAMP_1], gridIndex)
               table.insert(_waitPropList[PROP_BIG_TYPE.PUMPKIN_LAMP_2], gridIndex)

               isUsePimpkin = true
            end
         end
         if isUsePimpkin then
            useProp()
            return
         end
      end

      --检查蜡烛
      if #_waitPropList[PROP_BIG_TYPE.CANDLE] > 0 then
         local costList = {}
         for _, index in ipairs(_waitPropList[PROP_BIG_TYPE.CANDLE]) do
            if _waitPropList[PROP_BIG_TYPE.GHOST][1] then
               local ghostIndex = table.remove(_waitPropList[PROP_BIG_TYPE.GHOST], 1)
               if boardConfig[ghostIndex] == PROP_TYPE.GHOST_1 then
                  _totaleScore = _totaleScore + 1
               elseif boardConfig[ghostIndex] == PROP_TYPE.GHOST_2 then
                  _totaleScore = _totaleScore + 2
               end
               table.insert(costList, index)
            else
               break
            end
         end
         for _, index in ipairs(costList) do
            table.removebyvalue(_waitPropList[PROP_BIG_TYPE.CANDLE], index)
         end
      end
   end

   -- read balls
   while #balls > 0 do
      _readBallCount = _readBallCount + 1
      local ballNum = balls[1]
      table.remove(balls, 1)
      local ballIndex = numToBoardIndex[ballNum]
      assert(ballIndex ~= nil, string.format("Cant find ball num index: %d", ballNum))
      assert(board[ballIndex].mNum_ == ballNum, "NumToBoardIndex Map Error!")

      board[ballIndex]:beenTagged()
      if ballIndex ~= DefaultClickCellIndex then
         updateLogic(ballIndex)
      end

      if DebugOut then
         PrintCurrentState(board, _totaleScore, ballNum)
      end

      if _totaleScore > COLLECT_TARGET then
         _totaleScore = COLLECT_TARGET
      end
      if _readBallCount <= 15 then
         _scoreWhen15 = _totaleScore
      end

      if checkBingo() then
         break
      end
   end

   return _readBallCount, _scoreWhen15
end

local bingoNeedReadBall = { }
for i = 1, CellCount do
   bingoNeedReadBall[i] = 0
end
local allTotalScoreWhen15 = 0

for i = 1, LoopCount do
   local ballCountCompleteBingo, snailNodePositionWhen15 = simulateReadBall()
   bingoNeedReadBall[ballCountCompleteBingo] = bingoNeedReadBall[ballCountCompleteBingo] + 1
   allTotalScoreWhen15 = allTotalScoreWhen15 + snailNodePositionWhen15
end
local bingoTotal = { }
bingoTotal[1] = 0
for i = 2, CellCount do
   bingoTotal[i] = bingoNeedReadBall[i] + bingoTotal[i - 1]
end

print("标记格子数量发生bingo:", table.concat(bingoNeedReadBall, ", "))
print("标记累计发生bingo数量:", table.concat(bingoTotal, ", "))
print("第15次读球, 分:", allTotalScoreWhen15 / LoopCount)