使用Python求解Nonogram

Nonogram也是一種類似於數獨的數字遊戲,但是似乎要簡單一些,只要有足夠的耐心,任何玩家都可以成功完成,而且在遊戲過程中會逐漸熟練。

在應用商店中也可以找到,而且評分高達4.9。

遊戲界面如下,感興趣的話可以自行查找遊戲規則,app中也有新手指引。很容易上手。

求解思路:

這個遊戲比數獨簡單的一點是,它不需要反覆試探,也就是不需要回撤,只需要依次掃描,先從能夠確定的位置出發,逐步填充即可。

(1)找到可能的開頭組合。

比如對上面的第6列,只有一個數字8,其可能的開頭爲1~10共10種,但是如果從4及以後開頭,則會超出格子,故只能有3種情況

①從1開頭,第6列爲1111111100

②從2開頭,第6列爲0111111110

③從3開頭,第6列爲0011111111

(2)取交集部分,完成部分填充。

可以看到,不論怎樣取,第3~8位都是1,所以就可以將其標記爲1。

(3)不斷重複上述過程,直到完成所有填充。

另:當已有部分方格被填充時,還應保證新的填充不與其發生衝突。

上面只是舉了個簡單的例子,實際程序中,可以對每行每列反覆掃描。

代碼實現:

import numpy as np
from itertools import combinations

class nonogram:
    def __init__(self,rows,cols):
        if len(rows) != len(cols):
            raise Exception('The number of rows and columns varies')
        self.rank = len(rows)
        self.rows = rows
        self.cols = cols
        # 初始化結果矩陣爲-1,用1表示填充,用0表示×
        self.result = np.zeros(shape=(self.rank, self.rank))-1
    
    def cal_coms(self,nums):
        """
        由某行或某列的數字列表計算可能的開頭組合
        nums可表示rows或cols中的一個元素
        """
        l = len(nums)
        # 可能的開頭組合
        coms_p = list(combinations(range(0,self.rank), l))
        # 可行的開頭組合
        coms_v = []
        # 對所有可能的組合逐一篩選
        for com in coms_p:
            flag = 0
            # 保證兩兩間隔
            for i in range(1,l):
                if com[i]-com[i-1]<nums[i-1]+1:
                    flag = 1
                    break
            # 保證不超過範圍
            if self.rank-com[-1]<nums[-1]:
                flag = 1
            
            if flag == 0:
                coms_v.append(com)
        return coms_v
    
    def cal_res(self,nums,result_l):
        '''
        檢查是否一定是1或者一定是0
        返回1、0或-1(不確定)
        '''
        coms_v = self.cal_coms(nums)
        lines = []
        # 對每種可能組合,列出在該組合下的數字nums記入line
        for com in coms_v:
            line = np.zeros(self.rank)
            for i in range(0,len(com)):
                for j in range(com[i],com[i]+nums[i]):
                    line[j]=1
            # 如果沒有與已知情況發生衝突,則加入lines
            if 1 not in line+result_l:
                lines.append(line)
        if len(lines) == 0:
            return
        lines = np.mat(lines)
        for n in range(0,self.rank):
            # 如果在各種組合下都是1,記爲1
            if lines[:,n].all():
                result_l[n] = 1
            # 如果在各種組合下都是0(沒有1),記爲0
            if not lines[:,n].any():
                result_l[n] = 0
        return result_l
    
    def cal_nono(self):
        '''
        完成nonogram的計算
        '''
        while -1 in self.result:
            # 當有未標記的位置時,對每行每列反覆掃描
            for i in range(0,self.rank):
                self.result[i,:] = \
                    self.cal_res(rows[i],self.result[i,:])
            for i in range(0,self.rank):
                self.result[:,i] = \
                    self.cal_res(cols[i],self.result[:,i])

測試一下:

# 測試用例
rows = [
        [5],
        [4],
        [6],
        [7],
        [1,5],
        [5],
        [1,5,2],
        [6,2],
        [1,2],
        [1,2]]
cols = [
        [1],
        [1],
        [5,4],
        [4,2],
        [4,2],
        [8],
        [1,6],
        [4],
        [7],
        [6]]

N = nonogram(rows,cols)
N.cal_nono()
print(N.result)

計算得到的結果爲:

完成一局遊戲後就會獲得一張色塊拼圖,我目前積攢的部分拼圖如下。(前面的都是手動推理的,後面幾個是用上面的程序計算的,程序雖然快很多,但手動纔有趣味嘛。)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章