• 使用Dancing link X (舞蹈链)求解dominosa游戏


      puzzle team club做了一个网站,里面有好多解谜游戏可以玩。身为程序员玩这种游戏,就应该想一些使用程序解决的方法。

      但是里面好多游戏还蛮难解的。。。我是说写出简明扼要的程序,并且像在ACM赛场一样执行和开发时间占优。所有游戏都需要用搜索完成毫无疑问。

      但是不少游戏都涉及到图论,单纯的搜索很难一下想到方法,像slither link(连接回路)、hashi(搭桥)、nirikabe(围城)和slithes(连接)这几个游戏就是跟图论相关。

      其他看起来不像图论的游戏,像skyscrapers(摩天大楼)、star battle(星星战斗),属于有限次数完全覆盖,很难一下想到简明的方法。

      这里只求解能使用舞蹈链解决的,相对简单的dominosa游戏(链接:https://www.puzzle-dominosa.com/),这个游戏跟舞蹈链天生一对(误),跟数独的规则差不多,场上所有数字都必须用到1次,用到的数字用于连接数对(任意顺序数对算同种),所有数对必须并且只出现1次,属于精确覆盖题目。

      这里就可以想怎么转化为舞蹈链了,这里数对的所有可能连接方式就是舞蹈链的行,而数对的限制以及占用数字的限制则是舞蹈链的列,一旦有一个列被使用,舞蹈链会把其他用到这个列的所有行去除,以达到快速剪枝的目的。

      在这个游戏里面,数对可以横向连接((i,j)-(i,j+1)),也可竖向连接((i,j)-(i+1,j)),这两种连接方式分别是(r-1)c种和r(c-1)种,就是说行数有大约2cr;数字一般有0-(c-1)这几个数字,可以组成c(c+1)/2种数对(这个站点的dominosa游戏列数一般比行数大1),并且所有r*c个方格必须占据,只能占据1次,这意味着列数大约有3cr/2,并且这个站点的这种谜题只有1个解答,搜索宽度不会太长,最多只有20*19的谜题,执行时间不会太长。(这个站点还可以找到一个画方格游戏shikaku,也可以用精确覆盖,但是那个游戏长宽大得不像话)

      基于这个思路,我们开始写Python代码(DLX模板是用的https://github.com/DavideCanton/DancingLinksX这个链接里面的代码,我们只是构造表而已):

      首先,构造舞蹈表的列头:

    def get_names(r, c, m):
        cnt = 0
        for i in range(r):
            for j in range(c):
                yield f'L({i},{j})' # location pair
                cnt = cnt + 1
        for i in range(m+2):
            for j in range(i):
                yield f'O({j},{i-1})' # number pair
                cnt = cnt + 1
    View Code

      第一个二重循环对应占用数字条件列头,第一个二重循环对应占用数对条件列头

      构造每个可能的数对对应条件约束:

    def compute_row(i, j, a, b, isHorizontal, r, c):
        # H is 0 .. r*(c-1)-1
        # V is r*(c-1) .. r*(c-1)+c*(r-1)-1
        # O is r*c*2-r-c .. r*c*2-r-c+(m-1)*m//2-1
        if a < b:
            t = a
            a = b
            b = t
        row = []
        if isHorizontal:
            row.append(c*i+j)
            row.append(c*i+j+1)
        else:
            row.append(c*i+j)
            row.append(c*(i+1)+j)
        row.append(r*c+a*(a+1)//2+b)
        return row
    View Code

      完成舞蹈链遍历,还要把结果按照正确的格式输出。

      为了连接字符串方便,这里声明个给单个或多个字符串赋值的函数:

    def insertToStr(origin, i, j, value):
        if(origin[i][j] != '+'):
            origin[i] =  origin[i][:j] + value + origin[i][j+1:]
    View Code

      为了能按照指定格式输出答案,我们这里声明一个类:

    class PrintFirstSol:
        def __init__(self, r, c):
            self.r = r
            self.c = c
    
        def __call__(self, sol):
            maxNum = 0
            chessWidth = 0
            for v in sol.values():
                k = list(map(lambda a:int(a),v[2].split('(')[1].replace(')','').split(',')))
                maxNum = maxNum if k[0] < maxNum else k[0]
                maxNum = maxNum if k[1] < maxNum else k[1]
            while maxNum > 0:
                chessWidth += 1
                maxNum //= 10
            line = ['+','|']
            for _ in range(self.c):
                line[0] += ('-' * chessWidth + '+')
                line[1] += (' ' * chessWidth + '|')
            solImg = [line[0]]
            for _ in range(self.r):
                solImg.append(line[1])
                solImg.append(line[0])
            timeDelay = 0
            for v in sol.values():
                v.sort()
                v[0] = list(map(lambda a:int(a),v[0].split('(')[1].replace(')','').split(',')))
                v[1] = list(map(lambda a:int(a),v[1].split('(')[1].replace(')','').split(',')))
                v[2] = v[2].split('(')[1].replace(')','').split(',')
                # This code had a bug there, I fix it and chess image which higher than 9 don't mess
                # 之前这里代码有bug,我修掉之后,棋盘大小超过9的排布不会混乱
                i = v[0][0]
                j = v[0][1]
                i1 = v[1][0]
                j1 = v[1][1]
                if i > i1:
                    i, i1 = i1, i
                if j > j1:
                    j, j1 = j1, j
                if(v[0][1] != v[1][1]):
                    solImg[2*i+1] = solImg[2*i+1][:(1+chessWidth)*j]
                    + ('|%' + str(chessWidth) + 'd')%(chess[i][j])
                    + ' ' + ('%' + str(chessWidth) + 'd|')%(chess[i][j+1])
                    + solImg[2*i+1][(1+chessWidth)*(j+2)+1:]
                else:
                    startCut = (1+chessWidth)*j+1
                    endCut = (1+chessWidth)*(j+1)
                    solImg[2*i+1] = solImg[2*i+1][:startCut] + ('%' + str(chessWidth) + 'd')%(chess[i][j])
                    + solImg[2*i+1][endCut:]
                    solImg[2*i+2] = solImg[2*i+2][:startCut] + (' ' * chessWidth)
                    + solImg[2*i+2][endCut:]
                    solImg[2*i+3] = solImg[2*i+3][:startCut] + ('%' + str(chessWidth) + 'd')%(chess[i+1][j])
                    + solImg[2*i+3][endCut:]
                #
            #
            solImg = reduce(lambda a,b:a+'
    '+b,solImg)
            print(solImg)
            with open('result.txt','w') as f:f.write(solImg)
            return True
    View Code

      主要的处理步骤在main()这个函数里面:

    def main():
        start = time.time()
        with open('dominosaChess.txt','r') as f:
            chessStr = f.read()
        rowStrs = chessStr.split('
    ')
        rowsize = len(rowStrs)
        colSize = len(rowStrs[0].split(' '))
        maxnum = 0
        for rowStr in rowStrs:
            row = []
            for colStr in rowStr.split(' '):
                maxnum = maxnum if int(colStr) < maxnum else int(colStr)
                row.append(int(colStr))
            chess.append(row)
        #print(chess)
        d = DancingLinksMatrix(get_names(rowsize, colSize, maxnum))
    
        for i in range(rowsize):
            for j in range(colSize-1):
                row = compute_row(i, j, chess[i][j], chess[i][j+1], True, rowsize, colSize)
                #print(row)
                d.add_sparse_row(row, already_sorted=True)
        #
        for i in range(rowsize-1):
            for j in range(colSize):
                row = compute_row(i, j, chess[i][j], chess[i+1][j], False, rowsize, colSize)
                #print(row)
                d.add_sparse_row(row, already_sorted=True)
        d.end_add()
        
        p = PrintFirstSol(rowsize, colSize)
        AlgorithmX(d, p)()
        end = time.time()
        print('the DLX runtime is : ' + str(end-start) + 's')
    View Code

      总的代码就是这样:

    """
    dominosa solver using Dancing Links.
    """
    from functools import reduce
    from dlmatrix import DancingLinksMatrix
    from alg_x import AlgorithmX
    import math
    import time
    
    __author__ = 'Funcfans'
    chess = []
    def insertToStr(origin, i, j, value):
        if(origin[i][j] != '+'):
            origin[i] =  origin[i][:j] + value + origin[i][j+1:]
    
    def get_names(r, c, m):
        cnt = 0
        for i in range(r):
            for j in range(c):
                yield f'L({i},{j})' # location pair
                cnt = cnt + 1
        for i in range(m+2):
            for j in range(i):
                yield f'O({j},{i-1})' # number pair
                cnt = cnt + 1
    #
    def compute_row(i, j, a, b, isHorizontal, r, c):
        # H is 0 .. r*(c-1)-1
        # V is r*(c-1) .. r*(c-1)+c*(r-1)-1
        # O is r*c*2-r-c .. r*c*2-r-c+(m-1)*m//2-1
        if a < b:
            t = a
            a = b
            b = t
        row = []
        if isHorizontal:
            row.append(c*i+j)
            row.append(c*i+j+1)
        else:
            row.append(c*i+j)
            row.append(c*(i+1)+j)
        row.append(r*c+a*(a+1)//2+b)
        return row
    
    class PrintFirstSol:
        def __init__(self, r, c):
            self.r = r
            self.c = c
    
        def __call__(self, sol):
            maxNum = 0
            chessWidth = 0
            for v in sol.values():
                k = list(map(lambda a:int(a),v[2].split('(')[1].replace(')','').split(',')))
                maxNum = maxNum if k[0] < maxNum else k[0]
                maxNum = maxNum if k[1] < maxNum else k[1]
            while maxNum > 0:
                chessWidth += 1
                maxNum //= 10
            line = ['+','|']
            for _ in range(self.c):
                line[0] += ('-' * chessWidth + '+')
                line[1] += (' ' * chessWidth + '|')
            solImg = [line[0]]
            for _ in range(self.r):
                solImg.append(line[1])
                solImg.append(line[0])
            timeDelay = 0
            for v in sol.values():
                v.sort()
                v[0] = list(map(lambda a:int(a),v[0].split('(')[1].replace(')','').split(',')))
                v[1] = list(map(lambda a:int(a),v[1].split('(')[1].replace(')','').split(',')))
                v[2] = v[2].split('(')[1].replace(')','').split(',')
                # This code had a bug there, I fix it and chess image which higher than 9 don't mess
                # 之前这里代码有bug,我修掉之后,棋盘大小超过9的排布不会混乱
                i = v[0][0]
                j = v[0][1]
                i1 = v[1][0]
                j1 = v[1][1]
                if i > i1:
                    i, i1 = i1, i
                if j > j1:
                    j, j1 = j1, j
                if(v[0][1] != v[1][1]):
                    solImg[2*i+1] = solImg[2*i+1][:(1+chessWidth)*j]
                    + ('|%' + str(chessWidth) + 'd')%(chess[i][j])
                    + ' ' + ('%' + str(chessWidth) + 'd|')%(chess[i][j+1])
                    + solImg[2*i+1][(1+chessWidth)*(j+2)+1:]
                else:
                    startCut = (1+chessWidth)*j+1
                    endCut = (1+chessWidth)*(j+1)
                    solImg[2*i+1] = solImg[2*i+1][:startCut] + ('%' + str(chessWidth) + 'd')%(chess[i][j])
                    + solImg[2*i+1][endCut:]
                    solImg[2*i+2] = solImg[2*i+2][:startCut] + (' ' * chessWidth)
                    + solImg[2*i+2][endCut:]
                    solImg[2*i+3] = solImg[2*i+3][:startCut] + ('%' + str(chessWidth) + 'd')%(chess[i+1][j])
                    + solImg[2*i+3][endCut:]
                #
            #
            solImg = reduce(lambda a,b:a+'
    '+b,solImg)
            print(solImg)
            with open('result.txt','w') as f:f.write(solImg)
            return True
    
    def main():
        start = time.time()
        with open('dominosaChess.txt','r') as f:
            chessStr = f.read()
        rowStrs = chessStr.split('
    ')
        rowsize = len(rowStrs)
        colSize = len(rowStrs[0].split(' '))
        maxnum = 0
        for rowStr in rowStrs:
            row = []
            for colStr in rowStr.split(' '):
                maxnum = maxnum if int(colStr) < maxnum else int(colStr)
                row.append(int(colStr))
            chess.append(row)
        #print(chess)
        d = DancingLinksMatrix(get_names(rowsize, colSize, maxnum))
    
        for i in range(rowsize):
            for j in range(colSize-1):
                row = compute_row(i, j, chess[i][j], chess[i][j+1], True, rowsize, colSize)
                #print(row)
                d.add_sparse_row(row, already_sorted=True)
        #
        for i in range(rowsize-1):
            for j in range(colSize):
                row = compute_row(i, j, chess[i][j], chess[i+1][j], False, rowsize, colSize)
                #print(row)
                d.add_sparse_row(row, already_sorted=True)
        d.end_add()
        
        p = PrintFirstSol(rowsize, colSize)
        AlgorithmX(d, p)()
        end = time.time()
        print('the DLX runtime is : ' + str(end-start) + 's')
    
    if __name__ == "__main__":
        main()
    View Code

      这里在代码同目录下创建一个dominosaChess.txt文件,并且输入以下布局:

    7 5 1 2 6 7 6 7 3
    2 1 1 1 6 7 0 2 3
    5 7 3 6 2 0 4 2 0
    1 4 4 5 6 1 6 1 5
    7 2 0 5 4 3 3 0 4
    2 7 0 0 7 6 5 1 3
    4 5 5 4 1 0 6 2 0
    6 4 5 3 4 7 3 2 3
    View Code

      可以得到以下输出:

    +---+---+-+-+---+-+
    |7 5|1 2|6|7|6 7|3|
    +-+-+-+-+ | +-+-+ |
    |2|1 1|1|6|7|0|2|3|
    | +---+ +-+-+ | +-+
    |5|7 3|6|2 0|4|2|0|
    +-+---+-+-+-+-+-+ |
    |1|4 4|5 6|1|6|1|5|
    | +-+-+---+ | | +-+
    |7|2|0|5 4|3|3|0|4|
    +-+ | +---+-+-+-+ |
    |2|7|0|0 7|6|5 1|3|
    | +-+-+---+ +---+-+
    |4|5 5|4 1|0|6 2|0|
    +-+-+-+-+-+-+---+ |
    |6 4|5 3|4 7|3 2|3|
    +---+---+---+---+-+
    
    the DLX runtime is : 0.005000591278076172s

      可以看出来,这种方法解dominosa比其他方法解要快很多。

      把答案填在dominosa里面并提交:

      大功告成!

  • 相关阅读:
    Android笔记(六十六) android中的动画——XML文件定义属性动画
    Android笔记(六十五) android中的动画——属性动画(propertyanimation)
    python函数参数默认值及重要警告
    Python 列表
    Python数学运算入门把Python当作计算器
    Python 的非正式介绍
    python中为什么 if/while/def/class语句需要冒号?
    为什么Python在列表和元组的末尾允许使用逗号?
    现实世界中的 Python
    Python常见问题
  • 原文地址:https://www.cnblogs.com/dgutfly/p/11950845.html
Copyright © 2020-2023  润新知