93
社区成员




课程:《Python程序设计》
班级: 2242
姓名: 张志恒
学号:20224217
实验教师:王志强
实验日期:2024年5月25~28日
必修/选修:专选课
Python综合应用:爬虫、数据处理、可视化、机器学习、神经网络、游戏、网络安全等(在Windows/Linux系统上使用VIM、PDB、IDLE、Pycharm等工具编程实现)。
一开始,我是想做王老师给出的一个示例,就是基于OCR技术提取图片中数据并填入Excel的实践,理论上只要把所需要的Python库装上了应该就会很方便快捷。从网站上可以找到应用各种不同的库来实现这个应用的教程,怎奈我无论采用我能找到的哪一种方法,总会遇到第三方库无法正常安装的问题。我又根据网上的解决方案尝试了更换国内镜像源、安装对应依赖包、使用命令提示符安装等方法,都始终没能完全解决问题。还有的解释说是Python的版本与库/包/模块不兼容,我尝试装不同版本的库,也不行。算了,我实在不想重新装另一版本的Python,何况我也搞不清楚哪个版本才会是有效的。
毕竟留下的时间也不多了。最终,我看到好多同学都玩了一手Python小游戏,还是决定做小游戏吧,就做个我还挺喜欢的2048的游戏。因为我还是并不了解小游戏在Python上实现的具体过程,于是我直接借鉴了CSDN上的教程,很快就装好了所需要的库,照着给出的代码敲了一遍,最终倒也能够顺利运行。不过,其实里面相当一部分代码的意义和用法我并不是很清楚,鉴于这点,我借助Pycharm自带的代码分析,以及文心大模型的分析,终于能够逐行了解为什么要这样做。
以下是具体过程。
首先要安装所需的numpy和pygame两个库。可以通过Pycharm自带的设置(Settings)>项目(Project)>解释器(Interpreter)查找并添加,或者直接在命令行中通过“pip install”命令来安装。
建立configs文件。导入argparse库,对窗口和方块的大小、间距、弧度等参数进行解析和默认设置。
建立Game2048文件。
导入所需的模块/包/库。
定义Game2048类,并定义一系列方法。
创建__init__()方法。设置窗口规格,字体字号字色,游戏初始及结束等。
定义Form(窗口)。利用pygame中的模块设置窗口的基本显示。
定义Action(行为)。利用for循环设置程序的终止和游戏的进行。
定义InitGame(初始化游戏)。设置游戏的初始状态。
定义CreatNum(生成数字)。在随机位置上生成随机数字。
定义GetEmpty(取空)。遍历一个4x4矩阵,找到所有值为0的元素的索引位置。
定义MoveUp/Down/Left/Right(向上/下/左/右划)。这是游戏的核心规则,也是比较难想的部分。
其工作原理大致如下:
遍历矩阵:代码通过两层循环遍历矩阵的每一行和每一列。
查找相同数字:对于当前位置(i, j)上的数字,它会检查上面的数字(即self.matrix[i-1][j],但在这个代码片段中,是通过一个变量index来追踪上一个非零元素的索引来实现的)。
合并逻辑:
如果当前数字和上面的数字相同(self.matrix[i][j] == self.matrix[index][j]),则它们会合并,并且分数会增加这两个数字的总和(self.score += self.matrix[i][j] + self.matrix[index][j])。
合并后,上面的数字会更新为两个数字的总和,而当前数字会变为0(表示已经被合并)。
index变量会更新为当前元素的索引,以便在下一次迭代中继续查找相同的数字。
处理空位置:如果上面的位置是空的(即self.matrix[index][j] == 0),则当前数字会移动到该位置,并将当前位置设为0。
更新索引:每次循环迭代,如果找到一个非零数字,index变量都会更新为该数字的索引。这确保了index总是指向当前非零数字上面的最近一个非零数字(或者,如果没有找到相同的数字,它就是上一个非零数字)。
分数累计:每次合并都会增加分数,但在这个代码片段中,分数只是临时存储在self.score中。你可能需要另一个变量(如self.total_score)来跟踪游戏的总分数。
定义JudgeGameOver/Success(判断游戏结束/胜利)。
定义Paint。设置游戏界面的标题、背景、分数、提示等元素。
建立main文件。调用configs中对应函数来定义main函数。创建Game2048类的一个实例game,并调用Form方法。
运行main则可以开始游戏啦!简单打了一把。
完整源代码如下:
configs.py
import argparse
def parse_args():
parser = argparse.ArgumentParser(description='Game2048')
# Form
"""
screen_width: Width of the form
screen_height: Height of the form
"""
parser.add_argument('--screen_width', default=400)
parser.add_argument('--screen_height', default=500)
# Block
"""
block_gap: Gap between two blocks
block_size: Size of a block
block_arc: Arc of a block
"""
parser.add_argument('--block_gap', default=10)
parser.add_argument('--block_size', default=86)
parser.add_argument('--block_arc', default=10)
return parser.parse_args()
Game2048.py
import os
import sys
import numpy
import random
import pygame
class Game2048:
def __init__(self, screen_width, screen_height, block_gap, block_size, block_arc):
""" 窗口 """
self.form = None
self.screen_width = screen_width
self.screen_height = screen_height
self.block_gap = block_gap
self.block_size = block_size
self.block_arc = block_arc
self.size = 4 # 矩阵 4 * 4
self.matrix = [] # 初始化矩阵 4 * 4 的 0 矩阵
""" 其他 """
self.is_over = False # 游戏是否结束
self.is_success = False # 游戏是否成功
self.score = 0
self.isadd = True # 是否添加数字
self.block_color = {
0: (205, 193, 180),
2: (238, 228, 218),
4: (237, 224, 200),
8: (242, 177, 121),
16: (245, 149, 99),
32: (246, 124, 95),
64: (246, 94, 59),
128: (237, 207, 114),
256: (237, 204, 97),
512: (237, 200, 80),
1024: (237, 197, 63),
2048: (237, 194, 46)
}
self.nums_color = {
0: (205, 193, 180),
2: (0, 0, 0),
4: (0, 0, 0),
8: (255, 255, 255),
16: (255, 255, 255),
32: (255, 255, 255),
64: (255, 255, 255),
128: (255, 255, 255),
256: (255, 255, 255),
512: (255, 255, 255),
1024: (255, 255, 255),
2048: (255, 255, 255)
}
""" 字体 """
self.title_font = '' # 窗口标题字体类型及大小: 2048
self.score_font = '' # 分数字体类型及大小
self.tips_font = '' # 说明字体类型及大小
self.font = '' # 数字字体
def Form(self):
"""
init(): 初始化所有导入的 pygame 模块
display.set_caption(title): 设置窗口的标题
display.set_mode(): 初始化一个准备显示的窗口或屏幕
display.update(): 使绘制的显示到窗口上
"""
pygame.init() # 初始化所有导入的 pygame 模块
pygame.display.set_caption("Game2048") # 窗口标题
os.environ['SDL_VIDEO_CENTERED'] = '1' # 窗口居中显示
self.form = pygame.display.set_mode([self.screen_width, self.screen_height], 0, 0) # 窗口大小
self.InitGame() # 矩阵的初始化
while True:
self.Action() # 用户行为: 按键/鼠标
self.Paint() # 表格绘制
pygame.display.update() # 使绘制的显示到窗口上
def Action(self):
for event in pygame.event.get(): # pygame.event.get(): 获取所有消息并将其从队列中删除
if event.type == pygame.QUIT: # pygame.QUIT: 窗口右上角的红 ×
sys.exit() # sys.exit()函数是通过抛出异常的方式来终止进程的
elif event.type == pygame.KEYDOWN:
""" 重新开始游戏 """
if event.key == pygame.K_ESCAPE: # print('ESC')
self.InitGame() # 游戏初始化
""" 向上划动 """
if event.key == pygame.K_UP and self.is_over == False: # print('UP')
self.MoveUp()
""" 向下划动 """
if event.key == pygame.K_DOWN and self.is_over == False: # print('DOWN')
self.MoveDown()
""" 向左滑动 """
if event.key == pygame.K_LEFT and self.is_over == False: # print('LEFT')
self.MoveLeft()
""" 向右滑动 """
if event.key == pygame.K_RIGHT and self.is_over == False: # print('RIGHT')
self.MoveRight()
def InitGame(self):
self.score = 0
self.is_over = False
self.is_success = False
self.matrix = numpy.zeros([self.size, self.size])
# 随机生成两个数
for i in range(2):
self.isadd = True
self.CreatNum() # 随机在一个位置生成一个数
def CreatNum(self):
l = self.GetEmpty() # 获取空白方格下标
if l and self.isadd:
""" 随机生成的数字 """
# 2, 4出现概率3:1
# random.randint(m, n): 随机生成[m, n]
value = 4 if random.randint(0, 3) % 3 == 0 else 2
""" 获取随机位置下标 """
x, y = random.sample(l, 1)[0]
""" 在随机位置上生成随机数字 """
self.matrix[x][y] = value
self.isadd = False
# print('CreatNum: {}'.format(value), (x, y))
# print(self.matrix)
def GetEmpty(self):
empty_positions = []
for i in range(4):
for j in range(4):
if self.matrix[i][j] == 0: # 假设matrix是一个4x4的二维列表,并且已经正确初始化
empty_positions.append([i, j])
return empty_positions
def MoveUp(self):
"""
向上移动,只需考虑第二行到第四行
共分为两种情况:
1、当前数字上边无空格,即上边值不为 0
a. 当前数字与上边数字相等,合并
b. 当前数字与上边数字不相等,continue
2、当前数字上边有空格,即上边值为 0, 上移
"""
for j in range(4):
index = 0
for i in range(1, 4):
if self.matrix[i][j] > 0:
if self.matrix[i][j] == self.matrix[index][j]:
# 当前数字 == 上边数字
""" 分数: 当前数字 + 上边数字
数值: 上边数字 = 上边数字 + 当前数字, 当前数字 = 0 """
self.score += self.matrix[i][j] + self.matrix[index][j]
self.matrix[index][j] = self.matrix[i][j] + self.matrix[index][j]
self.matrix[i][j] = 0
index += 1
self.isadd = True
# 当前数字与上边数字不相等,continue 可以省略不写
elif self.matrix[index][j] == 0:
# 当前数字上边有0
""" 分数: 不变
数值: 上边数字 = 当前数字, 当前数字 = 0 """
self.matrix[index][j] = self.matrix[i][j]
self.matrix[i][j] = 0
self.isadd = True
else:
index += 1
if self.matrix[index][j] == 0:
# index相当于慢指针,j相当于快指针
# 也就是说快指针和慢指针中间可能存在一个以上的空格,或者index和j并未相邻
# 上边数字 = 0
""" 分数: 不变
数值: 上边数字 = 当前数字, 当前数字 = 0 """
self.matrix[index][j] = self.matrix[i][j]
self.matrix[i][j] = 0
self.isadd = True
def MoveDown(self):
"""
向下移动,只需考虑第一列到第三列
共分为两种情况:
1、当前数字下边无空格,即下边值不为 0
a. 当前数字与下边数字相等,合并
b. 当前数字与下边数字不相等,continue
2、当前数字下边有空格,即下边值为 0, 下移
"""
for j in range(4):
index = 3
for i in range(2, -1, -1):
if self.matrix[i][j] > 0:
if self.matrix[i][j] == self.matrix[index][j]:
# 当前数字 == 下边数字
""" 分数: 当前数字 + 下边数字
数值: 下边数字 = 下边数字 + 当前数字, 当前数字 = 0 """
self.score += self.matrix[i][j] + self.matrix[index][j]
self.matrix[index][j] = self.matrix[i][j] + self.matrix[index][j]
self.matrix[i][j] = 0
index -= 1
self.isadd = True
# 当前数字与下边数字不相等,continue 可以省略不写
elif self.matrix[index][j] == 0:
# 当前数字下边有0
""" 分数: 不变
数值: 下边数字 = 当前数字, 当前数字 = 0 """
self.matrix[index][j] = self.matrix[i][j]
self.matrix[i][j] = 0
self.isadd = True
else:
index -= 1
if self.matrix[index][j] == 0:
# index相当于慢指针,j相当于快指针
# 也就是说快指针和慢指针中间可能存在一个以上的空格,或者index和j并未相邻
# 下边数字 = 0
""" 分数: 不变
数值: 下边数字 = 当前数字, 当前数字 = 0 """
self.matrix[index][j] = self.matrix[i][j]
self.matrix[i][j] = 0
self.isadd = True
def MoveLeft(self):
# print('left')
"""
Move Left
"""
"""
向左移动,只需考虑第二列到第四列
共分为两种情况:
1、当前数字左边无空格,即左边值不为 0
a. 当前数字与左边数字相等,合并
b. 当前数字与左边数字不相等,continue
2、当前数字左边有空格,即左边值为 0, 左移
"""
for i in range(4):
index = 0
for j in range(1, 4):
if self.matrix[i][j] > 0:
if self.matrix[i][j] == self.matrix[i][index]:
# 当前数字 == 左边数字
""" 分数: 当前数字 + 左边数字
数值: 左边数字 = 左边数字 + 当前数字, 当前数字 = 0 """
self.score += self.matrix[i][j] == self.matrix[i][index]
self.matrix[i][index] = self.matrix[i][j] + self.matrix[i][index]
self.matrix[i][j] = 0
index += 1
self.isadd = True
# 当前数字与左边数字不相等,continue 可以省略不写
elif self.matrix[i][index] == 0:
# 当前数字左边有0
""" 分数: 不变
数值: 左边数字 = 当前数字, 当前数字 = 0 """
self.matrix[i][index] = self.matrix[i][j]
self.matrix[i][j] = 0
self.isadd = True
else:
index += 1
if self.matrix[i][index] == 0:
# index相当于慢指针,j相当于快指针
# 也就是说快指针和慢指针中间可能存在一个以上的空格,或者index和j并未相邻
# 左边数字 = 0
""" 分数: 不变
数值: 左边数字 = 当前数字, 当前数字 = 0 """
self.matrix[i][index] = self.matrix[i][j]
self.matrix[i][j] = 0
self.isadd = True
def MoveRight(self):
# print('right')
"""
Move Right
"""
"""
向右移动,只需考虑第一列到第三列
共分为两种情况:
1、当前数字右边无空格,即右边值不为 0
a. 当前数字与右边数字相等,合并
b. 当前数字与右边数字不相等,continue
2、当前数字右边有空格,即右边值为 0, 右移
"""
for i in range(4):
index = 3
for j in range(2, -1, -1):
if self.matrix[i][j] > 0:
if self.matrix[i][j] == self.matrix[i][index]:
# 当前数字 == 右边数字
""" 分数: 当前数字 + 右边数字
数值: 右边数字 = 右边数字 + 当前数字, 当前数字 = 0 """
self.score += self.matrix[i][j] + self.matrix[i][index]
self.matrix[i][index] = self.matrix[i][j] + self.matrix[i][index]
self.matrix[i][j] = 0
index -= 1
self.isadd = True
# 当前数字与左边数字不相等,continue 可以省略不写
elif self.matrix[i][index] == 0:
# 当前数字右边有0
""" 分数: 不变
数值: 右边数字 = 当前数字, 当前数字 = 0 """
self.matrix[i][index] = self.matrix[i][j]
self.matrix[i][j] = 0
self.isadd = True
else:
index -= 1
if self.matrix[i][index] == 0:
# index相当于慢指针,j相当于快指针
# 也就是说快指针和慢指针中间可能存在一个以上的空格,或者index和j并未相邻
# 右边数字 = 0
""" 分数: 不变
数值: 右边数字 = 当前数字, 当前数字 = 0 """
self.matrix[i][index] = self.matrix[i][j]
self.matrix[i][j] = 0
self.isadd = True
def JudgeGameOver(self):
# 当空白空格不为空时,即游戏未结束
zerolist = self.GetEmpty()
if zerolist:
return False
# 当空白方格为空时,判断是否存在可合并的方格
for i in range(3):
for j in range(3):
if self.matrix[i][j] == self.matrix[i][j + 1]:
return False
if self.matrix[i][j] == self.matrix[i + 1][j]:
return False
# 若不满足以上两种情况,则游戏结束
return True
def JudgeGameSuccess(self):
# 检查是否有2048
if self.matrix.max() == 2048:
return True
return False
# 绘制表格
def Paint(self):
""" 游戏背景 """
# fill(color): 填充某一种颜色
self.form.fill((220, 220, 220))
""" 字体设置 """
# 初始化字体
pygame.font.init()
# 添加标题
# f = pygame.font.get_fonts() #: 获取字体样式
# pygame.font.Font.render(): 在一个新 Surface 对象上绘制文本
self.title_font = pygame.font.SysFont('幼圆', 50, True)
title_text = self.title_font.render('2048', True, (0, 0, 0))
self.form.blit(title_text, (50, 10))
# 添加分数: 得分: 0
pygame.draw.rect(self.form, (128, 128, 128), (250, 0, 120, 60))
self.score_font = pygame.font.SysFont('幼圆', 28, True)
score_text = self.score_font.render('得 分', True, (0, 0, 0))
self.form.blit(score_text, (275, 0))
digtial_score = self.score_font.render(str(int(self.score)), True, (255, 250, 250))
self.form.blit(digtial_score, (280, 30))
# 添加游戏说明
self.tips_font = pygame.font.SysFont('simsunnsimsun', 20)
tips_text = self.tips_font.render('option: up/down/left/right, press esc to restart', True, (0, 0, 0))
self.form.blit(tips_text, (25, 70))
""" 绘制方格 """
for i in range(4):
for j in range(4):
# (x, y) 方块的初始位置
x = j * self.block_size + (j + 1) * self.block_gap
y = i * self.block_size + (i + 1) * self.block_gap
# 绘制方块
value = int(self.matrix[i][j])
# print(value)
pygame.draw.rect(self.form, self.block_color[value], (x + 5, y + 100, self.block_size, self.block_size),
border_radius=self.block_arc)
# 数字字体即大小
if value < 10:
self.font = pygame.font.SysFont('simsunnsimsun', 46, True) # 数字2、4、8
value_text = self.font.render(str(value), True, self.nums_color[value])
self.form.blit(value_text, (x + 35, y + 120))
elif value < 100:
self.font = pygame.font.SysFont('simsunnsimsun', 40, True) # 数字16, 32, 64
value_text = self.font.render(str(value), True, self.nums_color[value])
self.form.blit(value_text, (x + 25, y + 120))
elif value < 1000:
self.font = pygame.font.SysFont('simsunnsimsun', 34, True) # 数字128, 256, 512
value_text = self.font.render(str(value), True, self.nums_color[value])
self.form.blit(value_text, (x + 15, y + 120))
else:
self.font = pygame.font.SysFont('simsunnsimsun', 28, True) # 数字1024, 2048
value_text = self.font.render(str(value), True, self.nums_color[value])
self.form.blit(value_text, (x + 5, y + 120))
self.CreatNum() # 新增数字
""" 如果游戏结束 """
self.is_over = self.JudgeGameOver()
if self.is_over:
over_font = pygame.font.SysFont("simsunnsimsun", 60, True)
str_text = over_font.render('Game Over!', True, (255, 255, 255))
self.form.blit(str_text, (30, 220))
""" 如果游戏成功 """
self.is_success = self.JudgeGameSuccess()
if self.is_success:
success_font = pygame.font.SysFont("simsunnsimsun", 60, True)
str_text = success_font.render('Successful!', True, (178, 34, 34))
self.form.blit(str_text, (10, 220))
main.py
import configs
from Game2048 import Game2048
def main(args):
screen_width = args.screen_width
screen_height = args.screen_height
block_gap = args.block_gap
block_size = args.block_size
block_arc = args.block_arc
game = Game2048(screen_width, screen_height, block_gap, block_size, block_arc)
game.Form()
if __name__ == '__main__':
args = configs.parse_args()
main(args)
从整个实验的过程来看,起初的问题主要集中在一些所需库/包/模块的安装使用。我对此很不熟悉,很多时候无法顺利完成安装又找寻不到合适的解决办法,加上时间限制,导致我的实践设计不得不做出调整。在了解了小游戏制作的相关教程后,新的问题变成了:看不懂代码。于是去弄懂(这个过程中我也发现了网上给的代码也存在着的一些错误)。总之,我深深感到还是没有玩转Python编程,感觉这个综合实践,就是一个边学边做的过程,一场探索发现之旅。也许,实践的意义往往在过程而非结果。不过,当代码终于成功运行时,还是会觉得挺激动的。
终于,结束了吗?一学期的Python编程课竟已进入了尾声。好像有点太快了吧。主要是,我还有好多Python的知识还没掌握好呢(在本次综合实践过后更加这样想)。当然,这大部分还是在于我自己规划给Python学习的时间太少了。真是说来惭愧。我明明也知道,这样一门技术类课程离不开大量的练习与实践。而王老师是极好的。王老师在课堂上是那么可爱,讲到复杂概念时举的生动有趣的例子,总不免令人会心一笑。王老师在课下是那么可敬,坚持和同学们一起思考探索解决报错的途径,不离不弃。也可能确实我在一些问题的理解上确实有困难。每次完成实验任务的时候总感觉王老师好像都把饭送到嘴边了我却吃不到。最终总会发现真的不会很难,但不知怎么的,在做实验的过程中却总是会被一些莫名其妙的问题给卡住。哈哈。
其实吧,我同时也觉得我从本门课程中得到的,绝非仅仅是Python程序设计的理论和应用。比如王老师的激励使我在六级过后又一次捡起了英语词汇的学习。当然,还有更重要的是,永远保持着对学习的热情,遇到问题不拖着,想尽一切办法也要将其解决,就像王老师一直以来展现给我们的那样,即使我们行政管理的同学专业基础差了很多,王老师也总是那样耐心。善解人意的王老师在我们回答不出问题的时候也表示理解和鼓励,学习有问题不要紧,解决要紧。在课堂上,王老师还为我们介绍了一些大模型的使用,引导我们正确地对待越来越强大的AI,通过对其合理的使用来促进我们的学习。真的十分感谢王老师,选这门课,值了!
如果我对于课程要有所建议的话,那就是请王老师一如既往地保持热情,保持理解。这样的老师的课,有谁会不喜欢呢?
人生苦短,我用Python!