编辑代码

-- 一个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,
    Gold = 1,
    ManyGolds = 2,
    MapFragment = 3,
    Magnifier = 4,
    Map = 5,
    TreasureBox = 6,
    Count = 7,
}
local PropEnum = propEnum

--[[********************** 可修改参数 start **********************]]
local numberOfProp = {              -- 初始的时候,每种道具的数量。
    [propEnum.Gold] = 6,            -- 金币数量。
    [propEnum.ManyGolds] = 3,       -- 金币堆数量。
    [propEnum.MapFragment] = 4,     -- 地图碎片数量。
    [propEnum.Magnifier] = 3,       -- 探测仪数量。
}
local propToScore = {               -- 每种道具获得分数。
    [propEnum.Gold] = 1,            -- 标记金币得分。
    [propEnum.ManyGolds] = 3,       -- 标记金币堆得分。
    [propEnum.TreasureBox] = 6,     -- 获得宝箱得分。
}

local fragmentsCountToMap = 2       -- 多少碎片合成地图。
local magnifierEffectCells = 2      -- 探测仪会作用多少个格子。

local ScoreGetBingo = 20            -- 多少分达成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 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.Gold] = "��",
    [PropEnum.ManyGolds] = "��",
    [PropEnum.MapFragment] = "��",
    [PropEnum.Magnifier] = "��",
    [PropEnum.Map] = "��️",
    [PropEnum.TreasureBox] = "��",
}

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
        local tag = self.tag
        self.cellNum = InvalidNum
        self.tag = PropEnum.None
        return true, 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
    table.remove(candidateCellIndexs, 13)
    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 currentFragmentCount = 0
    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 propToScore[propType] then
            totalScore = totalScore + propToScore[propType]
        elseif propType == PropEnum.MapFragment then
            currentFragmentCount = currentFragmentCount + 1
            if currentFragmentCount == fragmentsCountToMap then
                totalScore = totalScore + propToScore[PropEnum.TreasureBox]
                currentFragmentCount = 0
            end
        elseif propType == PropEnum.Magnifier then
            local indexs = { }
            for cellIndex, cell in pairs(board) do
                if cell.cellNum ~= InvalidNum and (cell.tag == PropEnum.Gold or cell.tag == PropEnum.ManyGolds) then
                    table.insert(indexs, cellIndex)
                end
            end
            shuffleTable(indexs)
            local remainingCount = magnifierEffectCells
            while remainingCount > 0 and #indexs > 0 do
                local cellIndex = table.remove(indexs, 1)
                totalScore = totalScore + propToScore[board[cellIndex].tag]

                board[cellIndex]:resetTag()
                remainingCount = remainingCount - 1
            end
        end

        local ballStr = string.format("读球: %d --- %d --- %d", readIndex, ballNum, readBallCount)
        if DebugOut then
            PrintCurrentState(board, totalScore, ballStr, string.format("fragments count: %d", currentFragmentCount))
        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)