美文网首页
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