编辑代码

      
-- 一个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  第五行
-- }
local propEnum = {
    None = 0,
    EggN1 = 1,   -- 普通蛋
    EggN2 = 2,
    EggN3 = 3,
    EggS1 = 4,   -- 高级蛋
    EggS2 = 5,
    EggS3 = 6,
    Branch = 7,  -- 树枝
    Count = 8,
}
local PropEnum = propEnum

--[[********************** 可修改参数 start **********************]]
local numberOfProp = {              -- 初始的时候,每种道具的数量。
    [propEnum.EggN1] = 12,        -- 普通蛋
    [propEnum.EggS1] = 8,        -- 高级蛋
    [propEnum.Branch] = 5,      -- 树枝(固定使用2次)。
}
local ScoreGetBingo = 8            -- 多少分达成bingo。

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

local InvalidNum = 0
local RowCount = 5
local ColumnCount = 5
local CellCount = RowCount * ColumnCount

math.randomseed(os.time())

local function indexToRowColumn(index)
    return math.floor((index - 1) / ColumnCount) + 1, (index - 1) % ColumnCount + 1
end

local function rowColumnToIndex(row, column)
    if row <= 0 or column <= 0 or row > RowCount or column > ColumnCount then
        return nil
    end
    return (row - 1) * ColumnCount + column
end

local function getAroundIndexs(index)
    local indexs = { }
    local row, column = indexToRowColumn(index)
    for tempRow = row - 1, row + 1, 1 do
        for tempColumn = column - 1, column + 1, 1 do
            if tempRow ~= row or tempColumn ~= column then
                if tempRow >= 1 and tempRow <= RowCount and tempColumn >= 1 and tempColumn <= ColumnCount then
                    table.insert(indexs, rowColumnToIndex(tempRow, tempColumn))
                end 
            end
        end
    end
    return indexs
end

local function getCrossIndexs(index)
    local indexs = { }
    local row, column = indexToRowColumn(index)
    local tempCoordinates = {
        { row - 1, column },
        { row + 1, column },
        { row, column + 1 },
        { row, column - 1 },
    }
    for _, config in pairs(tempCoordinates) do
        local tempRow = config[1]
        local tempColumn = config[2]
        if tempRow >= 1 and tempRow <= RowCount and tempColumn >= 1 and tempColumn <= ColumnCount then
            table.insert(indexs, rowColumnToIndex(tempRow, tempColumn))
        end
    end
    return indexs
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.contain(t, v)
    for _, value in pairs(t) do
        if value == v then
            return true
        end
    end
    return false
end

local function dump(list, string)
    local str = "" .. string
    for k, v in ipairs(list) do
        str = str .. v .. ", "
    end

    print(str)
end

local PropToEmoji = {
    [PropEnum.EggN1]   = "��",
    [PropEnum.EggS1]   = "��",
    [PropEnum.Branch] = "��",
    [PropEnum.EggN2]  = "��",
    [PropEnum.EggS2]  = "��",
    [PropEnum.EggN3]  = "��",
    [PropEnum.EggS3]  = "��",
}
if DebugOut then
    print(string.format("Up route: %s -> %s -> %s", PropToEmoji[PropEnum.EggN1], PropToEmoji[PropEnum.EggN2], PropToEmoji[PropEnum.EggN3]))
    print(string.format("Up route: %s -> %s -> %s", PropToEmoji[PropEnum.EggS1], PropToEmoji[PropEnum.EggS2], PropToEmoji[PropEnum.EggS3]))
end

local function getOneCell(number)
    local cell = { }
    cell.cellNum = number
    cell.tag = propEnum.None
    function cell:resetTag()
        self.tag = propEnum.None
    end
    function cell:setTag(tag)
        self.tag = tag
    end
    function cell:getDescription()
        local cellNumStr = "❎"
        if self.cellNum ~= InvalidNum then
            cellNumStr = tostring(self.cellNum)
            if string.len(cellNumStr) == 1 then
                cellNumStr = cellNumStr .. " "
            end
        end
        local cellTagStr = "  "
        if self.tag ~= propEnum.None then
            cellTagStr = PropToEmoji[self.tag]
        end
        
        return string.format("{%s, %s}", cellNumStr, cellTagStr)
    end

    -- 格子被点了。
    function cell:beenTagged()
        if self.cellNum == InvalidNum then
            return false, 0, 0
        end
        self.cellNum = InvalidNum
        return true, self.tag
    end
    return cell
end

local function addTagToBoard(board)
    local addPropQueue = { }
    for propId = PropEnum.None + 1, PropEnum.Count - 1, 1 do
        local tagCount = numberOfProp[propId] or 0
        while tagCount > 0 do
            table.insert(addPropQueue, propId)
            tagCount = tagCount - 1
        end
    end
    shuffleTable(addPropQueue)

    local candidateCellIndexs = { }
    for cellIndex = 1, CellCount, 1 do
        table.insert(candidateCellIndexs, cellIndex)
    end
    shuffleTable(candidateCellIndexs)

    while #addPropQueue > 0 and #candidateCellIndexs > 0 do
        local propId = table.remove(addPropQueue, 1)
        local tagCellIndex = table.remove(candidateCellIndexs, 1)
        
        board[tagCellIndex]:setTag(propId)
    end
end

local function PrintCurrentState(board, currentScore, ballStr, extraMsg, ignore)
    local str = ""
    for i = 1, RowCount, 1 do
        for j = 1, ColumnCount, 1 do
            local cellIndex = (i - 1) * ColumnCount + j
            local cell = board[cellIndex]
            str = str .. string.format("%s    ", cell:getDescription())
        end
        if i ~= RowCount then
            str = str .. "\r\n"
        end
    end
    if ballStr and ballStr ~= "" then
        print(ballStr)
    end
    print(str)
    if extraMsg and extraMsg ~= "" then
        print(extraMsg)
    end
    if currentScore then
        print("Current score: ", currentScore)
    end
    if not ignore then
        print()
    end
end

local function simulateReadBall()
    -- generated read balls.
    local balls = { }
    for i = 1, CellCount do
        table.insert(balls, i)
    end
    -- generate board.
    local board = { }
    for i = 1, CellCount do
        table.insert(board, getOneCell(i))
    end
    shuffleTable(board)
    -- generate tag.
    addTagToBoard(board)
    -- build map for board.
    local numToBoardIndex = { }
    for cellIndex, cell in ipairs(board) do
        if cell.cellNum ~= InvalidNum then
            numToBoardIndex[cell.cellNum] = cellIndex
        end
    end

    shuffleTable(balls)
    -- read balls
    local totalScore = 0
    local theScoreWhen15 = 0
    local readBallCount = 0

    if DebugOut then
        PrintCurrentState(board, nil, nil, nil, false)
    end

    local propPositions = { }
    while #balls > 0 do
        readBallCount = readBallCount + 1
        local ballNum = balls[1]
        table.remove(balls, 1)
        local readIndex = numToBoardIndex[ballNum]
        assert(readIndex ~= nil, string.format("Cant find ball num index: %d", ballNum))
        assert(board[readIndex].cellNum == ballNum or board[readIndex].cellNum == InvalidNum, "NumToBoardIndex Map Error!")

        -- 这个位置被点了。
        local success, propType = board[readIndex]:beenTagged()
        if propType ~= PropEnum.None then
            if propType == PropEnum.Branch then
                table.insert(propPositions, { readIndex, propType, 2, { } })
            else
                table.insert(propPositions, { readIndex, propType, 1 })
            end
        end

        -- 点击影响 > 皇冠小鸟 > 树枝。
        local effectInfos = {
            { "click", readIndex }
        }
        while #effectInfos > 0 do
            -- 鸟的影响。
            while #effectInfos > 0 do
                local nextInfos = { }
                for _, effectInfo in ipairs(effectInfos) do
                    local crossIndexs
                    if effectInfo[1] == "click" then
                        crossIndexs = getCrossIndexs(effectInfo[2])
                    else
                        crossIndexs = getAroundIndexs(effectInfo[2])
                    end
                    for _, tempCellIndex in pairs(crossIndexs) do
                        for i = #propPositions, 1, -1 do
                            if propPositions[i][1] == tempCellIndex then
                                if propPositions[i][2] == PropEnum.EggN1 then
                                    propPositions[i][2] = PropEnum.EggN2
                                    board[tempCellIndex]:setTag(propPositions[i][2])
                                elseif propPositions[i][2] == PropEnum.EggN2 then
                                    totalScore = totalScore + 1
                                    table.remove(propPositions, i)
                                    board[tempCellIndex]:setTag(PropEnum.EggN3)
                                elseif propPositions[i][2] == PropEnum.EggS1 then
                                    propPositions[i][2] = PropEnum.EggS2
                                    board[tempCellIndex]:setTag(propPositions[i][2])
                                elseif propPositions[i][2] == PropEnum.EggS2 then
                                    totalScore = totalScore + 1
                                    table.remove(propPositions, i)
                                    board[tempCellIndex]:setTag(PropEnum.EggS3)

                                    table.insert(nextInfos, { "superB", tempCellIndex })
                                end
                            end
                        end
                    end
                end
                effectInfos = nextInfos
            end

            -- 检查场上树枝的影响。
            local eggIndexs = { }
            local treeBranchIndexs = { }
            for i, propPosition in ipairs(propPositions) do
                if propPosition[3] > 0 then
                    local propId = propPosition[2]
                    if (propId >= PropEnum.EggN1 and propId <= PropEnum.EggN2)
                    or (propId >= PropEnum.EggS1 and propId <= PropEnum.EggS2) then
                        table.insert(eggIndexs, i)
                    elseif propId == PropEnum.Branch then
                        table.insert(treeBranchIndexs, i)
                    end
                end
            end
            while #treeBranchIndexs > 0 and #eggIndexs > 0 do
                local nextInfos = { }

                table.sort(eggIndexs, function(tempEggIndex1, tempEggIndex2)
                    if propPositions[tempEggIndex1][2] == propPositions[tempEggIndex2][2] then
                        return tempEggIndex1 < tempEggIndex2
                    else
                        return propPositions[tempEggIndex1][2] > propPositions[tempEggIndex2][2]
                    end
                end)

                local bracnhPropIndex = table.remove(treeBranchIndexs, 1)

                local branchPropInfo = propPositions[bracnhPropIndex]
                
                if branchPropInfo[3] > 0 and #eggIndexs > 0 then
                    for _, eggPropIndex in ipairs(eggIndexs) do
                        local eggPropInfo = propPositions[eggPropIndex]
                        local eggCellIndex = eggPropInfo[1]
                        -- 树枝没有作用过这个格子。
                        if eggPropInfo[3] > 0 and not branchPropInfo[4][eggCellIndex] then
                            if eggPropInfo[2] == PropEnum.EggN1 then
                                eggPropInfo[2] = PropEnum.EggN2
                                board[eggCellIndex]:setTag(eggPropInfo[2])
                            elseif eggPropInfo[2] == PropEnum.EggN2 then
                                totalScore = totalScore + 1
                                eggPropInfo[3] = eggPropInfo[3] - 1
                                board[eggCellIndex]:setTag(PropEnum.EggN3)
                            elseif eggPropInfo[2] == PropEnum.EggS1 then
                                eggPropInfo[2] = PropEnum.EggS2
                                board[eggCellIndex]:setTag(eggPropInfo[2])
                            elseif eggPropInfo[2] == PropEnum.EggS2 then
                                totalScore = totalScore + 1
                                eggPropInfo[3] = eggPropInfo[3] - 1
                                board[eggCellIndex]:setTag(PropEnum.EggS3)

                                table.insert(nextInfos, { "superB", eggCellIndex })
                            end
                            branchPropInfo[3] = branchPropInfo[3] - 1
                            branchPropInfo[4][eggCellIndex] = true

                            -- 树枝使得一个超级蛋变鸟了,根据优先级,先检查这个蛋的作用。
                            if #nextInfos > 0 or branchPropInfo[3] == 0 then
                                break
                            end
                        end
                    end
                end

                effectInfos = nextInfos
                if #nextInfos > 0 then
                    break
                end
            end

            for i = #propPositions, 1, -1 do
                if propPositions[i][3] == 0 then
                    table.remove(propPositions, i)
                end
            end

        end

        local ballStr = string.format("读球: %d --- %d --- %d", readIndex, ballNum, readBallCount)
        if DebugOut then
            PrintCurrentState(board, totalScore, ballStr)
        end

        if readBallCount <= 15 then
            theScoreWhen15 = totalScore
        end
        if totalScore >= ScoreGetBingo then
            break
        end
    end
    
    -- assert(totalScore >= ScoreGetBingo and readBallCount <= CellCount, "Config cannot reach bingo.")

    if theScoreWhen15 > ScoreGetBingo then
        theScoreWhen15 = ScoreGetBingo 
    end

    return readBallCount, theScoreWhen15, totalScore >= ScoreGetBingo
end

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

local strH = ""
local str1 = ""
local str2 = ""
for i = 1, #bingoNeedReadBall, 1 do
    local l1 = string.len(tostring(bingoNeedReadBall[i]))
    local l2 = string.len(tostring(bingoTotal[i]))
    local l = l1 > l2 and l1 or l2
    if l < string.len(tostring(i)) then
        l = string.len(tostring(i))
    end
    strH = strH .. string.format(string.format("%%%dd", l), i)
    str1 = str1 .. string.format(string.format("%%%dd", l), bingoNeedReadBall[i])
    str2 = str2 .. string.format(string.format("%%%dd", l), bingoTotal[i])
    if i ~= #bingoNeedReadBall then
        strH = strH .. ", "
        str1 = str1 .. ", "
        str2 = str2 .. ", "
    end
end

print("                  ", strH)
print("daub bingo count: ", str1)
print("cumulative bingo: ", str2)
print("aver score at 15: ", allTotalScoreWhen15 / LoopCount)

if DebugOut then
    print(string.format("Up route: %s -> %s -> %s", PropToEmoji[PropEnum.EggN1], PropToEmoji[PropEnum.EggN2], PropToEmoji[PropEnum.EggN3]))
    print(string.format("Up route: %s -> %s -> %s", PropToEmoji[PropEnum.EggS1], PropToEmoji[PropEnum.EggS2], PropToEmoji[PropEnum.EggS3]))
end