用PyGame设计益智游戏《八皇后》——代码详解

用PyGame设计益智游戏《八皇后》——代码详解

本教程的原始代码可以从这里下载

我在原文的基础上进行修改,原文在拖拽后的处理有bug,在此代码中我做了修正,修正后的代码从这里下载
原文地址:https://thecodingfun.com/2021/06/29/design-puzzle-game-with-pygame-a-detailed-code-analysis/

在上一篇文章中,我介绍了如何使用 PyGame 将 Scratch 项目转换为 Python 项目。如果您对该项目感兴趣,可以参考这篇文章:森林迷宫。

这场比赛被命名为八皇后。如果你还没有听说过八皇后这个游戏,你可以在网上搜索一下。八皇后谜题是将八个象棋皇后放在一个 8×8 的棋盘上,这样两个皇后就不会相互威胁。因此,解决方案要求没有两个皇后共享相同的行、列或对角线。

现在让我们一步步开始分析。游戏的最终效果如下视频所示。

一般来说,有两种设计这个游戏的方法。您可以设计一种算法,让计算机自动为您生成解决方案,如本页所示:https : //www.geeksforgeeks.org/8-queen-problem/。

在这个游戏中,我们正在设计一个游戏,用户可以下棋并改变棋位,以便他们在多次反复试验后找到解决方案。

整个项目由两个独立的 Python 文件组成。Puzzle_game.py 是使用 PyGame 编码结构与用户交互并展示可视化界面。另一个8_queens.py用于定义后端数据处理和有效性检查。

Puzzle_game.py

这个文件包含了整个益智游戏的核心代码。

在文件的开头,程序导入了几个模块,包括 pygame、math 和八皇后。模块“eight_queens”是我后面要介绍的自定义模块。

import pygame, sys, random
from pygame.locals import *
import math
import eight_queens,buttonBASICFONTSIZE = 20class QueenSprite:def __init__(self, img, target_posn):'''在棋盘当前位置创建和初始化一个皇后'''self.image = imgself.target_posn = target_posnself.posn = self.target_posnself.y_velocity = 0self.dragging = False

导入模块后,从第 8 行到第 41 行,程序定义了一个名为“QueenSprite”的类。这个类代表棋盘上的皇后棋。对于一个8*8的棋盘,总共需要放置8个皇后。在 __Init__ 函数中,该类定义了几个属性。“图像”属性被分配一个图像对象,代表国际象棋皇后图像。“posn”属性是一个元组类型,表示对象的x和y位置,而“dragging”属性是一个布尔值,表示对象是否被拖动。另外两个属性“target_posn”和“y_velocity”与当前项目中没有实现的其他功能相关,这里我们可以忽略它们。

从第 20 行到第 41 行,程序定义了类的一些方法。方法“drag_with_mouse”检查拖动属性是否为 True。如果是,该方法将更新对象的“posn”属性以紧密跟随鼠标位置。方法“draw”会将对象图像 blit 到显示表面。当我们调用“blit”时,我们可以指定图像应该放置在主表面上的位置。术语“blit”广泛用于计算机图形中,意思 是将像素从一个内存区域快速复制到另一个区域

最后一个方法是“mouse_touch_sprite”。它返回一个布尔值,用于检查 QueenSprite 对象是否触摸鼠标。

如果您熟悉 Scratch 编程,很容易找到匹配的 Scratch 编程块。“drag_with_mouse”对应的是Scratch编程中的“go to mouse-pointer”编程块,而“mouse_touch_sprite”相当于“if touching mouse-pointer”的编程块。

对于“draw”方法,你在Scratch中找不到对应的block,因为在Scratch中,你不需要处理“blit”或者更新每个精灵的显示。PyGame 假设您已经掌握了足够的技能,因此您需要使用 PyGame 库提供的基础方法来设计自己的功能。

在这里插入图片描述

以上是QueenSprite类的定义。从第 44 行开始,代码开始了函数“draw_board”,它是整个项目的主要函数。它首先通过调用“pygame.init()”来启动pygame状态,并定义了一个元组[(255, 0, 0), (0, 0, 0)],代表红色和黑色。

请注意,输入参数“n”表示棋盘的网格大小。如果 n = 8,则表示棋盘为 8 * 8。您可以定义更大的棋盘,例如 13 * 13 网格大小,但这意味着您的用户解决此难题会更加复杂。

从第49行到第51行,程序将棋盘分成n块,重新计算整体大小。然后调用 pygame.display() 方法来设置窗口标题、字体类型和字体大小。

在这里插入图片描述
从第 58 行到第 63 行,程序加载了球图像。它还计算球位置相对于每个国际象棋网格左上角的偏移量。在这个演示项目中,我使用球图像来代表皇后。您可以用更多类似女王的图像替换它。

在第 69 行和第 70 行,程序创建了一个空列表来存储 QueenSprite 对象。chess_board 列表用于存储 QueenSprite 对象的位置信息。当Eight_queens.py 判断国际象棋布局的有效性时,它会与chess_board 列表进行交互。

变量“FPS”用于设置 Pygame 的帧速率。在第169行,代码“fpsClock.tick(FPS)”的目的是保证帧率固定在FPS的值上,这样游戏就不会跑得太快。如果不设置此帧速率,Pygame 可以以高达每秒 3000 的帧速率运行,具体取决于您的计算机性能。

在这里插入图片描述
从第 84 行到第 169 行,程序进入 Pygame 结构的主循环。编写游戏的现代方法(现在我们拥有快速的计算机和快速的显卡)是在游戏循环的每次迭代中从头开始重新绘制所有内容。这就是 Pygame 所做的。

在每个循环中,棋盘需要重新绘制自己。从第 86 行到第 92 行的代码实现了这个功能。它通过重复嵌套循环来绘制自己。每次绘制都会改变填充颜色,所以最终的效果是一个黑色和红色的方格棋盘。

在这里插入图片描述

绘制棋盘后,从第 95 行开始,程序进入一个事件循环。这个结构被定义为捕捉键盘和鼠标事件。当鼠标被按下时,程序将检查是否有任何 QueenSprite 对象接触了鼠标。如果是,程序将对象的拖动属性设置为 True 并清除 chess_board 列表中相应项目的值。

从第 115 行到第 132 行,程序检查鼠标抬起时的行为。它首先检查放置在当前单元格中的国际象棋皇后是否与任何现有的 QueenSprite 对象冲突。如果有任何水平、垂直或对角的 QueenSprite 冲突,程序将拒绝放下 QueenSprite。

从第 134 行到第 140 行,如果程序验证它是有效的移动,则需要进一步检查程序是否正在拖动现有的 QueenSprite 对象。如果是,程序将对象的“posn”属性设置为当前单元格位置,并将其“拖动”属性设置为 False。这意味着,QueenSprite 固定在当前位置,不再用鼠标移动。
在这里插入图片描述

在这里插入图片描述
在第 144 行,有一个变量“drag_existing”来指示程序是否正在拖动现有的 QueenSprite 对象。如果变量值为 False,则表示用户正在放下皇后,因此程序在当前位置创建一个新的 QueenSprite 并将其附加到 all_sprites 列表中。

在这里插入图片描述

从第154行到第164行,程序通过“blit”方法将精灵绘制到显示面上。然而,直到这一刻,用户还不能看到它们。
这不是错误,而是这样设计的。如果您的图形显示硬件在程序写入内存的同时尝试从内存中读取,它们会相互干扰,导致视频噪声和闪烁。为了解决这个问题,PyGame 为主表面保留了两个缓冲区—— 程序绘制到的 后台缓冲区前台缓冲区 ,而 前台缓冲区 显示给用户。每次程序完全准备好后台缓冲区时,它都会通过调用“pygame.display.update()”(在第 168 行)来更新前台缓冲区,并且将发生的变换显示给用户。
在这里插入图片描述
以下模块由上述 Puzzle_game.py 导入。它包含两个功能。主要函数是“has_clashes_2”,它检查任何两个 QueenSprite 对象是否相互冲突。如果有任何冲突,则返回 False,否则返回 True。上面的文件在第 129 行调用这个函数来检查对角线冲突。

本模块中的功能与Pygame的代码结构无关,这里简单说明一下。

八皇后.py 模块

import randomdef share_diagonal(x0,y0,x1,y1):dy = y1-y0dx = x1 - x0return abs(dx)==abs(dy)def col_clashes(exist_chess, index):for i in range(index):if share_diagonal(i,exist_chess[i], index, exist_chess[index]):return Truereturn Falsedef has_clashes_2(chess_list):for i, item in enumerate(chess_list):if item == -1:continuefor j, pre_item in enumerate(chess_list):if j >= i:breakif pre_item == -1:continueif share_diagonal(j,pre_item, i,item):print("conflict: index{0} value {1} conflicts with index {2} value {3}".format(j,pre_item,i, item))return Truereturn False

按钮模块

from pygame import font
import pygame.rectclass Button():def __init__(self,surface,text,rect_w,rect_h):self.surface=surfaceself.text = textself.button_color = (0,255,0)self.text_color=(255,255,255)self.font=font.SysFont(None,48)self.rect=pygame.rect.Rect(0,0,rect_w,rect_h)self.text_image= self.font.render(self.text,True,self.text_color,self.button_color)self.text_rect=self.text_image.get_rect()def draw(self,x,y):self.rect.x=xself.rect.y=yself.text_rect.center = self.rect.centerself.surface.fill(self.button_color, self.rect)self.surface.blit(self.text_image,self.text_rect)

这就是这个益智游戏的所有代码。希望本文能帮助您更多地了解如何设计 Python 游戏并利用 OOP 的概念。享受编码并玩得开心!

EightQuess.py

import pygame, sys, random
from pygame.locals import *
import math
import eight_queens,buttonBASICFONTSIZE = 20class QueenSprite:def __init__(self, img, target_posn):'''在棋盘当前位置创建和初始化一个皇后'''self.image = imgself.target_posn = target_posnself.posn = self.target_posnself.y_velocity = 0self.dragging = Falsedef update(self):returndef drag_with_mouse(self, mousex, mousey):if self.dragging:self.posn = (mousex,mousey)return Nonedef draw(self, target_surface):target_surface.blit(self.image, self.posn)def mouse_touch_sprite(self, mousex, mousey):center_x = self.posn[0]center_y = self.posn[1]distance_mouse = math.sqrt((mousex - center_x)**2 + (mousey-center_y)**2)if distance_mouse < 40:print('distance to mouse:',distance_mouse)return Trueelse:return Falsedef draw_board(n):pygame.init()colors = [(255,0,0), (0,0,0)]# n = len(the_board)surface_sz = 480sq_sz = surface_sz // nsurface_sz = n * sq_sz# 设置屏幕的标题、字体和字体大小display_surface = pygame.display.set_mode((surface_sz, surface_sz+ 80))pygame.display.set_caption('Eight Queens Game')BASICFONT = pygame.font.Font(None, BASICFONTSIZE)# 加载皇后图像并计算皇后图像的偏移量queen_image = pygame.image.load(r'Image\queen.png')queen_image.convert()queen_image = pygame.transform.rotozoom(queen_image, 0, 0.15)queen_offset = (sq_sz - queen_image.get_width()) // 2# 创建胜利之后的图像win_image = pygame.image.load(r'Image\win.png').convert_alpha()win_image = pygame.transform.rotozoom(win_image,0, 0.3)# 创建按钮btn_clear = button.Button(display_surface,'clear all',surface_sz,80)# 创建了一个空列表来存储 QueenSprite 对象。# chess_board 列表用于存储 QueenSprite 对象的位置信息。all_sprites = []chess_board = [-1] * n# 设置当前程序的运行频率FPS = 30fpsClock = pygame.time.Clock()# 加载背景音乐# soundObj = pygame.mixer.Sound(r'Music\background_music.mp3')# soundObj.play(-1,0,0)# 程序主循环is_win = False#它通过重复嵌套循环来绘制自己。每次绘制都会改变填充颜色,# 所以最终的效果是一个黑色和红色的方格棋盘。while True:# 重新绘制棋盘for row in range(n):  # 绘制棋盘的每一行c_indx = row % 2  # 改变每一行中首个棋盘的颜色for col in range(n):  # 绘制每一列the_square = (col*sq_sz, row*sq_sz, sq_sz, sq_sz)display_surface.fill(colors[c_indx], the_square)# 改变下一个棋格的颜色c_indx = (c_indx +1) % 2btn_clear.draw(0,surface_sz)#捕获游戏中的键盘和鼠标事件for event in pygame.event.get():if event.type == QUIT:pygame.quit()sys.exit()elif event.type == MOUSEMOTION:mousex, mousey = event.pos# 当鼠标被按下时,程序将检查是否有任何 QueenSprite 对象接触了鼠标。# 如果是,程序将对象的拖动属性设置为 True 并清除 chess_board 列表中相应项目的值。elif event.type == MOUSEBUTTONDOWN:mousex, mousey = event.poscol_index = mousex // sq_szrow_index = mousey // sq_szfor item in all_sprites:if (not item.dragging) and item.mouse_touch_sprite(mousex, mousey):print("draging")item.dragging = Truechess_board[row_index] = -1breakelif event.type == MOUSEBUTTONUP:mousex, mousey = event.posbtn_clicked = btn_clear.rect.collidepoint(mousex,mousey)if btn_clicked:all_sprites = []chess_board = [-1] * nis_win = FalsebreakmouseClicked = Truecol_index = mousex // sq_szrow_index = mousey // sq_szif chess_board[row_index] != -1 or (col_index in chess_board):print('在水平或垂直上发生冲突') #if there is chess in the same column or row,it indicates a conflictprint(chess_board)else:# 检查对角线上的冲突chess_board[row_index] = col_indexprint(chess_board)if eight_queens.has_clashes_2(chess_board):print("对角线上有冲突")chess_board[row_index] = -1print(chess_board)else:drag_existing = Falsefor item in all_sprites:if item.dragging:drag_existing = Trueitem.dragging = Falseitem.posn = (col_index * sq_sz + queen_offset, row_index * sq_sz + queen_offset)break# 变量“drag_existing”来指示程序是否正在拖动现有的 QueenSprite 对象。# 如果变量值为 False,则表示用户正在放下新棋,# 因此程序在当前位置创建一个新的 QueenSprite 并将其附加到 all_sprites 列表中。if not drag_existing:a_queen = QueenSprite(queen_image, (col_index * sq_sz + queen_offset, row_index * sq_sz + queen_offset))all_sprites.append(a_queen)# 判断是否完成所有皇后的摆放if -1 not in chess_board:is_win = True# 程序通过“blit”方法将精灵绘制到显示面上for sprite in all_sprites:# 拖动的精灵将随鼠标移动if sprite.dragging:sprite.drag_with_mouse(mousex - queen_offset, mousey - queen_offset)else:sprite.update()sprite.draw(display_surface)if is_win:display_surface.blit(win_image, ((surface_sz-win_image.get_width())/2,(surface_sz-win_image.get_height())/2))pygame.display.update()fpsClock.tick(FPS)if __name__ == '__main__':while True:s=input("请输入皇后的数量N,N ≥ 4:")if s.isdigit():n = int(s)if n >= 4:breakdraw_board(n)


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部