1.关于本文
Nonogram是一种逻辑游戏,以猜谜的方式绘画位图。在一个网格中,每一行和列都有一组数,玩家需根据它们来填满或留空格子,最后就可以由此得出一幅图画。
具体规则可以参见Wikipedia上的相关页面:
中文简体:https://zh.wikipedia.org/zh-cn/Nonogram
英文:https://en.wikipedia.org/wiki/Nonogram
本文中的程序,通过写在文本文件中给定的输入,经过计算,把输出写到另一个文本文件中
(这个程序最开始写出来的目的是用来计算PythonChallenge的第32关的~~~,因此输入文件的格式设计也采用了与该关输入相同的格式)
2.输入的文本文件
输入是一个文本文件。共有Dimensions、Horizontal lines、Vertical lines三个区,Dimensions指定输入矩阵的高度和长度,Horiziontal lines 和 Vertical line 下各行的数字分别代表着Nonogram每行或每列头部出现的提示性数字
如下面这个up.txt,就是一个合法的输入文本:
# Dimensions 32 32 # Horizontal lines 3 2 8 10 3 1 1 5 2 1 5 2 1 4 1 1 15 19 6 14 6 1 12 6 1 10 7 2 1 8 6 1 1 2 1 1 1 1 5 1 4 1 5 4 1 4 1 1 1 5 1 1 8 5 2 1 8 6 1 2 1 3 6 3 2 1 6 1 5 1 6 3 2 7 2 3 3 10 4 9 12 1 22 1 21 4 1 17 1 2 8 5 1 2 2 4 5 2 1 1 5 # Vertical lines 5 5 5 3 1 3 1 5 5 6 5 6 9 5 11 5 1 13 6 1 14 6 1 7 12 1 6 1 11 1 3 1 1 1 9 1 3 4 10 8 1 1 2 8 1 10 1 1 1 7 1 10 4 1 1 7 1 3 2 5 2 1 2 6 2 3 2 4 2 1 1 4 1 2 6 3 1 1 1 1 1 12 3 1 2 1 1 1 3 2 7 3 1 2 1 2 2 6 3 1 1 1 1 12 3 1 5 6 3 1 6 4 1 5 4 4 1 1 5
3.程序代码
FILL, UNCERTAIN, NOTHING = 'X', '_', ' ' def inputdata(inputfile): ''' 函数:从inputfile读取数据 输入:@inputfile,字符串,输入文件 输出:读取到的数据 @height 矩阵高度 @width 矩阵长度 @horizdata 各行数据(Nonogram矩阵左侧数据) @verticdata 各列数据(Nonogram矩阵上侧数据) ''' (height, width, horizdata, verticdata) = 0, 0, [], [] #标识进入到各个步骤的常量 STEP_NOTHING = 'NOTHING' STEP_READDMI = 'READDIM' STEP_READHRZ = 'READHRZ' STEP_READVRT = 'READVRT' currstep = STEP_NOTHING #打开文件 f = open(inputfile, 'r') for line in f.readlines(): #读到空行直接跳过 if line.strip() == '': continue #去掉行尾的'\n' if line[-1] == '\n': line = line[:-1] #处理读到的数据 if line.startswith('# Dimensions'): #读取维度 currstep = STEP_READDMI elif line.startswith('# Horizontal'): #读取矩阵高度 currstep = STEP_READHRZ elif line.startswith('# Vertical'): #读取矩阵长度 currstep = STEP_READVRT else: #读取数据 if currstep == STEP_READDMI: #读取维度 elements = line.split(' ') if elements[0].isdigit() and elements[1].isdigit(): height = int(elements[0]) width = int(elements[1]) else: raise Exception("CANNOT READ DIMENSION") elif currstep == STEP_READHRZ: #读取各行数据 elements = line.split(' ') horizdata.append( [int(ele) for ele in elements if ele != '0' and ele.isdigit()]) elif currstep == STEP_READVRT: #读取各列数据 elements = line.split(' ') verticdata.append( [int(ele) for ele in elements if ele != '0' and ele.isdigit()]) #关闭文件 f.close() return (height, width, horizdata, verticdata) def outputdata(outputfile, matrix): ''' 函数:将matrix内容输出到文件outputfile 输入 @ouputfile:字符串,保存到的文件 输入 @matrix:一个二维列表 ''' f = open(outputfile, 'w') output = '' title = ' |' counter = 0 for row in matrix: title += str((counter + 1) % 10) output += str((counter + 1) % 10) + '|' + ''.join(row) + '\n' counter += 1 f.write(title + '\n' + '-' * (len(title)) + '\n' + output) f.close() def alternatives(data, length): ''' 函数:指出某一行或某一列所有可能的填充方案 输入 @data:列表 输入 @length:可填充长度 输出 @result:列表,可能的填充方案 ''' datalen = len(data) datasum = sum(data) #如果一行或一列不需要任何填充,直接返回唯一的全空备选方案 if datalen == 0: return [UNCERTAIN * length] j = 0 result = [] if datalen == 1: j = 1 for i in range(length - datalen + 1 - datasum + 1): header = UNCERTAIN * i + FILL * data[0] + UNCERTAIN * (1 - j) if j: tails = [UNCERTAIN * (length - len(header))] else: tails = alternatives(data[1:], (length - len(header))) result.extend([header + tail for tail in tails]) return result def decide(alterset, idx): ''' 函数:根据某行/列备选组合判断改行/列第idx格是否可判定(一定填充或一定不填充) 输入 @alterset:备选方案集 输入 @id:(备选方案的)第idx个格子 输出 @mark:None:不能确定 FILL:一定填充 NOTHING:一定不填充 ''' mark = alterset[0][idx] for j in range(1, len(alterset)): if mark != alterset[j][idx]: return None return FILL if mark == FILL else NOTHING def deleteillegal(alterset, idx, mark): ''' 函数:去除非法的候选组合 输入 @alterset:备选方案集 输入 @id:(备选方案的)第idx个格子 输入 @mark:标记,指出该格子一定填充或一定不填充, 如果备选方案与判定结果不符,则是非法方案,应该删掉 ''' if alterset != 'ELIMINATED' and len(alterset) > 1: mark = FILL if mark == FILL else UNCERTAIN for i in range(len(alterset) - 1, -1, -1): #删除要从后向前删 if alterset[i][idx] != mark: alterset.pop(i) def drawpic(inputfile, outputfile): ''' 函数:绘制题解 输入 @inputfile:字符串,输入的文件地址 输入 @outputfile:字符串,输出到的文件地址 ''' #读取数据 (height, width, horizdata, verticdata) = inputdata(inputfile) #所有可能的候选组合 alterh = [alternatives(data, width) for data in horizdata] alterv = [alternatives(data, height) for data in verticdata] counter = 0 #遍历变量 total = height * width #矩阵总元素数 matrix = [] #结果矩阵 #初始化矩阵 for i in range(height): matrix.append([UNCERTAIN] * width) #循环逐行考虑、逐列考虑 while counter < total: for i in range(height): #逐行考虑 if alterh[i] == 'ELIMINATED': #不考虑已经考虑完毕的行 continue if len(alterh[i]) == 0: #某行无备选方案,报错 raise Exception("LACK OF ALTERNATIVES") elif len(alterh[i]) == 1: #该行只有一种选择的情况 for j in range(width): #逐列筛选可能的组合 if matrix[i][j] == UNCERTAIN: #标记该行剩余未判定信息 matrix[i][j] = NOTHING if alterh[i][0][j] != FILL else FILL counter += 1 #已判定方格数也要+1 deleteillegal(alterv[j], i, matrix[i][j]) #删除不可能正确的列组合 elif matrix[i][j] != alterh[i][0][j] and alterh[i][0][j] != UNCERTAIN: raise Exception("LACK OF ALTERNATIVES") #唯一备选方案与实际不符的情况 alterh[i] = 'ELIMINATED' #该行已经考虑完毕,打上标记 else: for j in range(width): #逐列筛选可能的组合 if matrix[i][j] != UNCERTAIN: #只考虑空闲的方格 continue mark = decide(alterh[i], j) #判断第i行第j格是否可判定 if mark is not None: #该方格可判定的情况 matrix[i][j] = mark #标记该方格我们判定好的值 counter += 1 #已判定方格数+1 deleteillegal(alterv[j], i, mark) #删除不可能正确的列组合 for i in range(width): #逐列考虑 if alterv[i] == 'ELIMINATED': #不考虑已经考虑完毕的列 continue if len(alterv[i]) == 0: #某列无备选方案,报错 raise Exception("LACK OF ALTERNATIVES") elif len(alterv[i]) == 1: #该列只有一种选择的情况 for j in range(height): #逐行筛选可能的组合 if matrix[j][i] == UNCERTAIN: #标记该列剩余未判定信息 matrix[j][i] = NOTHING if alterv[i][0][j] != FILL else FILL counter += 1 #已判定方格数也要+1 deleteillegal(alterh[j], i, matrix[j][i]) #删除不可能正确的行组合 elif matrix[j][i] != alterv[i][0][j] and alterv[i][0][j] != UNCERTAIN: raise Exception("LACK OF ALTERNATIVES") #唯一备选方案与实际不符的情况 alterv[i] = 'ELIMINATED' #该列已经考虑完毕,打上标记 else: for j in range(height): #逐行筛选可能的组合 if matrix[j][i] != UNCERTAIN: #只考虑空闲的方格 continue mark = decide(alterv[i], j) #判断第i列第j格是否可判定 if mark is not None: #该方格可判定的情况 matrix[j][i] = mark #标记该方格我们判定好的值 counter += 1 #已判定方格数+1 deleteillegal(alterh[j], i, mark) #删除不可能正确的行组合 #输出数据 outputdata(outputfile, matrix) if __name__ == '__main__': drawpic('up.txt', 'result.txt')
这段代码调用drawpic函数,从up.txt读入,将最后绘制的结果输出到result.txt中
4.程序输出
result.txt的内容就是上面的输入和代码创造的输出:
如果这个图看着不是很直观,那下面还有一张在画图(mspaint)上描格子画出来的图:
把眼镜摘了,就能看到图里画的是一条蛇的形状啦 :-P
附:
后来我又在网上找了个在线的Nonogram: http://www.newgrounds.com/portal/view/304506
游戏的名字叫ArmorPicross,玩了一个晚上玩到半夜1:30终于玩通了 ^^,把每关的通关截图上传到网盘,留个档,睡觉! 通关截图地址:http://pan.baidu.com/s/1eQy7vr0
END