编辑代码

-- 一个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  第五行
-- }
--[[
255 雪山攀登主题
- 从起点走到终点
- 道具 骰子 移动1格
- 道具 饮料 1格+1 2格+2 3格x2   都只触发一次,触发翻倍后清空
- 道具 滑板 将人送到行末 每行最后一个点不触发,转移 滑板遇到锁链,直接上锁链
- 道具 锁链 前往下一行 道具不出现在人后面 无法转移则原地出现
]]

local DIR_TYPE = {
    LEFT = 0,          -- 向左
    RIGHT = 1,         -- 向右
}

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

local PROP_TYPE = {
    NONE = 0,            -- 空
    DRINKS = 1,          -- 饮料
    SKI = 2,             -- 滑雪板
    CHAIN = 3,           -- 锁链
    DICE_1 = 4,          -- 骰子1
    DICE_2 = 5,          -- 骰子2
    DICE_3 = 6,          -- 骰子3
    DICE_4 = 7,          -- 骰子4
    DICE_5 = 8,          -- 骰子5
    DICE_6 = 9,          -- 骰子6
}
local DICE_POINT = {
    [PROP_TYPE.DICE_1] = 1,
    [PROP_TYPE.DICE_2] = 2,
    [PROP_TYPE.DICE_3] = 3,
    [PROP_TYPE.DICE_4] = 4,
    [PROP_TYPE.DICE_5] = 5,
    [PROP_TYPE.DICE_6] = 6,
}
local function isDice(propType)
    if DICE_POINT[propType] then
        return true
    end
    return false
end
local ENERGY_MAX = 3     --能量上限

local POINT_COL = 6
local POINT_ROW = 6

local DefaultClickCellIndex = nil
--[[********************** 可修改参数 start **********************]]
local COLLECT_TARGET = 30    --bingo目标移动格子数
local PROP_COUNT_LIST = {
    [PROP_TYPE.DRINKS] = 5,
    [PROP_TYPE.SKI] = 2,
    [PROP_TYPE.CHAIN] = 1,
    [PROP_TYPE.DICE_1] = 4,
    [PROP_TYPE.DICE_2] = 3,
    [PROP_TYPE.DICE_3] = 2,
    [PROP_TYPE.DICE_4] = 1,
    [PROP_TYPE.DICE_5] = 1,
    [PROP_TYPE.DICE_6] = 1,
}

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

local TableUtil = {}
TableUtil.shuffle = function(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

local function index2Coord(index)
    return math.ceil(index / MAX_COL), (index - 1) % MAX_COL + 1
end

local function coord2Index(row, col)
    return (row - 1) * MAX_COL + col
end

--将card coord转为point coord
local function cardCoord2pointCoord(row, col)
    return MAX_ROW - row,col - 1
end

--需要转换为point使用的坐标 原点在左下角(0, 0)
local function pointCoord2poinIndex(row, col)
    if row % 2 == 1 then col = POINT_COL - 1 - col end
    return row * POINT_COL + col
end

local function pointIndex2pointCoord(index)
    local col = index % POINT_COL
    local row = math.floor(index / POINT_COL)
    if row % 2 == 1 then col = POINT_COL - 1 - col end
    return row, col
end

--从0开始
local function getPointDir(point)
    local y = math.floor(point / POINT_COL)
    return y % 2 == 1 and DIR_TYPE.LEFT or DIR_TYPE.RIGHT
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, MAX_ROW, 1 do
        for col = 1, MAX_COL, 1 do
            local cellIndex = coord2Index(row, col)
            local cell = value[cellIndex]
            str = str .. " " .. cell
        end
        if row ~= MAX_ROW then
            str = str .. "\r\n"
        end
    end
    print(str)
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

local function getOneCell(cellNum, cellType)
    local cell = { }
    cell.mNum_ = cellNum
    cell.mType_ = cellType
    function cell:getDescription()
        local cellNumStr = "  "
        if self.mNum_ ~= INVALID_NUM 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_ ~= INVALID_NUM, "Cell has been tagged.")
        self.mNum_ = INVALID_NUM
        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, MAX_ROW, 1 do
        for j = 1, MAX_COL, 1 do
            local cellIndex = (i - 1) * MAX_COL + j
            local cell = board[cellIndex]
            str = str .. string.format("%s    ", cell:getDescription())
        end
        if i ~= MAX_ROW then
            str = str .. "\r\n"
        end
    end
    if ballStr ~= INVALID_NUM 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 getOneBoardConfig()
    local propConfig = {}

    local allProp = {}
    for pType, pCount in pairs(PROP_COUNT_LIST) do
        for idx = 1, pCount, 1 do
            table.insert(allProp, pType)
        end
    end
    for idx = 1, MAX_ROW * MAX_COL, 1 do
        propConfig[idx] = allProp[idx] or PROP_TYPE.NONE
    end
    TableUtil.shuffle(propConfig)

    return propConfig
end

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

    -- generate tag.
    local _markList = {}
    local _isBingo = 0
    local _totaleScore = 0
    local _scoreWhen15 = 0
    local _readBallCount = 0
    local _collections = 0

    for i = 1, MAX_ROW * MAX_COL do
        _markList[i] = GRID_STATE.NONE
    end

    local _rolePoint = 0
    local _waitPropList = {}
    local _energyCollect = 0     -- >0收集到能量未使用 <0收集能量已使用

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


    local function checkUseChain()
        if _rolePoint >= COLLECT_TARGET then
            return false
        end

        local isUse = false
        for index, propInfo in ipairs(_waitPropList) do
            if propInfo.gridType == PROP_TYPE.CHAIN then
                if _rolePoint == propInfo.startP1 then
                    _rolePoint = propInfo.endP1
                    table.remove(_waitPropList, index)
                    isUse = true
                    checkUseChain()
                    break
                elseif _rolePoint == propInfo.startP2 then
                    _rolePoint = propInfo.endP2
                    table.remove(_waitPropList, index)
                    isUse = true
                    checkUseChain()
                    break
                end
            end
        end
        return isUse
    end

    local function checkAddDrinks()
        if _rolePoint >= COLLECT_TARGET then
            return false
        end

        for index, propInfo in ipairs(_waitPropList) do
            if propInfo.gridType == PROP_TYPE.DRINKS then
                if _energyCollect >= ENERGY_MAX then
                    return false
                else
                    _energyCollect = math.abs(_energyCollect) + 1
                    table.remove(_waitPropList, index)
                    return true
                end
            end
        end
    end

    local function checkUseDice()
        if _rolePoint >= COLLECT_TARGET then
            return 0
        end


        for index, propInfo in ipairs(_waitPropList) do
            local moveCount = DICE_POINT[propInfo.gridType]
            if moveCount then
                table.remove(_waitPropList, index)

                if _energyCollect > 0 then
                    if _energyCollect == 3 then
                        moveCount = moveCount * 2
                        _energyCollect = 0
                    else
                        moveCount = moveCount + _energyCollect
                        _energyCollect = - _energyCollect
                    end
                end

                local realCount = 0
                for i = 1, moveCount do
                    if _rolePoint < COLLECT_TARGET then
                        _rolePoint = _rolePoint + 1
                        realCount = realCount + 1
                        checkUseChain()
                    end
                end
                return realCount
            end
        end
    end

    local function checkUseSki()
        if _rolePoint >= COLLECT_TARGET then
            return false
        end

        local isUse = false
        local moveCount = nil
        local row, col = pointIndex2pointCoord(_rolePoint)

        for index, propInfo in ipairs(_waitPropList) do
            if propInfo.gridType == PROP_TYPE.SKI then
                if row % 2 == 1 then
                    isUse = true
                    moveCount = col
                    table.remove(_waitPropList, index)
                else
                    isUse = true
                    moveCount = POINT_COL - col - 1
                    table.remove(_waitPropList, index)
                end
                break
            end
        end

        if moveCount then
            for i = 1, moveCount do
                _rolePoint = _rolePoint + 1
                if checkUseChain() then
                    break
                end
            end
        end
        return isUse
    end

    local function checkUseProp()
        local isUseProp = false
        for index, propInfo in ipairs(_waitPropList) do
            if propInfo.gridType == PROP_TYPE.DRINKS then
                if checkAddDrinks() then
                    isUseProp = true
                    break
                end
            elseif propInfo.gridType == PROP_TYPE.CHAIN then
                if checkUseChain() then
                    isUseProp = true
                    break
                end
            elseif propInfo.gridType == PROP_TYPE.SKI then
                if checkUseSki() then
                    isUseProp = true
                    break
                end
            elseif isDice(propInfo.gridType) then
                local moveCount = checkUseDice()
                if moveCount > 0 then
                    isUseProp = true
                    break
                end
            end
        end
        
        if isUseProp then
            checkUseProp()
        end
    end

    local function removeOnlyGrid(gridIndex)
        if _markList[gridIndex] == GRID_STATE.MARK then
            return
        end
        _markList[gridIndex] = GRID_STATE.MARK
        local propType = _propConfig[gridIndex]

        --该主题只能按队列使用道具,将道具放入队列按顺序逐个检查使用
        if propType == PROP_TYPE.CHAIN then
            local row, rol = index2Coord(gridIndex)
            local pointRow, pointRol = cardCoord2pointCoord(row, rol)

            local startP1, startP2 = pointCoord2poinIndex(pointRow, pointRol), pointCoord2poinIndex(pointRow, pointRol + 1)
            local endP1, endP2 = pointCoord2poinIndex(pointRow + 1, pointRol), pointCoord2poinIndex(pointRow + 1, pointRol + 1)

            --同一行开出后则不再开出
            local isOpen = false
            -- for i = 1, MAX_ROW do
            --     local index = coord2Index(i, pointRol)
            --     if index ~= gridIndex and _markList[index] == GRID_STATE.MARK and _propConfig[index] == PROP_TYPE.CHAIN then
            --         isOpen = true
            --         break
            --     end
            -- end
            --如果在人后面标出则转移
            if ((_rolePoint > startP1 and _rolePoint > startP2) or gridIndex <= 5 or isOpen) then
                local emptyList = {}
                for index = 1, CELL_COUNT do
                    if _markList[index] ~= GRID_STATE.MARK and _propConfig[index] ~= PROP_TYPE.CHAIN then
                        table.insert(emptyList, index)
                    end
                end
                TableUtil.shuffle(emptyList)
                --如果有满足交换条件的就叫唤否则不交换
                if emptyList[1] then
                    _propConfig[gridIndex] = _propConfig[emptyList[1]]
                    _propConfig[emptyList[1]] = PROP_TYPE.CHAIN
                    propType = _propConfig[gridIndex]
                end
                propType = _propConfig[gridIndex]
            else
                table.insert(_waitPropList, {gridIndex = gridIndex, gridType = propType, startP1 = startP1, startP2 = startP2, endP1 = endP1, endP2 = endP2})
            end
        end

        if propType == PROP_TYPE.SKI then
            --在对应行的最后一格需要转移
            local row, col  = pointIndex2pointCoord(_rolePoint)
            local needChange = false
            if row % 2 == 1 then
                if col < 1 then
                    needChange = true
                end
            else
                if col > 4 then
                    needChange = true
                end
            end
            if needChange then
                local emptyList = {}
                for index = 1, CELL_COUNT do
                    if _markList[index] ~= GRID_STATE.MARK and _propConfig[index] ~= PROP_TYPE.NONE then
                        table.insert(emptyList, index)
                    end
                end
                TableUtil.shuffle(emptyList)
                if emptyList[1] then
                    _propConfig[gridIndex] = _propConfig[emptyList[1]]
                    _propConfig[emptyList[1]] = PROP_TYPE.SKI
                    propType = _propConfig[gridIndex]
                end
                propType = _propConfig[gridIndex]
            else
                table.insert(_waitPropList, {gridIndex = gridIndex, gridType = propType})
            end
        end

        if propType == PROP_TYPE.DRINKS then
            table.insert(_waitPropList, {gridIndex = gridIndex, gridType = propType})
        end

        if isDice(propType) then
            table.insert(_waitPropList, {gridIndex = gridIndex, gridType = propType})
        end

        checkUseProp()

        if DebugOut then
            dump_board(_markList, gridIndex .. " " .. propType .. " " .. _rolePoint)
        end
        
        _collections = _rolePoint
    end

    --算法逻辑
    local function updateLogic(gridIndex)
        removeOnlyGrid(gridIndex)
    end
    
    local function checkBingo()
        if _totaleScore >= COLLECT_TARGET then
            _isBingo = 1
            return true
        end
        return false
    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

        _totaleScore = _collections

        if _totaleScore > COLLECT_TARGET then
            _totaleScore = COLLECT_TARGET
        end
        if DebugOut then
            PrintCurrentState(board, _totaleScore, ballNum)
        end
        if _readBallCount <= 15 then
            _scoreWhen15 = _totaleScore
        end

        if checkBingo() then
            break
        end
    end

    return _readBallCount, _scoreWhen15
end

local bingoNeedReadBall = { }
for i = 1, CELL_COUNT 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, CELL_COUNT do
    bingoTotal[i] = bingoNeedReadBall[i] + bingoTotal[i - 1]
end

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