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操作sqlite的CRUD实例分析
May 08 Python
Fabric 应用案例
Aug 28 Python
Python标准模块--ContextManager上下文管理器的具体用法
Nov 27 Python
Python3的介绍、安装和命令行的认识(推荐)
Oct 20 Python
python3 property装饰器实现原理与用法示例
May 15 Python
Python获取当前脚本文件夹(Script)的绝对路径方法代码
Aug 27 Python
Python pandas.DataFrame 找出有空值的行
Sep 09 Python
pytorch 彩色图像转灰度图像实例
Jan 13 Python
在python中实现求输出1-3+5-7+9-......101的和
Apr 02 Python
解决PyCharm无法使用lxml库的问题(图解)
Dec 22 Python
教你使用一行Python代码玩遍童年的小游戏
Aug 23 Python
python_tkinter事件类型详情
Mar 20 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
Flash空降上海 化身大魔王接受挑战
2020/03/02 星际争霸
dedecms中使用php语句指南
2014/11/13 PHP
PHP编程中的__clone()方法使用详解
2015/11/27 PHP
JavaScript 面向对象之命名空间
2010/05/04 Javascript
JS中动态添加事件(绑定事件)的代码
2011/01/09 Javascript
javascript学习笔记(十) js对象 继承
2012/06/19 Javascript
一个封装js代码-----展开收起效果示例
2013/07/03 Javascript
js制作简易年历完整实例
2015/01/28 Javascript
JavaScript实现点击单元格改变背景色的方法
2016/02/12 Javascript
Javascript基础之数组的使用
2016/05/13 Javascript
JavaScript实现页面无操作倒计时退出
2016/10/22 Javascript
bootstrapValidator表单验证插件学习
2016/12/30 Javascript
Vue2.x中的父子组件相互通信的实现方法
2017/05/02 Javascript
JavaScript ES6中const、let与var的对比详解
2017/06/18 Javascript
浅谈React Native 中组件的生命周期
2017/09/08 Javascript
JS验证输入的是否是数字及保留几位小数问题
2018/05/09 Javascript
JavaScript 九种跨域方式实现原理
2019/02/11 Javascript
迅速了解一下ES10中Object.fromEntries的用法使用
2019/03/05 Javascript
vue父子组件的通信方法(实例详解)
2019/11/10 Javascript
Python中的descriptor描述器简明使用指南
2016/06/02 Python
Python 多线程Threading初学教程
2017/08/22 Python
Django中STATIC_ROOT和STATIC_URL及STATICFILES_DIRS浅析
2018/05/08 Python
pycharm 将python文件打包为exe格式的方法
2019/01/16 Python
教你如何编写、保存与运行Python程序的方法
2019/07/12 Python
解析python的局部变量和全局变量
2019/08/15 Python
pip 安装库比较慢的解决方法(国内镜像)
2019/10/06 Python
通过HTML5 Canvas API绘制弧线和圆形的教程
2016/03/14 HTML / CSS
中粮集团旗下食品网上购物网站:我买网
2016/09/22 全球购物
英国比较机场停车场网站:Airport Parking Essentials
2019/12/01 全球购物
Brasty罗马尼亚:购买手表、香水、化妆品、珠宝
2020/04/21 全球购物
日本最大的彩色隐形眼镜销售网站:CharmColor
2020/09/09 全球购物
关于廉洁的广播稿
2014/01/30 职场文书
销售助理岗位职责
2014/02/21 职场文书
全国税务系统先进集体事迹材料
2014/05/19 职场文书
《牧场之国》教学反思
2016/02/22 职场文书
解决pytorch-gpu 安装失败的记录
2021/05/24 Python