150行Python代码实现带界面的数独游戏


Posted in Python onApril 04, 2020

今天闲着没事干,以前做过html+js版的数独,这次做个python版本的,界面由pygame完成,数独生成由递归算法实现,由shuffle保证每次游戏都是不一样的情况,have fun;

功能列表:

  • 图形化的数独游戏;
  • python实现,依赖pygame库;
  • 随机生成游戏,每次运行都不一样;
  • 数字填入后的正确性判断以及颜色提示;
  • 显示剩余需填入的空格,已经操作的次数;
  • 难度可选,通过修改需要填入的空的数量;

 游戏界面

初始界面

150行Python代码实现带界面的数独游戏

过程中界面

150行Python代码实现带界面的数独游戏

运行方式

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仓库 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

到此这篇关于150行Python代码实现带界面的数独游戏的文章就介绍到这了,更多相关Python 数独游戏内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

作者:Ho Loong
Github:https://github.com/NemoHoHaloAi
Kaggle:https://www.kaggle.com/holoong9291

Python 相关文章推荐
Python操作MongoDB详解及实例
May 18 Python
python3.4用循环往mysql5.7中写数据并输出的实现方法
Jun 20 Python
Python插件virtualenv搭建虚拟环境
Nov 20 Python
Python实现PS图像明亮度调整效果示例
Jan 23 Python
简单谈谈python基本数据类型
Sep 26 Python
python 定时器,实现每天凌晨3点执行的方法
Feb 20 Python
解决python3.5 正常安装 却不能直接使用Tkinter包的问题
Feb 22 Python
python pprint模块中print()和pprint()两者的区别
Feb 10 Python
Python爬虫:Request Payload和Form Data的简单区别说明
Apr 30 Python
jupyter notebook的安装与使用详解
May 18 Python
python中pathlib模块的基本用法与总结
Aug 17 Python
python实现跨年表白神器--你值得拥有
Jan 04 Python
Python decorator拦截器代码实例解析
Apr 04 #Python
浅谈Python中os模块及shutil模块的常规操作
Apr 03 #Python
Python实现多线程下载脚本的示例代码
Apr 03 #Python
python实现将两个文件夹合并至另一个文件夹(制作数据集)
Apr 03 #Python
pycharm安装及如何导入numpy
Apr 03 #Python
解决pyPdf和pyPdf2在合并pdf时出现异常的问题
Apr 03 #Python
Python利用PyPDF2库获取PDF文件总页码实例
Apr 03 #Python
You might like
php生成随机数或者字符串的代码
2008/09/05 PHP
php获取数组长度的方法(有实例)
2013/10/27 PHP
非常有用的9个PHP代码片段
2016/04/06 PHP
浅析php如何实现App常用的秒发功能
2016/08/03 PHP
Laravel接收前端ajax传来的数据的实例代码
2017/07/20 PHP
js右键菜单效果代码
2007/07/21 Javascript
Javascript 类与静态类的实现(续)
2010/04/02 Javascript
jquery multiSelect 多选下拉框
2010/07/09 Javascript
基于JQuery的访问WebService的代码(可访问Java[Xfire])
2010/11/19 Javascript
js禁止小键盘输入数字功能代码
2011/08/01 Javascript
JavaScript中split() 使用方法汇总
2015/04/17 Javascript
js中日期的加减法
2015/05/06 Javascript
javascript获取重复次数最多的字符
2015/07/08 Javascript
Bootstrap每天必学之附加导航(Affix)插件
2016/04/25 Javascript
整理关于Bootstrap表单的慕课笔记
2017/03/29 Javascript
微信小程序 判断手机号的实现代码
2017/04/19 Javascript
ES6中javascript实现函数绑定及类的事件绑定功能详解
2017/11/08 Javascript
JavaScript闭包原理与用法实例分析
2018/08/10 Javascript
JS加密插件CryptoJS实现的Base64加密示例
2020/08/16 Javascript
微信小程序开发之tabbar图标和颜色的实现
2018/10/17 Javascript
Vue项目自动转换 px 为 rem的实现方法
2018/10/29 Javascript
Python制作Windows系统服务
2017/03/25 Python
Python装饰器(decorator)定义与用法详解
2018/02/09 Python
浅谈python中字典append 到list 后值的改变问题
2018/05/04 Python
numpy中矩阵合并的实例
2018/06/15 Python
python覆盖写入,追加写入的实例
2019/06/26 Python
python处理excel绘制雷达图
2019/10/18 Python
python中wheel的用法整理
2020/06/15 Python
Julep官网:美容产品和指甲油
2017/02/25 全球购物
红领巾心向党广播稿
2014/01/19 职场文书
2013年最新自荐信范文
2014/06/23 职场文书
工厂清洁工岗位职责
2015/02/14 职场文书
大学四年个人总结
2015/03/03 职场文书
关于成立领导小组的通知
2015/04/23 职场文书
Python import模块的缓存问题解决方案
2021/06/02 Python
数据设计之权限的实现
2022/08/05 MySQL