美文网首页
CocosLua3.10 连连看(深度算法和A星算法)

CocosLua3.10 连连看(深度算法和A星算法)

作者: 叫我29就好啦 | 来源:发表于2019-12-05 16:31 被阅读0次

    参考文章:泰课消消乐详解:https://www.taikr.com/article/1967
    参考文章:A星算法:https://blog.csdn.net/zhulichen/article/details/78786493

    主界面:

    用于添加地图,游戏逻辑,处理连线,转消息和数据等。
    ps. 连线这里运用的是改变layout的size,也可以用进度条或者动画等

    
    local InvisibleWall = true  -- 隐藏墙,经过的路(测试用)
    
    local MapLayer = require('app.testFile.event.Key_4_Event_File.Layer.MapLayer')
    local RoadCheckByDepth = require('app.testFile.event.Key_4_Event_File.Data.RoadCheckByDepth')
    local RoadCheckByAStar = require('app.testFile.event.Key_4_Event_File.Data.RoadCheckByAStar')
    
    local event = class('event', cc.Node)
    
    function event:ctor()
        self:initValue()
        self:initLayer()
        self:initData()
    end
    
    function event:initValue()
        self._MapLayer = nil
        self._roadCheck = nil
    end
    
    function event:initLayer()
        self._MapLayer = MapLayer:create(self)
        self:addChild(self._MapLayer)
    
        self._MapLayer:createMap()
    end
    
    function event:initData()
        -- self._roadCheck = RoadCheckByDepth:create(self, {Row = self._MapLayer:getRow(), Column = self._MapLayer:getColumn(), TurnTotalCount = self._MapLayer:getTurnTotalCount()})  -- 深度算法
        self._roadCheck = RoadCheckByAStar:create(self, {Row = self._MapLayer:getRow(), Column = self._MapLayer:getColumn(), TurnTotalCount = self._MapLayer:getTurnTotalCount()})  -- A星算法
        self:addChild(self._roadCheck)
    end
    
    function event:getNode(_x, _y)
        return self._MapLayer:getNode(_x, _y)
    end
    
    function event:getNodeByPos(_pos)
        return self._MapLayer:getNodeByPos(_pos)
    end
    
    function event:getNodeValeByPos(_pos)
        return self._MapLayer:getNodeValeByPos(_pos)
    end
    
    function event:getNodeValue(_x, _y)
        return self._MapLayer:getNodeValue(_x, _y)
    end
    
    -- 检查是否连
    function event:checkIsLink(lastPos, currentPos)
        if not lastPos or not currentPos then
            self:linkReturn(false, currentPos)
            return
        end
    
        local isLink = false
        dump(lastPos, 'lastpos..')
        dump(currentPos, 'currentPos..')
        print('Click..')
    
        self._roadCheck:startRoadCheck(lastPos, currentPos)
    end
    
    -- 查询返回
    function event:linkReturn(_value, cPos)
        self._MapLayer:resetToWall(self._MapLayer:getLastPos(), cPos)
        if _value then
            self:doRemoveTrueReset(cPos)
        else
            self._MapLayer:doClickCurrentNode(cPos)
        end
    end
    
    function event:doRemoveTrueReset(cPos)
        self:removeNode(self._MapLayer:getLastPos(), cPos)
    end
    
    function event:removeNode(pos1, pos2)
        local function endCallback()
            local node1 = self._MapLayer:getNode(pos1.x, pos1.y)
            local node2 = self._MapLayer:getNode(pos2.x, pos2.y)
    
            self._MapLayer:setNodeValue(pos1.x , pos1.y, false)
            self._MapLayer:setNodeValue(pos2.x , pos2.y, false)
    
            if node1 then
                node1:resetToWall()
                node1:setVisible(not InvisibleWall and true or false)
            end
    
            if node2 then
                node2:resetToWall()
                node2:setVisible(not InvisibleWall and true or false)
            end
    
            self:setDefaultState()
        end
    
        dump(self._roadCheck:getLineTable(), 'lineTable')
        self:drawLine(endCallback)
    end
    
    -- 画线
    function event:drawLine(endFunc)
        local lineTable = self._roadCheck:getLineTable()
        if not lineTable or #lineTable <= 1 then
            return
        end
    
        local idx = 1
        local temp = {}
        local function draw(t1, t2)
            local layout = ccui.Layout:create()
            layout:setAnchorPoint(cc.p(0, 0.5))
            layout:setPosition(self._MapLayer:getInterval() * lineTable[t1].x + self._MapLayer:getStartPos().x, self._MapLayer:getInterval() * lineTable[t1].y + self._MapLayer:getStartPos().y)
    
            layout:setContentSize(cc.size(0, 4))
            layout:setBackGroundColorType(ccui.LayoutBackGroundColorType.solid) --设置颜色
            layout:setBackGroundColor(cc.c3b(255, 255, 255))
            layout.progress = 0
    
            self:addChild(layout, 10)
            table.insert(temp, layout)
    
            local r = self:getAngleByPos(lineTable[t1], lineTable[t2])
            layout:setRotation(-r)
    
            local time = 0.002
            local seq = cc.Sequence:create(
                -- cc.DelayTime:create(time),
                cc.CallFunc:create(function()
                    layout.progress = layout.progress + 25
                    layout:setContentSize(cc.size(layout.progress * 0.01 * self._MapLayer:getInterval(), 4))
    
                    if layout.progress == 100 then
                        layout:stopAllActions()
    
                        idx = idx + 1
                        if idx ~= #lineTable then
                            draw(idx, idx+1)
                        else
                            for i, v in ipairs(temp) do
                                v:removeFromParent()
                                temp = {}
                            end
    
                            if endFunc then
                                endFunc()
                            end
                        end
                    end
                end)
            )
            local action = cc.RepeatForever:create(seq)
            layout:runAction(action)
        end
    
        draw(idx, idx+1)
    end
    
    function event:getAngleByPos(pos1, pos2)
        if not pos1 or not pos2 then
            return 0
        end
    
        local p = {}  
        p.x = pos2.x - pos1.x  
        p.y = pos2.y - pos1.y  
    
        local r = math.atan2(p.y, p.x) * 180 / math.pi
        return r
    end
    
    function event:setDefaultState()
        self._MapLayer:setDefaultState()
        self._roadCheck:setDefaultState()
    end
    
    function event:getRoadCheck()
        return self._roadCheck
    end
    
    function event:getInvisibleWall()
        return InvisibleWall
    end
    return event
    
    地图:

    创建游戏行*列个地图节点,创建外部围墙,和可点击事件

    
    local GameNode = require('app.testFile.event.Key_4_Event_File.Node.GameNode')
    
    local Row = 8  -- 行
    local Column = 8  -- 列
    local TypeCount = 6  -- 类型种数
    local Interval = 70  -- 区块间隔
    local Start_Pos = cc.p(160, 0)  --起始坐标
    local TurnTotalCount = 2  -- 限制转弯次数
    
    local MapLayer = class('MapLayer', cc.Node)
    function MapLayer:ctor(_delegate)
        self._delegate = _delegate
        self:initValue()
        self:initData()
    end
    
    function MapLayer:initValue()
        self._isCheck = false  -- 可检查(点击完第一个变为true)
        self._lastPos = nil  -- 上个坐标
    
        self._modelTable = {}  -- 模板表(地图表)
        self._nodeTable = {}  -- 可点击的所有节点表
        self._nodeValueTable = {}  -- 记录位置是否有值
        self._currentClickNode = nil  -- 点击的点(独一个)
    
        self._wallTable = {}  -- 外墙
    end
    
    function MapLayer:initData()
    
    end
    
    function MapLayer:createMap()
        if not self:isCanBeModelMap() then
            print('行列需要组成偶数才行喔xD 重新配吧')
            return
        end
    
        self:createNormalLayer()
        self._modelTable = self:createModelData()
        self:createNode()
        self:createCurrentClickNode()
    end
    
    function MapLayer:createNormalLayer()
        local layout = ccui.Layout:create()
        layout:setContentSize(cc.size(display.width, display.height))
        layout:setTouchEnabled(true)
        self:addChild(layout, 1)
    
        local function callback(sender, eventType)
            if eventType == ccui.TouchEventType.ended then
                self._delegate:setDefaultState()
            end
        end
            
        layout:addTouchEventListener(callback)
    end
    
    function MapLayer:setDefaultState()
        self._lastPos = nil
        self._isCheck = false
        self._currentClickNode:setVisible(false)
    end
    
    function MapLayer:isCanBeModelMap()
        return (Row * Column) % 2 == 0
    end
    
    function MapLayer:createNode()
        if not self._modelTable or #self._modelTable <= 0 then
            return
        end
    
        -- 围墙
        for x = 0, Row+1 do
            for y = 0, Column+1 do
                if x == 0 or x == Row+1 or y == 0 or y == Column+1 then
                    local data = {Value = 0, Pos = cc.p(x, y)}
                    local node = GameNode:create(data)
                    self:addChild(node, 5)
                    node:createWallNode()
                    self:setNode(x, y, node)
                    table.insert(self._wallTable, node)
                    node:setVisible(not self._delegate:getInvisibleWall() and true or false)
                end
            end
        end
    
        -- 数据节点
        for i, v in ipairs(self._modelTable) do
            local x = (i % Row == 0) and Row or i % Row
            local y = (i % Row == 0) and (i / Row) or math.floor(i / Row + 1)
    
            local data = {Value = v, Pos = cc.p(x, y)}
            local node = GameNode:create(data)
            self:addChild(node, 5)
    
            if v ~= -1 then
                node:createNormalNode()
                self:setNode(x, y, node)
                self:setNodeValue(x, y, true)
            else
                node:createWallNode()
                self:setNode(x, y, node)            
            end
        end
    end
    
    function MapLayer:setNode(_x, _y, _node)
        if not _x or not _y or not _node then
            return
        end
    
        local str = string.format('%s_%s', _x, _y)
        self._nodeTable[str] = _node
    end
    
    function MapLayer:getNode(_x, _y)
        if not _x or not _y then
            return
        end
    
        local str = string.format('%s_%s', _x, _y)
        return self._nodeTable[str]
    end
    
    function MapLayer:getNodeByPos(_pos)
        return self:getNode(_pos.x, _pos.y)
    end
    
    function MapLayer:setNodeValue(_x, _y, _value)
        if not _x or not _y then
            return
        end
    
        local str = string.format('%s_%s', _x, _y)
        self._nodeValueTable[str] = _value or false
    end
    
    function MapLayer:getNodeValeByPos(_pos)
        return self:getNodeValue(_pos.x, _pos.y)
    end
    
    function MapLayer:getNodeValue(_x, _y)
        if not _x or not _y then
            return
        end
    
        local str = string.format('%s_%s', _x, _y)
        return self._nodeValueTable[str] and true or false
    end
    
    function MapLayer:createModelData()
        local data = {}
        for i = 1, (Row * Column) / 2 do
            local random = math.random(1, TypeCount)
            table.insert(data, random)
            table.insert(data, random)
        end
    
        local newData = self:upsetData(data)
        return newData
    end
    
    function MapLayer:upsetData(_data)
        local temp = {}
        local idx = 1
        while (idx <= #_data and #temp <= #_data) do
            local random = math.random(1, #_data)
            table.insert(temp, _data[random])
            idx = idx + 1
        end
    
        return temp
    end
    
    function MapLayer:createCurrentClickNode()
        local data = {Value = -1, Pos = cc.p(0, 0)}
        self._currentClickNode = GameNode:create(data)
        self:addChild(self._currentClickNode, 5)
        self._currentClickNode:createCurrentNode()
    end
    
    function MapLayer:doClickEvent(cPos)
        self:resetCurrentClickPosition(cPos)
        self:checkIsTheSecondClick(cPos)
    end
    
    function MapLayer:checkIsTheSecondClick(cPos)
        if self._isCheck and self:checkIsTheSameValue(self._lastPos, cPos) then
            self._delegate:checkIsLink(self._lastPos, cPos)
        else
            -- 没选择前一个
            self:doClickCurrentNode(cPos)
        end
    end
    
    function MapLayer:checkIsTheSameValue(pos1, pos2)
        local node1 = self:getNode(pos1.x, pos1.y)
        local node2 = self:getNode(pos2.x, pos2.y)
    
        if not node1 or not node2 then
            return
        end
    
        return node1:getValue() == node2:getValue()
    end
    
    function MapLayer:resetCurrentClickPosition(cPos)
        if not self._currentClickNode then
            return
        end
    
        self._currentClickNode:setPos(cPos)
        self._currentClickNode:resetPos()
    end
    
    function MapLayer:resetIsCheck()
        self._isCheck = not self._isCheck
    end
    
    
    function MapLayer:doClickCurrentNode(cPos)
        if self._lastPos then
            self:getNodeByPos(self._lastPos):setIsTheNormalColor()
        end
    
        if cPos then
            self:getNodeByPos(cPos):setIsTheNormalColor()
        end
    
        self._currentClickNode:setVisible(true)
        self._lastPos = cPos
        self._isCheck = true
        self._delegate:getRoadCheck():setDefaultState()
    end
    
    -- 重置为可移动的点
    function MapLayer:resetToWall(_lastPos, _currentPos)
        for i, v in ipairs(self._wallTable) do
            v:resetToWall()
        end
    
        if self._delegate:getRoadCheck().getOpenList then
            for i, v in pairs(self._delegate:getRoadCheck():getOpenList()) do
                self:getNodeByPos(v):resetToWall()
            end
        end
    
        if self._delegate:getRoadCheck().getCloseList then
            for i, v in pairs(self._delegate:getRoadCheck():getCloseList()) do
                if not (v.x == _lastPos.x and v.y == _lastPos.y) and not (v.x == _currentPos.x and v.y == _currentPos.y) then  -- 如果不是起点也不是终点
                    self:getNodeByPos(v):resetToWall()
                end
            end
        end
    end
    
    function MapLayer:getStartPos()
        return Start_Pos
    end
    
    function MapLayer:getInterval()
        return Interval
    end
    
    function MapLayer:getRow()
        return Row
    end
    
    function MapLayer:getColumn()
        return Column
    end
    
    function MapLayer:getTurnTotalCount()
        return TurnTotalCount
    end
    
    function MapLayer:getLastPos()
        return self._lastPos
    end
    
    return MapLayer
    
    单个节点
    --[[
        Value = (数值),
        Pos = (位置),
    
        F = G + H + J (权值,3个数值相加。 获得下个计算点是通过最小的F值来获得)
        G (从起点经过父节点到该点的权值)
        H (该点到目标点的权值(估算值))
        J (获得该点的所有下一个移动点(openlist中),如果可移动点与父节点到该点的方向不同,则++1)
    ]]
    
    local NodeSize = cc.size(60, 60)  -- 形块大小
    
    local Color_IsChecked = cc.c3b(255, 0, 0)
    local Color_IsThroughed = cc.c3b(0, 255, 0)
    
    local GameNode = class('GameNode', cc.Node)
    
    function GameNode:ctor(data)
        self._data = data
        self:initValue()
    end
    
    function GameNode:initValue()
        self._layout = nil
    
        self._Value = self._data.Value or 1
        self._Pos = self._data.Pos or cc.p(0, 0)
    
        self._parentPos = nil
    
        self._text = nil
        self._text_F = nil
        self._text_G = nil
        self._text_H = nil
        self._text_J = nil
    
        self._value_G = 0
        self._value_H = 0
        self._value_J = 0
        self._value_F = self._value_G + self._value_H   
    end
    
    function GameNode:createNormalNode()
        self._layout = self:initNode(false, true)
        self:resetPos()
        self:bindEvent()
    end
    
    function GameNode:createWallNode()
        self._layout = self:initNode(true, false)
        self:resetPos()
    end
    
    function GameNode:initNode(isWall, isShowText)
        local layout = ccui.Layout:create()
        layout:setAnchorPoint(cc.p(0.5, 0.5))
        layout:setContentSize(isWall and cc.size(50, 50) or NodeSize)
        layout:setBackGroundColorType(ccui.LayoutBackGroundColorType.solid) --设置颜色
        layout:setBackGroundColor(isWall and cc.c3b(40, 40, 40) or cc.c3b(80, 80, 80))
        layout:setTouchEnabled(true)
        self:addChild(layout)
    
        if isShowText then
            self._text = ccui.Text:create()
            self._text:setString(string.format('%s', self._Value))
            self._text:setFontSize(32)
            self:addChild(self._text, 2)
        end
    
        return layout
    end
    
    function GameNode:setPos(pos)
        if not pos or not pos.x or not pos.y then
            return
        end
    
        self._Pos = pos 
    end
    
    function GameNode:getPos()
        return self._Pos
    end
    
    function GameNode:resetPos()
        self:setPositionX(self:getParent():getStartPos().x + self:getParent():getInterval() * self._Pos.x)
        self:setPositionY(self:getParent():getStartPos().y + self:getParent():getInterval() * self._Pos.y)
    end
    
    function GameNode:bindEvent()
        if not self._layout then
            return
        end
    
        local function callback(sender, eventType)
            if eventType == ccui.TouchEventType.ended then
                self:clickEvent()
            end
        end
            
        self._layout:addTouchEventListener(callback)  
    end
    
    function GameNode:clickEvent()
        self:getParent():doClickEvent(self._Pos)
    end
    
    function GameNode:createCurrentNode()
        self._layout = ccui.ImageView:create('res/frame.png')
        self:addChild(self._layout)
        self:setVisible(false)
    end
    
    function GameNode:getValue()
        return self._Value
    end
    
    function GameNode:showTextF(str)
        if not self._text_F then
            self._text_F = ccui.Text:create()
            self._text_F:setFontSize(18)
            self._text_F:setPosition(-self._layout:getContentSize().width/2 + 6, self._layout:getContentSize().height/2 - 8)
            self:addChild(self._text_F)
        end
    
        self._text_F:setVisible(true)
        self._text_F:setString(string.format('%s', str))
    end
    
    function GameNode:showTextG(str)
        if not self._text_G then
            self._text_G = ccui.Text:create()
            self._text_G:setFontSize(18)
            self._text_G:setPosition(-self._layout:getContentSize().width/2 + 6, -self._layout:getContentSize().height/2 + 8)
            self:addChild(self._text_G)
        end
    
        self._text_G:setVisible(true)
        self._text_G:setString(string.format('%s', str))
    end
    
    function GameNode:showTextH(str)
        if not self._text_H then
            self._text_H = ccui.Text:create()
            self._text_H:setFontSize(18)
            self._text_H:setPosition(self._layout:getContentSize().width/2 - 6, -self._layout:getContentSize().height/2 + 8)
            self:addChild(self._text_H)
        end
    
        self._text_H:setVisible(true)
        self._text_H:setString(string.format('%s', str))
    end
    
    function GameNode:showTextJ(str)
        if not self._text_J then
            self._text_J = ccui.Text:create()
            self._text_J:setFontSize(18)
            self._text_J:setPosition(self._layout:getContentSize().width/2 - 6, self._layout:getContentSize().height/2 - 8)
            self:addChild(self._text_J)
        end
    
        self._text_J:setVisible(true)
        self._text_J:setString(string.format('%s', str))
    end
    
    function GameNode:showText()
        self:showTextG(self._value_G)
        self:showTextH(self._value_H)
        self:showTextJ(self._value_J)
        self:showTextF(self._value_F)
    end
    
    function GameNode:setText(_g, _h, _j)
        self._value_G, self._value_H, self._value_J, self._value_F = _g, _h, _j, _g+_h+_j
    end
    
    function GameNode:getFValue()
        return self._value_F
    end
    
    function GameNode:getGValue()
        return self._value_G
    end
    
    function GameNode:getJValue()
        return self._value_J
    end
    
    function GameNode:getAngleByPos(pos1, pos2)
        if not pos1 or not pos2 then
            return 0
        end
    
        local p = {}  
        p.x = pos2.x - pos1.x  
        p.y = pos2.y - pos1.y  
    
        local r = math.atan2(p.y, p.x) * 180 / math.pi
        return r
    end
    
    function GameNode:setParentPos(pos)
        self._parentPos = pos
    end
    
    function GameNode:getParentPos()
        return self._parentPos
    end
    
    -- 测试用,设置为查找,红色(A星查找)
    function GameNode:setIsCheckedColor()
        self._layout:setBackGroundColor(Color_IsChecked)
    end
    
    -- 测试用,设置为最终经过,绿色(A星查找)
    function GameNode:setIsThroughedColor()
        self._layout:setBackGroundColor(Color_IsThroughed)
    end
    
    -- 设置为普通
    function GameNode:setIsTheNormalColor()
        self._layout:setBackGroundColor(cc.c3b(80, 80, 80))
    end
    
    -- 改变为墙
    function GameNode:resetToWall()
        self._layout:setBackGroundColor(cc.c3b(40, 40, 40))
        self._layout:setTouchEnabled(false)
    
        self._value_G, self._value_H, self._value_F = 0, 0, 0
    
        if self._text_F then
            self._text_F:setVisible(false)
        end
        if self._text_G then
            self._text_G:setVisible(false)
        end
        if self._text_H then
            self._text_H:setVisible(false)
        end
        if self._text_J then
            self._text_J:setVisible(false)
        end
        if self._text then
            self._text:setVisible(false)
        end
    
        if self._ptext then
            self._ptext:setVisible(false)
        end
    end
    
    -- 获得父节点到这个节点的方向
    function GameNode:getDirInParentToHere()
        if not self._parentPos then
            return
        end
    
        if self._Pos.x - self._parentPos.x > 0 then
            return 'right'
        elseif self._Pos.x - self._parentPos.x < 0 then
            return 'left'
        elseif self._Pos.y - self._parentPos.y < 0 then
            return 'down'
        elseif self._Pos.y - self._parentPos.y > 0 then
            return 'up'
        end
    
        return
    end
    
    -- 显示指向父节点的text ui
    function GameNode:showParentTag()
        if not self._ptext then
            self._ptext = ccui.Text:create()
            self._ptext:setString(string.format('个'))
            self._ptext:setFontSize(26)
            self:addChild(self._ptext, 2)
        end
    
        local angle = self:getAngleByPos(self._Pos, self._parentPos)
        self._ptext:setVisible(true)
        self._ptext:setRotation(-angle + 90)
    end
    
    return GameNode
    

    算法相关

    深度算法 查看checkRoadLink()方法:

    深度算法是我使用的第一种写法,代码中包含两种写法。
    1.一种是获得终点后,直接把经过的点(路,只记录最终经过的。途中经过的会被for给返回,不添加到currentVisit内),然后直接赋值给self._lineTable,直接返回
    2.另一种是找到终点后,递归返回添加到self._lineTable数据里面。

    ps. xD因为第二种是我一开始写的,就一直保留着了,然后通过高人学习到第一种也可以。两种比较后,感觉第1种比较好理解。
    ps+. 代码中有很多showPrint,showDump等测试代码,可以通过关闭开关ShowPrint,不打印这些测试日志。

    local Row = 0
    local Column = 0
    local TurnTotalCount = 0
    local ShowPrint = false  -- 打印,不用在意在意这个,其实就是print和dump,加个开关打印测试日志而已
    
    local RoadCheckByDepth = class('RoadCheckByDepth', cc.Node)
    
    function RoadCheckByDepth:ctor(delegate, data)
        self._delegate = delegate
        self._data = data
        self:initValue()
    end
    
    function RoadCheckByDepth:initValue()
        self._startPos = nil
        self._endPos = nil
    
        self._lineTable = {}
        self._currentStep = -1
        Row = self._data.Row or 0
        Column = self._data.Column or 0
        TurnTotalCount = self._data.TurnTotalCount or 0
    end
    
    function RoadCheckByDepth:startRoadCheck(lastPos, currentPos)
        self._startPos = lastPos
        self._endPos = currentPos
        self:checkRoadLink(lastPos, currentPos, 0, 1, 0)
    
        self._delegate:linkReturn(self._currentStep > 0, self._endPos)
    end
    
    -- 通过改变pos1,来找到是否与pos2匹配
    --[[
        currentVisit = {
            visitPosTableInKey = {}  -- 记录保存过的坐标(放于key中,无序)
            visitPosTableInValue = {}   -- 记录保存过的坐标(放于Value中,有序)
        }
    ]]
    
    -- 通过传的当前遍历顺序,直接遇到结果后直接插入(深度遍历算法)
    function RoadCheckByDepth:checkRoadLink(pos1, pos2, count, direction, index, currentVisit)
        local x1, y1 = pos1.x, pos1.y
        local x2, y2 = pos2.x, pos2.y
    
        local dirText = {'左', '上', '右', '下'}
        self:showPrint('调用GameLayer:checkRoadLink 当前位置是', 'x='..x1, 'y='..y1, '当前弯数='..count, '当前方向='..dirText[direction], '当前步数='..index)
    
        if x1 < 0 or x1 > Row+1 or y1 < 0 or y1 > Column+1 then  -- 不允许超出界外两格检测
            self:showPrint('return false 超出界面 *****************************')
            return false
        end
    
        if count then
            if count > TurnTotalCount then
                self:showPrint('return false 超出转弯数 *****************************')
                return false
            end
        end
    
        if index then
            if self._currentStep ~= -1 and index >= self._currentStep then
                self:showPrint('return false 步数比上个太多 *****************************')
                return false
            end
        end
    
        -- 记录当前访问过的数据(这里只计划记录之前所保存的,而不是包括其他的时候,如果想把其他情况给记录,如果希望如此,则把下面的cVisit.visitPosTableInKey[keyStr] = nil注释掉即可)
        local cVisit = currentVisit and currentVisit or {visitPosTableInKey = {}, visitPosTableInValue = {}}
        local isStartPos = (self._startPos.x == x1 and self._startPos.y == y1)
        if not isStartPos and self._delegate:getNodeValue(x1, y1) then
            if x1 == x2 and y1 == y2 then
                self:showPrint('有节点 并且遇到最终目标')
                self:showPrint('return true 两值相等 *****************************',  '当前是', x1, y1)
                self:showPrint('清除所有self._lineTable !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!')
                self._lineTable = {}
    
                self:showPrint('添加数据', pos1.x, pos1.y,'++++++++++++++++++++++++++++++++==')
    
                table.insert(cVisit.visitPosTableInValue, pos1)
                self._lineTable = clone(cVisit.visitPosTableInValue)
                table.remove(cVisit.visitPosTableInValue, #cVisit.visitPosTableInValue)
    
                self._currentStep = index + 1
                self:showPrint('一共', self._currentStep, '步')
    
                self:showDump(cVisit, 'cVisit>>>>')
    
                return true
            end
    
            self:showPrint('有节点 没有遇到最终目标')
            return false
        end
    
        local keyStr = string.format('%s_%s', x1, y1)
        if cVisit.visitPosTableInKey[keyStr] then
            self:showPrint('return false 该节点访问过了 *******************************************************', keyStr)
            return false
        end
        cVisit.visitPosTableInKey[keyStr] = true
        table.insert(cVisit.visitPosTableInValue, pos1)
    
        local temp = {cc.p(x1-1, y1), cc.p(x1, y1+1), cc.p(x1+1, y1), cc.p(x1, y1-1)}   -- 左上右下
        for i, v in ipairs(temp) do
            self:showPrint('\n')
            self:showPrint('执行循环', i, '当前是', x1, y1)
    
            local keyStr = string.format('%s_%s', v.x, v.y)
    
            if not isStartPos then
                if direction ~= i then
                    self:showPrint('direction是', direction, 'i是', i, '不相等 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~')
                    self:showPrint('index = ', index, '当前是', x1, y1)
    
                    self:checkRoadLink(v, pos2, count+1, i, index + 1, cVisit)
                else
                    self:showPrint('direction是', direction, 'i是', i, '相等 ==================================')
                    self:showPrint('index = ', index, '当前是', x1, y1)
                    self:checkRoadLink(v, pos2, count, i, index + 1, cVisit)
                end
            else
                self:checkRoadLink(v, pos2, count, i, index + 1, cVisit)
            end
        end
    
        cVisit.visitPosTableInKey[keyStr] = nil
        table.remove(cVisit.visitPosTableInValue, #cVisit.visitPosTableInValue)
    
        self:showPrint('检查完毕', self._currentStep > 0)
        return false
    end
    
    -- 通过isEnd来判断是否结束,添加数据
    -- function RoadCheckByDepth:checkRoadLink(pos1, pos2, count, direction, index, currentVisit)
    --  local x1, y1 = pos1.x, pos1.y
    --  local x2, y2 = pos2.x, pos2.y
    
    --  local dirText = {'左', '上', '右', '下'}
    --  self:showPrint('调用GameLayer:checkRoadLink 当前位置是', 'x='..x1, 'y='..y1, '当前弯数='..count, '当前方向='..dirText[direction], '当前步数='..index)
    
    --  if x1 < 0 or x1 > Row+1 or y1 < 0 or y1 > Column+1 then  -- 不允许超出界外两格检测
    --      self:showPrint('return false 超出界面 *****************************')
    --      return false
    --  end
    
    --  if count then
    --      if count > TurnTotalCount then
    --          self:showPrint('return false 超出转弯数 *****************************')
    --          return false
    --      end
    --  end
    
    --  if index then
    --      if self._currentStep ~= -1 and index >= self._currentStep then
    --          self:showPrint('return false 步数比上个太多 *****************************')
    --          return false
    --      end
    --  end
    
    --  local isStartPos = (self._startPos.x == x1 and self._startPos.y == y1)
    --  if not isStartPos and self._delegate:getNodeValue(x1, y1) then
    --      if x1 == x2 and y1 == y2 then
    --          self:showPrint('有节点 并且遇到最终目标')
    --          self:showPrint('return true 两值相等 *****************************',  '当前是', x1, y1)
    --          self:showPrint('清除所有self._lineTable !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!')
    --          self._lineTable = {}
    
    --          self:showPrint('添加数据', pos1.x, pos1.y,'++++++++++++++++++++++++++++++++==')
    --          table.insert(self._lineTable, 1, pos1)
    
    --          self._currentStep = index + 1
    --          self:showPrint('一共', self._currentStep, '步')
    
    --          self:showDump(currentVisit, 'currentVisit>>>>')
    
    --          return true
    --      end
    
    --      self:showPrint('有节点 没有遇到最终目标')
    --      return false
    --  end
        
    --  -- 记录当前访问过的数据(这里只计划记录之前所保存的,而不是包括其他的时候,如果想把其他情况给记录,如果希望如此,则把下面的cVisit.visitPosTableInKey[keyStr] = nil注释掉即可)
    --  local cVisit = currentVisit and currentVisit or {visitPosTableInKey = {}, visitPosTableInValue = {}}
    --  local keyStr = string.format('%s_%s', x1, y1)
    --  if cVisit.visitPosTableInKey[keyStr] then
    --      self:showPrint('return false 该节点访问过了 *******************************************************', keyStr)
    --      return false
    --  end
    --  cVisit.visitPosTableInKey[keyStr] = true
    --  table.insert(cVisit.visitPosTableInValue, pos1)
    
    --  local temp = {cc.p(x1-1, y1), cc.p(x1, y1+1), cc.p(x1+1, y1), cc.p(x1, y1-1)}   -- 左上右下
    --  local isEnd = false
    --  local tag = false
    
    --  for i, v in ipairs(temp) do
    --      self:showPrint('\n')
    --      self:showPrint('执行循环', i, '当前是', x1, y1)
    
    --      local keyStr = string.format('%s_%s', v.x, v.y)
    
    --      if not isStartPos then
    --          if direction ~= i then
    --              self:showPrint('direction是', direction, 'i是', i, '不相等 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~')
    --              self:showPrint('index = ', index, '当前是', x1, y1)
    
    --              tag = self:checkRoadLink(v, pos2, count+1, i, index + 1, cVisit)
    --              isEnd = isEnd and isEnd or tag  -- 当isEnd为true就一直保持为true状态
    --              self:showPrint('isEnd', isEnd)
    --          else
    --              self:showPrint('direction是', direction, 'i是', i, '相等 ==================================')
    --              self:showPrint('index = ', index, '当前是', x1, y1)
    --              tag = self:checkRoadLink(v, pos2, count, i, index + 1, cVisit)
    --              isEnd = isEnd and isEnd or tag  -- 当isEnd为true就一直保持为true状态
    --          end
    --      else
    --          tag = self:checkRoadLink(v, pos2, count, i, index + 1, cVisit)
    --          isEnd = isEnd and isEnd or tag  -- 当isEnd为true就一直保持为true状态
    --      end
    --  end
        
    --  if self._currentStep > 0 and isEnd then
    --      self:showPrint('添加数据', pos1.x, pos1.y,'++++++++++++++++++++++++++++++++==')
    --      table.insert(self._lineTable, 1, pos1)
    --  end
    
    --  cVisit.visitPosTableInKey[keyStr] = nil
    --  table.remove(cVisit.visitPosTableInValue, #cVisit.visitPosTableInValue)
    
    --  self:showPrint('检查完毕', self._currentStep > 0)
    --  return isEnd, (self._currentStep > 0)
    -- end
    
    
    function RoadCheckByDepth:getLineTable()
        return self._lineTable
    end
    
    function RoadCheckByDepth:getCurrentStep()
        return self._currentStep
    end
    
    function RoadCheckByDepth:setDefaultState()
        self._lineTable = {}
        self._currentStep = -1
    end
    
    function RoadCheckByDepth:showPrint(...)
        if not ShowPrint then
            return
        end
    
        print(...)
    end
    
    function RoadCheckByDepth:showDump(...)
        if not ShowPrint then
            return
        end
    
        dump(...)
    end
    return RoadCheckByDepth
    
    A星算法

    ps.代码中有很多测试开关
    1.ShowTest 打印一些测试区块,包括A星算法的寻找路线等。
    2.UseDelay 运用延迟寻找,方便测试查看当前是到达哪个位置查找

    local OneRoadValue = 10
    local Row = 0
    local Column = 0
    local TurnTotalCount = 0
    
    -- 测试相关
    local ShowTest = false
    local UseDelay = false  -- 是否启动延迟
    local DelayTime = 3  -- 每一个移动的延迟(最好搭配开启showTest一起使用)
    
    local RoadCheckByAStar = class('RoadCheckByAStar', cc.Node)
    
    function RoadCheckByAStar:ctor(delegate, data)
        self._delegate = delegate
        self._data = data
        self:initValue()
    end
    
    function RoadCheckByAStar:initValue()
        self._startPos = nil
        self._endPos = nil
    
        self._openList = {}
        self._closeList = {}
    
        self._lineTable = {}  -- 通过的表
        Row = self._data.Row or 0
        Column = self._data.Column or 0
        TurnTotalCount = self._data.TurnTotalCount or 0
    end
    
    function RoadCheckByAStar:startRoadCheck(_startPos, _endPos)
        self._startPos = _startPos
        self._endPos = _endPos
    
        self:checkRoadLink(self._startPos)
    end
    
    function RoadCheckByAStar:addToOpenTable(pos)
        local str = string.format('%s_%s', pos.x, pos.y)
        self._openList[str] = pos
    end
    
    function RoadCheckByAStar:checkIsInOpenTable(pos)
        local str = string.format('%s_%s', pos.x, pos.y)
        return self._openList[str]
    end
    
    function RoadCheckByAStar:removeInOpenTable(pos)
        local str = string.format('%s_%s', pos.x, pos.y)
        self._openList[str] = nil
    end
    
    function RoadCheckByAStar:addToCloseTable(pos)
        local str = string.format('%s_%s', pos.x, pos.y)
        self._closeList[str] = pos
    end
    
    function RoadCheckByAStar:checkIsInCloseTable(pos)
        local str = string.format('%s_%s', pos.x, pos.y)
        return self._closeList[str]
    end
    
    -- 找到open表中最小的f值
    function RoadCheckByAStar:getMinFPosInOpenTable()
        if table.nums(self._openList) == 0 then
            return false
        end
    
        local minPos = cc.p(0, 0)
        local minF = 99999
        for i, v in pairs(self._openList) do
            local node = self._delegate:getNodeByPos(v)
            if node:getFValue() < minF then
                minF = node:getFValue()
                minPos = v
            end
        end
    
        return minPos
    end
    
    -- 获得当前node的g值,根据当前寻路的节点获得
    function RoadCheckByAStar:getGValueInThroughRoad(pPos)
        local gValue = OneRoadValue
        local node = self._delegate:getNodeByPos(pPos)
        gValue = gValue + node:getGValue()
    
        return gValue
    end
    
    function RoadCheckByAStar:isInEnd()
        return self:checkIsInOpenTable(self._endPos)
    end
    
    function RoadCheckByAStar:isEndPos(_pos)
        return self._endPos.x == _pos.x and self._endPos.y == _pos.y
    end
    
    function RoadCheckByAStar:setLineTable()
        local pos = self._endPos
        local endNode = self._delegate:getNodeByPos(pos)
        if ShowTest then
            endNode:setIsThroughedColor()
        end
        table.insert(self._lineTable, pos)  -- 添加终点进去
        -- 将终点从openlist中移除,移到closelist里面(方便主界面画线)
        self:addToCloseTable(pos)
        self:removeInOpenTable(pos) 
    
        local action = nil
        local function fun()
            local node = self._delegate:getNodeByPos(pos)
            local pPos = node:getParentPos()
            local pNode = self._delegate:getNodeByPos(pPos)
    
            if ShowTest then
                pNode:setIsThroughedColor()
            end
            table.insert(self._lineTable, pPos)
    
            if pPos.x == self._startPos.x and pPos.y == self._startPos.y then
                if action then
                    self:stopAction(action)
                end
    
                -- 因为A星是从终点遍历到终点,所以需要倒序一下
                local temp = {}
                for i = #self._lineTable, 1, -1 do
                    table.insert(temp, self._lineTable[i])
                end
                self._lineTable = temp
    
                self._delegate:linkReturn(#self._lineTable > 0, self._endPos)
                return true
            end
    
            pos = pNode:getPos()
    
            -- 不带延迟调用
            if not UseDelay then
                fun()
            end
        end
    
        -- 带延迟调用
        if UseDelay then
            action = schedule(self, fun, DelayTime)
        else
            -- 不带延迟调用
            fun()
        end
    end
    
    -- 添加到open表内
    function RoadCheckByAStar:checkRoadLink(currentPos)
        if self:isInEnd() then  -- 如果到达了终点
            self:setLineTable()
            return
        end
    
        -- 添加到closeList,在openList中移除,插入到遍历表中
        self:addToCloseTable(currentPos)
        self:removeInOpenTable(currentPos)
    
        -- 改变当前的颜色
        local cNode = self._delegate:getNodeByPos(currentPos)
        if ShowTest then
            cNode:setIsCheckedColor()
        end
    
        local x, y = currentPos.x, currentPos.y
        local temp = {cc.p(x, y+1), cc.p(x, y-1), cc.p(x-1, y), cc.p(x+1, y)}  -- 上下左右
    
        for i, v in ipairs(temp) do
            local node = self._delegate:getNodeByPos(v)
            if node then
                if (not self:checkIsInCloseTable(v)) and (self:isCanMoveInSpeicalRule(v, currentPos)) then  -- 如果不处于closelist里面,并且满足了特殊规则
                    if not self._delegate:getNodeValeByPos(v) or self:isEndPos(v) then  -- 如果该路没有值(即非正常方块,或者是结束点)
                        -- 检查是否在openlist中
                        if self:checkIsInOpenTable(v) then
                            if self:getGValueInThroughRoad(currentPos) < node:getGValue() or self:getJValueInThroughRoad(currentPos, v) < node:getJValue() then  -- 如果现在经过的点,比之前经过的点,G值还小,重新设置父节点和FGH值
                                node:setParentPos(currentPos)
                                local g = self:getGValueInThroughRoad(currentPos)
                                local h = (math.abs(v.x - self._endPos.x) + math.abs(v.y - self._endPos.y)) * OneRoadValue
                                local j = self:getJValueInThroughRoad(currentPos, v)
                                node:setText(g, h, j)
                                if ShowTest then
                                    node:showText()
                                    node:showParentTag()
                                end     
                            end
                        else
                            self:addToOpenTable(v)
                            node:setParentPos(currentPos)
                            local g = self:getGValueInThroughRoad(currentPos)
                            local h = (math.abs(v.x - self._endPos.x) + math.abs(v.y - self._endPos.y)) * OneRoadValue
                            local j = self:getJValueInThroughRoad(currentPos, v)
                            node:setText(g, h, j)
                            if ShowTest then
                                node:showText()
                                node:showParentTag()
                            end                     
                        end
                    end
                end
            end
        end
    
        local pos = self:getMinFPosInOpenTable()
        if not pos then
            self._delegate:linkReturn(#self._lineTable > 0, self._endPos)
            return 
        end
    
        -- 带延迟调用
        if UseDelay then
            local function fun()
                self:checkRoadLink(pos)
            end
            performWithDelay(self, fun, DelayTime)
        else
            -- 不带延迟调用
            self:checkRoadLink(pos)
        end
    end
    
    function RoadCheckByAStar:isCanMoveInSpeicalRule(_pos, _lastPos)
        if _pos.x < 0 or _pos.x > Row+1 or _pos.y < 0 or _pos.y > Column+1 then  -- 不允许超出界外两格检测
            return false
        end
    
        -- 不允许超出弯道n次(定为2次)
        if self:getTurnRoundCount(_pos, _lastPos) > TurnTotalCount then
            return false
        end
    
        return true
    end
    
    function RoadCheckByAStar:getTurnRoundCount(_pos, _lastPos)
        if _lastPos.x == self._startPos.x and _lastPos.y == self._startPos.y then
            return 0
        end
    
        local tCount = 0
        local rDir = nil
        local pos = _lastPos
    
        -- 检查当前与之前的位置
        local lNode = self._delegate:getNodeByPos(pos)
        local lDir = lNode:getDirInParentToHere()
        if lDir ~= self:getDirInPosToPos(_lastPos, _pos) then
            tCount = tCount + 1
        end
    
        local function fun()
            local node = self._delegate:getNodeByPos(pos)
            local dir = node:getDirInParentToHere()
            if not rDir then
                rDir = dir
            else
                if rDir ~= dir then
                    tCount = tCount + 1
                    rDir = dir
                end
            end
    
            local pPos = node:getParentPos()
            if pPos.x == self._startPos.x and pPos.y == self._startPos.y then
                return
            end
    
            pos = pPos
            fun()
        end
    
        fun()
    
        return tCount
    end
    
    function RoadCheckByAStar:getDirInPosToPos(_p1, _p2)
        if _p2.x - _p1.x > 0 then
            return 'right'
        elseif _p2.x - _p1.x < 0 then
            return 'left'
        elseif _p2.y - _p1.y < 0 then
            return 'down'
        elseif _p2.y - _p1.y > 0 then
            return 'up'
        end
    
        return
    end
    
    function RoadCheckByAStar:setDefaultState()
        self._lineTable = {}
        self._openList = {}
        self._closeList = {}
        self._startPos = nil
        self._endPos = nil  
    end
    
    function RoadCheckByAStar:getLineTable()
        return self._lineTable
    end
    
    function RoadCheckByAStar:getOpenList()
        return self._openList
    end
    
    function RoadCheckByAStar:getCloseList()
        return self._closeList
    end
    
    function RoadCheckByAStar:getJValueInThroughRoad(_lastPos, _pos)
        if _lastPos.x == self._startPos.x and _lastPos.y == self._startPos.y then
            return 0
        end
    
        local node = self._delegate:getNodeByPos(_pos)
        local lDir = node:getDirInParentToHere()  -- 获得父节点到这里的方向
        local jValue = 0
        local x, y = _pos.x, _pos.y
        local temp = {cc.p(x, y+1), cc.p(x, y-1), cc.p(x-1, y), cc.p(x+1, y)}  -- 上下左右
        for i, v in ipairs(temp) do
            if (not self:checkIsInCloseTable(v)) and (self:isCanMoveInSpeicalRule(v, _pos)) then  -- 如果不处于closelist里面,并且满足了特殊规则
                if not self._delegate:getNodeValeByPos(v) or self:isEndPos(v) then  -- 如果该路没有值(即非正常方块,或者是结束点)
                    if lDir ~= self:getDirInPosToPos(_pos, v) then
                        jValue = jValue + 1
                    end
                end
            end
        end
        return jValue
    end
    
    return RoadCheckByAStar
    
    消消乐.png
    一些代码后话:

    一开始想用泰课中的分布计算,但是考虑在转2处理转1,并且转1还要处理转0会需要比较多的代码。然后还有考虑到可能连连看会有不一定为2转的情况,所以没有考虑这种写法了。( o(╥﹏╥)o 后面觉得好像不可能会有这种情况啊)
    深度算法写起来比较容易理解
    A星算法写起来需要注意点比较多:
    1.因为A星算法是获得最短路径的算法,但是消消乐需要注意拐弯不能高于2次
    2.A星算法是根据F值来获得并赋予父节点的,所以可能会遇到此情况。图中2,3方块的F值是相等,所以2,3方块谁先走的话,就会被认为为4方块的父节点。如果是2方块为父节点,则最终会找不到结果。


    AStar_1.png

    处理方法:
    --- 1. 如果起点到终点找不到结果,反过来终点到起点试试(xD没测试过,盲猜)
    --- 2. 在A星F = G + H 的基础上,更改为F = G + H + J,J表示该节点的下一个所有可移动点(非closelist中的),获得这些点的移动方向,与父节点到该点的方向做比较。如果不一致,则++1。J为这个权值(就理解为之后可移动的方向,与父节点到这个点移动方向不一致的权值即可)。这样的话,得到的就会为:
    图中点1出发,点2为(G10 H50 J1),因为点2下一个为点4,点2到点4的方向与点1(父节点)到点2(该点)方向不一致),点3为(G50 H50 J1)。此时点2和点3F值相同。假设他先经过了点2,那么会将点4放入openList,并点4的权值各值为(G20 H40 J2 F62(3项相加),点2到点4方向为"右",而点4到可移动点分别为,点5"上",点3"下",所以J为2)。然后从openlist获得下一个最小的F值,即点3。然后发现点4已经在openlist中,并且G值相同,所以则计算J值,该值为0,因为点4可移动的点只有点5,并且点4到点5的方向与点3到点4的方向相同,所以为0。然后经过比较后,把点4的父节点更改为点3,已达到目的。(这里运用的是这种)

    3.或许还会有其他更好的办法。xD,所以还是希望先从深度算法开始写,理解一下消消乐的写法

    ε≡٩(๑>₃<)۶ 请大家多多评论一起讨论讨论

    相关文章

      网友评论

          本文标题:CocosLua3.10 连连看(深度算法和A星算法)

          本文链接:https://www.haomeiwen.com/subject/toodgctx.html