150 行代码实现图形化数独游戏
GitHub 地址 https://github.com/NemoHoHaloAi/Sudoku-- , 欢迎各位大佬们 fork,star 啥的, 感谢;
今天闲着没事干, 以前做过 HTML+JS 版的数独, 这次做个 python 版本的, 界面由 pygame 完成, 数独生成由递归算法实现, 由 shuffle 保证每次游戏都是不一样的情况, have fun;
功能列表:
图形化的数独游戏;
python 实现, 依赖 pygame 库;
随机生成游戏, 每次运行都不一样;
数字填入后的正确性判断以及颜色提示;
显示剩余需填入的空格, 已经操作的次数;
难度可选, 通过修改需要填入的空的数量;
游戏界面
初始界面
过程中界面
运行方式
python main.py 15
这里的 15 表示需要填入的空格数量为 15, 理论上这个值越大, 难度就越高, 大家可以随机调整, 或者设置容易, 简单, 困难, 地狱等对应不同的值即可, 很方便修改;
程序分析
界面部分
这部分很简单的通过 pygame 来实现, 主要使用了其中的主循环, 鼠标键盘监听, 画矩形线条, 字体, 颜色控制等, 理解起来很容易, 对于这部分不太熟悉的同学, 这样理解就好: pygame 的主循环中一方面负责接收用户输入, 一般就是鼠标和键盘, 另一方面负责实时更新界面显示内容;
对于界面上各部分内容的绘制的函数封装
- # 绘制背景部分, 这里就是 9*9 的九宫格
- def draw_background():
- # white background
- screen.fill(COLORS['white'])
- # draw game board
- pygame.draw.rect(screen,COLORS['black'],(0,0,300,900),5)
- pygame.draw.rect(screen,COLORS['black'],(300,0,300,900),5)
- pygame.draw.rect(screen,COLORS['black'],(600,0,300,900),5)
- pygame.draw.rect(screen,COLORS['black'],(0,0,900,300),5)
- pygame.draw.rect(screen,COLORS['black'],(0,300,900,300),5)
- pygame.draw.rect(screen,COLORS['black'],(0,600,900,300),5)
- # 将用户选中的各自背景改为蓝色块表示选中
- def draw_choose():
- pygame.draw.rect(screen,COLORS['blue'],(cur_j*100+5,cur_i*100+5,100-10,100-10),0)
- # 绘制九宫格中的数字, 包括本来就有的, 以及用户填入的, 本来就在的用灰色, 用户填入的如何合法则为绿色, 否则为红色, 是一种提示
- def draw_number():
- for i in range(len(MATRIX)):
- for j in range(len(MATRIX[0])):
- _color = check_color(MATRIX,i,j) if (i,j) in BLANK_IJ else COLORS['gray']
- txt = font80.render(str(MATRIX[i][j] if MATRIX[i][j] not in [0,'0'] else ''),True,_color)
- x,y = j*100+30,i*100+10
- screen.blit(txt,(x,y))
- # 绘制最下方的当前空格子数量以及用户的操作数量
- def draw_context():
- txt = font100.render('Blank:'+str(cur_blank_size)+'Change:'+str(cur_change_size),True,COLORS['black'])
- x,y = 10,900
- screen.blit(txt,(x,y))
主循环中对上述函数的调用以及鼠标键盘事件处理
- # 主循环, 负责监听鼠标键盘时间, 以及刷新界面内容, 以及检查是否赢得了游戏
- running = True
- while running:
- for event in pygame.event.get():
- if event.type == pygame.QUIT:
- running = False
- break
- elif event.type == pygame.MOUSEBUTTONDOWN:
- cur_j,cur_i = int(event.pos[0]/100),int(event.pos[1]/100)
- elif event.type == event.type == pygame.KEYUP:
- if chr(event.key) in ['1','2','3','4','5','6','7','8','9'] and (cur_i,cur_j) in BLANK_IJ:
- MATRIX[cur_i][cur_j] = int(chr(event.key))
- cur_blank_size = sum([1 if col==0 or col=='0' else 0 for row in MATRIX for col in row])
- cur_change_size +=1
- # background
- draw_background()
- # choose item
- draw_choose()
- # numbers
- draw_number()
- # point
- draw_context()
- # flip
- pygame.display.flip()
- # check win or not
- if check_win(MATRIX_ANSWER,MATRIX):
- print('You win, smarty ass!!!')
- break
- pygame.quit()
生成表示数独的二维数组
相对于界面部分, 这部分在逻辑上要难一些, 思路以递归为核心, 辅以随机性, 得到一个每次生成都不一致的数独游戏, 生成思路简单描述如下:
遍历每个空格, 填入目前为止合法的数字;
如果有数字可以填入, 则继续向下一个空格;
如果没有数字可以填入, 表示之前的数字有问题, 则结束递归;
当递归到最后一个格子的下一个时, 表示已经生成完毕, 返回即可;
这个过程中对 1~9 这九个数字的遍历数字会经过 shuffle 处理, 保证随机性而不是每次都得到同一个合法的数独数组;
生成过程代码
递归的一个优势是通常代码都很短, 当然阅读性不强, 欢迎大佬们改为循环;
- def shuffle_number(_list):
- random.shuffle(_list)
- return _list
- def check(matrix,i,j,number):
- if number in matrix[i]:
- return False
- if number in [row[j] for row in matrix]:
- return False
- group_i,group_j = int(i/3),int(j/3)
- if number in [matrix[i][j] for i in range(group_i*3,(group_i+1)*3) for j in range(group_j*3,(group_j+1)*3)]:
- return False
- return True
- def build_game(matrix,i,j,number):
- if i>8 or j>8:
- return matrix
- if check(matrix,i,j,number):
- _matrix = [[col for col in row] for row in matrix]
- _matrix[i][j] = number
- next_i,next_j = (i+1,0) if j==8 else (i,j+1)
- for _number in shuffle_number(number_list):
- __matrix = build_game(_matrix,next_i,next_j,_number)
- if __matrix and sum([sum(row) for row in __matrix])==(sum(range(1,10))*9):
- return __matrix
- return None
随机覆盖数独数组中的 N 个位置
matrix_all 表示整个数独数组
matrix_blank 表示部分被替换为 0 的用于显示的数组
blank_ij 表示被覆盖位置的 i 和 j
- def give_me_a_game(blank_size=9):
- matrix_all = build_game(matrix,0,0,random.choice(number_list))
- set_ij = set()
- while len(list(set_ij))<blank_size:
- set_ij.add(str(random.choice([0,1,2,3,4,5,6,7,8]))+','+str(random.choice([0,1,2,3,4,5,6,7,8])))
- matrix_blank = [[col for col in row] for row in matrix_all]
- blank_ij = []
- for ij in list(set_ij):
- i,j = int(ij.split(',')[0]),int(ij.split(',')[1])
- blank_ij.append((i,j))
- matrix_blank[i][j] = 0
- return matrix_all,matrix_blank,blank_ij
最后附上全部代码
大家也可以直接从我的 GitHub 仓库 https://github.com/NemoHoHaloAi/Sudoku-- fork 下来直接运行;
main.py: 主流程 + 界面 + 执行
- import sys
- import pygame
- from pygame.color import THECOLORS as COLORS
- from build import print_matrix,give_me_a_game,check
- def draw_background():
- # white background
- screen.fill(COLORS['white'])
- # draw game board
- pygame.draw.rect(screen,COLORS['black'],(0,0,300,900),5)
- pygame.draw.rect(screen,COLORS['black'],(300,0,300,900),5)
- pygame.draw.rect(screen,COLORS['black'],(600,0,300,900),5)
- pygame.draw.rect(screen,COLORS['black'],(0,0,900,300),5)
- pygame.draw.rect(screen,COLORS['black'],(0,300,900,300),5)
- pygame.draw.rect(screen,COLORS['black'],(0,600,900,300),5)
- def draw_choose():
- pygame.draw.rect(screen,COLORS['blue'],(cur_j*100+5,cur_i*100+5,100-10,100-10),0)
- def check_win(matrix_all,matrix):
- if matrix_all == matrix:
- return True
- return False
- def check_color(matrix,i,j):
- _matrix = [[col for col in row]for row in matrix]
- _matrix[i][j] = 0
- if check(_matrix,i,j,matrix[i][j]):
- return COLORS['green']
- return COLORS['red']
- def draw_number():
- for i in range(len(MATRIX)):
- for j in range(len(MATRIX[0])):
- _color = check_color(MATRIX,i,j) if (i,j) in BLANK_IJ else COLORS['gray']
- txt = font80.render(str(MATRIX[i][j] if MATRIX[i][j] not in [0,'0'] else ''),True,_color)
- x,y = j*100+30,i*100+10
- screen.blit(txt,(x,y))
- def draw_context():
- txt = font100.render('Blank:'+str(cur_blank_size)+'Change:'+str(cur_change_size),True,COLORS['black'])
- x,y = 10,900
- screen.blit(txt,(x,y))
- if __name__ == "__main__":
- # init pygame
- pygame.init()
- # contant
- SIZE = [900,1000]
- font80 = pygame.font.SysFont('Times', 80)
- font100 = pygame.font.SysFont('Times', 90)
- # create screen 500*500
- screen = pygame.display.set_mode(SIZE)
- # variable parameter
- cur_i, cur_j = 0,0
- cur_blank_size = int(sys.argv[1])
- cur_change_size = 0
- # matrix abount
- MATRIX_ANSWER,MATRIX,BLANK_IJ = give_me_a_game(blank_size=cur_blank_size)
- print(BLANK_IJ)
- print_matrix(MATRIX)
- # main loop
- running = True
- while running:
- for event in pygame.event.get():
- if event.type == pygame.QUIT:
- running = False
- break
- elif event.type == pygame.MOUSEBUTTONDOWN:
- cur_j,cur_i = int(event.pos[0]/100),int(event.pos[1]/100)
- elif event.type == event.type == pygame.KEYUP:
- if chr(event.key) in ['1','2','3','4','5','6','7','8','9'] and (cur_i,cur_j) in BLANK_IJ:
- MATRIX[cur_i][cur_j] = int(chr(event.key))
- cur_blank_size = sum([1 if col==0 or col=='0' else 0 for row in MATRIX for col in row])
- cur_change_size +=1
- # background
- draw_background()
- # choose item
- draw_choose()
- # numbers
- draw_number()
- # point
- draw_context()
- # flip
- pygame.display.flip()
- # check win or not
- if check_win(MATRIX_ANSWER,MATRIX):
- print('You win, smarty ass!!!')
- break
- pygame.quit()
build.py: 生成数独数组部分
- import random
- def print_matrix(matrix):
- print('-'*19)
- for row in matrix:
- print('|'+''.join([str(col) for col in row])+'|')
- print('-'*19)
- def shuffle_number(_list):
- random.shuffle(_list)
- return _list
- def check(matrix,i,j,number):
- if number in matrix[i]:
- return False
- if number in [row[j] for row in matrix]:
- return False
- group_i,group_j = int(i/3),int(j/3)
- if number in [matrix[i][j] for i in range(group_i*3,(group_i+1)*3) for j in range(group_j*3,(group_j+1)*3)]:
- return False
- return True
- def build_game(matrix,i,j,number):
- if i>8 or j>8:
- return matrix
- if check(matrix,i,j,number):
- _matrix = [[col for col in row] for row in matrix]
- _matrix[i][j] = number
- next_i,next_j = (i+1,0) if j==8 else (i,j+1)
- for _number in shuffle_number(number_list):
- #_matrixs.append(build_game(_matrix,next_i,next_j,_number))
- __matrix = build_game(_matrix,next_i,next_j,_number)
- if __matrix and sum([sum(row) for row in __matrix])==(sum(range(1,10))*9):
- return __matrix
- #return _matrixs
- return None
- def give_me_a_game(blank_size=9):
- matrix_all = build_game(matrix,0,0,random.choice(number_list))
- set_ij = set()
- while len(list(set_ij))<blank_size:
- set_ij.add(str(random.choice([0,1,2,3,4,5,6,7,8]))+','+str(random.choice([0,1,2,3,4,5,6,7,8])))
- matrix_blank = [[col for col in row] for row in matrix_all]
- blank_ij = []
- for ij in list(set_ij):
- i,j = int(ij.split(',')[0]),int(ij.split(',')[1])
- blank_ij.append((i,j))
- matrix_blank[i][j] = 0
- return matrix_all,matrix_blank,blank_ij
- number_list = [1,2,3,4,5,6,7,8,9]
- matrix = [([0]*9) for i in range(9)]
- if __name__ == "__main__":
- print_matrix(build_game(matrix,0,0,random.choice(number_list)))
总结
如果刻意减少代码的话, 实际应该控制在 100 行以内, 这也充分表达了 python 的强大, 确实可以在很短的时间内完成一些看似复杂的工作, 这个例子供一些同学上手 python 个人觉得还是不错的, 没有太复杂的用法, 对界面开发有一点点了解, 对递归有一些理解基本就能完全掌握这份代码, 希望大家玩的开心, 挑战一下 50 个空格呗, 哈哈, 反正我没通过, 太难了....
最后
大家可以到我的 GitHub 上看看有没有其他需要的东西, 目前主要是自己做的机器学习项目, Python 各种脚本工具, 有意思的小项目以及 Follow 的大佬, Fork 的项目等:
https://github.com/NemoHoHaloAi
来源: https://www.cnblogs.com/helongBlog/p/12627863.html