编辑代码

      
-- 一个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)