1、需求来历

某天娃拿着华容道板块来,喊她爹我求解,大约如图这样的一个东西,我:…,一把年岁了,和你玩这个破东西?自己一边玩去。

华容道问题-程序求解,python实现

花了一支烟时刻想了下,算了,帮你写一个程序来破解吧,后面别来烦我就行了。

2、初步建模

面板大小为 4 X 5 的数组,人物咱们称为卡片。 咱们观察人物有:曹操、关羽、张飞、黄忠、马超和4个卒(为了表明便利,四个卒咱们以为是四个不同人物)。 用一个二维数组表明游戏状况,为每个人物界说一个值。 人物值界说,卒用小写字母表明(谁叫你不是大佬?),大将们用大写字母表明,空白用 0 表明。(其实怎么界说都没关系,主要是为了便利阅读和同名不冲突) 如下:

 # "0":空
 # "a":卒1
 # "b":卒2
 # "c":卒3
 # "d":卒4
 # "M":马超
 # "H":黄忠
 # "Z":张飞
 # "Y":赵云
 # "G":关羽
 # "C":曹操

除了 0 值,其他相邻的相同值,咱们以为是不可分割的一块,根据界说,咱们构造其间一个初始化局势如下:

[  ["H","C","C","Y"],
  ["H","C","C","Y"],
  ["Z","G","G","M"],
  ["Z","b","c","M"],
  ["a","0","0","d"]
]

从初始状况开端,遍历每个可以移动的卡片进行下一个状况生成,得到一个状况树。

华容道问题-程序求解,python实现

最终生成树下假如把“曹操”人物移动到(1,3)、(2,3)(1,4)、(2,4)(如下图,蓝色数据移动到红色方位)表明完毕,拿到答案了:

华容道问题-程序求解,python实现

3、笼统目标化

在状况树图中,怎么从一个状况生成另一个状况?假如用上面的一个二维数组来做逻辑处理,比较复杂,如:

1) 除了0,相同的值的移动需求一起移动;

2) 移意向的范畴不能超出4X5区域范围并且方针要是0(空白);

3) 离开的区域,需求设置为0 等等。

根据二维数组做这些状况表逻辑处理,稍微复杂,将它们笼统简化下。

细想华容道游戏,包括一个面板,多个人物卡片,和两个空白方位这几种不同数据。咱们怎么把”面板”、”人物卡片”、”空白点”这三类有机的组合起来?过程如下:

1)从卡片开端,咱们界说一个Card 目标,它有人物值,有方位,有大小;

class Card:
    def __init__(self, role, x, y, width, height):
        self.role = role
        self.x = x
        self.y = y
        self.width = width
        self.height = height

2)空白点较为简略,界说为(x,y) 这样的一个元组即可,不过多规划了。

3)面板上包括属性有卡片,和空白点。卡片人物值是仅有的,用一个dir(role:Card) 表明人物;还有两个空白点,由于空白点也是仅有的,为了运算便利,咱们把它界说到调集set 里面。

面板模型代码:

class Board:
    '''
    cards: dir{role:card}
    blanks:set((x,y))
    '''
    def __init__(self, cards, blanks):
        self.cards = {card.role:card for card in cards}
        self.blanks = blanks

目前的 Board 目标和前面的状况state数组,它们表明是完全等价的。目标化之后,操作逻辑大大简化了,例如面板上移动一个卡片,便是两个过程:

1)对card 进行x,y进行加减操作(如Card提供 move 函数);

2)对进入区域和离开区域的空白方位的替换。

class Card:
    # 若干代码
    def move(self, offset_x, offset_y):
        self.x = self.x + offset_x
        self.y = self.y + offset_y

相对原来的要对 state 数组操作,清晰明了许多。

4、主要逻辑

再来收拾下建模游戏的主要逻辑,由于华容道答案是希望寻求最短途径的,所以需求一个广度优先算法。 从初始化局势(根局势)开端:

1)枚举每个卡片,对每个卡片进行上下左右可移动判别,并生成所有或许状况局势,记载到列表;

2)对每个生成状况局势进行判别,假如是答案,则返回“演化”前史;假如不是,则记载到“下一层”遍历列表;

3)对“下一层”遍历列表每个状况局势进行递归处理,回到过程 1)。

简略说说可移动判别逻辑,比方一个卡片想往上移动,它能往上移动的条件是什么?

1)卡片本身不能在最贴顶部;

2)卡片顶边的上方,每个格子都没有其他卡片,看状况二维数组,便是状况为“0”,在 Board 目标来看,便是上方的(x,y)点,都在 Board.blanks 内。

收拾好思路后,逻辑就简略了:

class Card:
    # 判别是否现已到顶部
    def isTopMost(self):
        return self.y == 0
    # 求顶边各个点
    def getTopLines(self):
        return [(self.x + i, self.y) for i in range(self.width)]
    # ... 其他许多代码

向上移动,假如是顶部了,则不能往上移动;否则,获取顶部各点的上方一格,并且判别上一个是空白,则可以移动了。 还需求笼统一个game 来做游戏总控制。

class Game:
    def __init__(self):
        pass
    # 返回移动后的状况,None 表明不合法移动
    def moveUp(self, card, board):
        if card.isTopMost():
            return None
        # 进入区域为顶边偏移-1,离开区域为底边,偏移值得 为(0,-1)
        above_tops = [(x, y - 1) for (x, y) in card.getTopLines()]
        return self.move(card, board, above_tops, card.getBottomLines(), 0, -1)
    def move(self, card, board, enter_points, leave_points, offset_x, offset_y):
        # 进入的空间要求为空白
        if not board.isAllBlanks(enter_points):
            return None
        new_card = card.clone()
        new_card.move(offset_x, offset_y)
        new_board = board.clone()
        new_board.updateCard(new_card)
        new_board.updateBlanks(enter_points, leave_points)
        return new_board

递归算法,采用广度优先遍历,枚举每个局势,中心代码便是这么一点了:

    def dfs(self, forest, traversed):
        next_forest = []
        for tree in forest:
            child_boards = self.nextBoards(tree.board)
            for child_board in child_boards:
                child_tree = Tree(tree, child_board)
                if self.isAnswer(child_board):
                    return child_tree.sourceBoard()
                next_forest.append(child_tree)
        if len(next_forest) == 0:
            return []
        return self.dfs(next_forest, traversed)

5、算法优化

1)由于移动卡片时分,移动先后两个卡片几次后,或许会呈现两个面板是相同的,所以针对每个局势进行hash 记载,前史上呈现过了,不需求再做便利,可以大大减少局势遍历量。

2)形状相同的两个卡片,假如呈现了方位互换,两个盘面其实是等价的,咱们以为两个卡片是等价的,所以他们hash 记载理应相同,省去不必要的遍历。

代码见Board.hash() 相关逻辑。

相同的两个状况,没有优化的情况下,跑一个小时还没有出成果,做了这两点优化后,约10秒出成果。优化作用是比较客观的。

6、输出界面

本来想简略偷闲点,用个文字替换输出的,这个样子:

华容道问题-程序求解,python实现

但考虑娃看不懂这种“高档”的诙谐,我不得不又花了她爸的一个多小时去抠图,然后编码…刷刷刷~弄了个6岁儿童能看得懂的界面作用:

华容道问题-程序求解,python实现

7、完整代码分享

不多说了,上代码吧。

github 链接:github.com/yuccnx/klot…