20212205王子权 2021-2022-2 《Python程序设计》实验四报告
20212205 2021-2022-2 《Python程序设计》实验四报告
课程:《Python程序设计》
班级: 2122
姓名: 王子权
学号:20212205
实验教师:王志强
实验日期:2022年5月28日
必修/选修: 公选课
1.实验内容
Python综合应用:爬虫、数据处理、可视化、机器学习、神经网络、游戏、网络安全等。
注:在华为ECS服务器(OpenOuler系统)和物理机(Windows/Linux系统)上使用VIM、PDB、IDLE、Pycharm等工具编程实现。
2. 实验过程及结果
1.运用python编写五子棋小游戏
游戏规则:双方分别在棋盘上进行下棋操作,如果有一方达成了五子连珠,游戏宣布结束,如果在棋盘上没有空地的时候双方仍没有分出真正的胜负,则算平局
2.游戏源代码
#encoding:utf-8
import pygame
import sys
from pygame.locals import *
# 颜色常量
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
RED = (255, 0, 0)
GREEN = (0, 255, 0)
BLUE = (0, 0, 255)
# 错误码
G_POS_PLACED = -4
G_RANGE_ERR = -3
G_STAT_ERR = -2
G_ERR = -1
G_OK = 0
G_FINISH = 1
G_WIN = 2
class GoBang:
def __init__(self, map_size=16):
self.map_size = map_size
# map_size * map_size的二维列表,用于表示棋盘
# 0 ~ 无棋子, 1 ~ 黑棋,-1 ~ 白棋
self.map = [[0 for y in range(0, map_size)] for x in range(0, map_size)]
# 走棋的历史记录,用于悔棋。它是一个list,它的成员是一个元组(棋子类型,map.x,map.y)
self.move_stack = []
self.status = 0
self.winner = 0
def start_move(self):
self.status = 1
def get_last_move(self):
return self.move_stack[-1]
def get_winner(self):
return self.winner
def get_steps(self):
return len(self.move_stack)
# 判断输赢的算法: 只需要判断当前落子相关的四条直线(横、竖、左斜、右斜),是否形成5个连子。
# 将直线上的落子(黑~ 1,白~ -1),依次相加,连续的子绝对值之和达到5,即可判定为胜利
def __check_winner_(self):
tmp = 0
last_step = self.move_stack[-1]
# 竖向直线, x 固定
for y in range(0, self.map_size):
# 必须是连续的
if y > 0 \
and self.map[last_step[1]][y] != self.map[last_step[1]][y - 1]:
tmp = 0
tmp += self.map[last_step[1]][y]
if abs(tmp) >= 5:
return last_step[0]
# 横向直线, y 固定
tmp = 0
for x in range(0, self.map_size):
# 必须是连续的
if x > 0 \
and self.map[x][last_step[2]] != self.map[x - 1][last_step[2]]:
tmp = 0
tmp += self.map[x][last_step[2]]
if abs(tmp) >= 5:
return last_step[0]
# 右斜直线,计算出左上角顶点的坐标。然后x,y都递增,到达最右下角顶点。
tmp = 0
min_dist = min(last_step[1], last_step[2])
top_point = [last_step[1] - min_dist, last_step[2] - min_dist]
for incr in range(0, self.map_size):
# 不能超出棋盘边界
if top_point[0] + incr > self.map_size - 1 \
or top_point[1] + incr > self.map_size - 1:
break
# 必须是连续的
if incr > 0 \
and self.map[top_point[0] + incr][top_point[1] + incr] \
!= self.map[top_point[0] + incr - 1][top_point[1] + incr - 1]:
tmp = 0
tmp += self.map[top_point[0] + incr][top_point[1] + incr]
if abs(tmp) >= 5:
return last_step[0]
# 左斜直线,计算出右上角顶点的坐标。然后x递减、y递增,到达最左下角顶点。
tmp = 0
min_dist = min(self.map_size - 1 - last_step[1], last_step[2])
top_point = [last_step[1] + min_dist, last_step[2] - min_dist]
for incr in range(0, self.map_size):
# 不能超出棋盘边界
if top_point[0] - incr < 0 \
or top_point[1] + incr > self.map_size - 1:
break
# 必须是连续的
if incr > 0 \
and self.map[top_point[0] - incr][top_point[1] + incr] \
!= self.map[top_point[0] - incr + 1][top_point[1] + incr - 1]:
tmp = 0
tmp += self.map[top_point[0] - incr][top_point[1] + incr]
if abs(tmp) >= 5:
return last_step[0]
return 0
# 判断本局是否结束
def __check_(self):
# 所有步数已经走完
if len(self.move_stack) >= self.map_size ** 2:
return G_FINISH
# 赢了
winner = self.__check_winner_()
if winner != 0:
self.winner = winner
return G_WIN
# 未结束
return G_OK
# 走一步棋
def move(self, x, y):
if self.status != 1 and self.status != 2:
return G_STAT_ERR
if self.map_size <= x or x < 0 \
or self.map_size <= y or y < 0:
return G_RANGE_ERR
if self.map[x][y] != 0:
return G_POS_PLACED
t = 1 if self.status == 1 else -1
self.map[x][y] = t
self.move_stack.append((t, x, y))
# 判断是否结束
ret = self.__check_()
if self.is_finish(ret):
if ret == G_WIN:
self.__set_status(3)
else:
self.__set_status(4)
return ret
# 切换状态
last_step = self.move_stack[-1]
stat = 2 if last_step[0] == 1 else 1
self.__set_status(stat)
return G_OK
def __set_status(self, stat):
self.status = stat
def is_finish(self, err_code):
if err_code == G_FINISH \
or err_code == G_WIN:
return True
return False
# 悔一步棋
def rollback(self):
if len(self.move_stack) == 0:
return G_ERR
step = self.move_stack.pop()
self.map[step[1]][step[2]] = 0
# 刷新当前状态
if step[0] == 1: # 如果当前悔的是黑棋,那么状态切换为等待黑棋落子
self.status = 1
elif step[0] == -1:
self.status = 2
else:
return G_ERR
return G_OK
# 获取当前状态
# 0 ~ 未开局
# 1 ~ 等待黑棋落子
# 2 ~ 等待白棋落子
# 3 ~ 结束(一方获胜)
# 4 ~ 结束(棋盘走满)
def get_status(self):
return self.status
def get_move_stack(self):
return self.move_stack
class TigerGoBang(GoBang):
def __init__(self, map_size=16, map_unit=40):
self.SIZE = map_size
self.UNIT = map_unit
self.TITLE = '五子棋游戏'
self.PANEL_WIDTH = 200 # 右侧面板宽度
self.BORDER_WIDTH = 50 # 预留宽度
# 计算棋盘的有效范围
self.RANGE_X = [self.BORDER_WIDTH, self.BORDER_WIDTH + (self.SIZE - 1) * self.UNIT]
self.RANGE_Y = [self.BORDER_WIDTH, self.BORDER_WIDTH + (self.SIZE - 1) * self.UNIT]
# 计算状态面板的有效范围
self.PANEL_X = [self.BORDER_WIDTH + (self.SIZE - 1) * self.UNIT, \
self.BORDER_WIDTH + (self.SIZE - 1) * self.UNIT + self.PANEL_WIDTH]
self.PANEL_Y = [self.BORDER_WIDTH, self.BORDER_WIDTH + (self.SIZE - 1) * self.UNIT]
# 计算窗口大小
self.WINDOW_WIDTH = self.BORDER_WIDTH * 2 \
+ self.PANEL_WIDTH \
+ (self.SIZE - 1) * self.UNIT
self.WINDOW_HEIGHT = self.BORDER_WIDTH * 2 \
+ (self.SIZE - 1) * self.UNIT
# 父类初始化
super(TigerGoBang, self).__init__(map_size=map_size)
# 初始化游戏
self.__game_init_()
# 绘制棋盘
def __draw_map(self):
# 绘制棋盘
POS_START = [self.BORDER_WIDTH, self.BORDER_WIDTH]
s_font = pygame.font.SysFont('arial', 16)
# 绘制行
for item in range(0, self.SIZE):
pygame.draw.line(self.screen, BLACK,
[POS_START[0], POS_START[1] + item * self.UNIT],
[POS_START[0] + (self.SIZE - 1) * self.UNIT, POS_START[1] + item * self.UNIT],
1)
s_surface = s_font.render(f'{item + 1}', True, BLACK)
self.screen.blit(s_surface, [POS_START[0] - 30, POS_START[1] + item * self.UNIT - 10])
# 绘制列
for item in range(0, self.SIZE):
pygame.draw.line(self.screen, BLACK,
[POS_START[0] + item * self.UNIT, POS_START[1]],
[POS_START[0] + item * self.UNIT, POS_START[1] + (self.SIZE - 1) * self.UNIT],
1)
s_surface = s_font.render(chr(ord('A') + item), True, BLACK)
self.screen.blit(s_surface, [POS_START[0] + item * self.UNIT - 5, POS_START[1] - 30])
# 绘制棋子
def __draw_chess(self):
mst = self.get_move_stack()
for item in mst:
x = self.BORDER_WIDTH + item[1] * self.UNIT
y = self.BORDER_WIDTH + item[2] * self.UNIT
t_color = BLACK if item[0] == 1 else WHITE
pygame.draw.circle(self.screen, t_color, [x, y], int(self.UNIT / 2.5))
# 全部重绘
def __redraw_all(self):
# 重刷背景图
self.screen.blit(pygame.image.load(r"bg.jpg"), (0, 0))
# 绘制棋盘
self.__draw_map()
# 绘制棋子
self.__draw_chess()
# 绘制面板
self.__draw_panel_()
def __game_init_(self):
# 初始化pygame
pygame.init()
# 设置窗口的大小,单位为像素
self.screen = pygame.display.set_mode((self.WINDOW_WIDTH, self.WINDOW_HEIGHT))
# 设置窗口标题
pygame.display.set_caption(self.TITLE)
# 设置背景颜色
# self.screen.fill(WHITE)
background = pygame.image.load(r"hhh.jpg")
self.screen.blit(background, (0, 0))
# 绘制棋盘
self.__draw_map()
# 绘制右侧的状态面板
self.__draw_panel_()
def __draw_panel_(self):
# panel区域重绘,用白色矩形覆盖
pygame.draw.rect(self.screen, WHITE,
[self.PANEL_X[0] + 30, 0,
1000, 1000])
self.panel_font = pygame.font.SysFont('simhei', 20)
# 走棋状态
stat = self.get_status()
if stat == 0:
stat_str = '点击开始按钮'
elif stat == 1:
stat_str = '等待黑棋落子..'
elif stat == 2:
stat_str = '等待白棋落子..'
elif stat == 4:
stat_str = '游戏结束!'
elif stat == 3:
winner = self.get_winner()
if winner == 1:
stat_str = '黑棋获胜!'
else:
stat_str = '白棋获胜!'
else:
stat_str = ''
self.surface_stat = self.panel_font.render(stat_str, False, BLACK)
self.screen.blit(self.surface_stat, [self.PANEL_X[0] + 50, self.PANEL_Y[0] + 50])
# 步数
steps = self.get_steps()
self.surface_steps = self.panel_font.render(f'步数: {steps}', False, BLACK)
self.screen.blit(self.surface_steps, [self.PANEL_X[0] + 50, self.PANEL_Y[0] + 150])
# 新的一局
offset_x = self.PANEL_X[0] + 50
offset_y = self.PANEL_Y[0] + 400
btn_h = 50
btn_w = 150
btn_gap = 20
btn_text_x = 35
btn_text_y = 15
self.BTN_RANGE_NEW_START_X = [offset_x, offset_x + btn_w]
self.BTN_RANGE_NEW_START_Y = [offset_y, offset_y + btn_h]
pygame.draw.rect(self.screen, BLACK,
[offset_x, offset_y,
btn_w, btn_h])
self.surface_btn = self.panel_font.render(f'新开一局', False, WHITE)
self.screen.blit(self.surface_btn, [offset_x + btn_text_x, offset_y + btn_text_y])
# 退出游戏
self.BTN_RANGE_EXIT_GAME_X = [offset_x, offset_x + btn_w]
self.BTN_RANGE_EXIT_GAME_Y = [offset_y + btn_h
+ btn_gap,
offset_y + btn_h + btn_gap + btn_h]
pygame.draw.rect(self.screen, BLACK,
[offset_x, offset_y + btn_h + btn_gap,
btn_w, btn_h])
self.surface_btn = self.panel_font.render(f'退出游戏', False, WHITE)
self.screen.blit(self.surface_btn,
[offset_x + btn_text_x, offset_y + btn_h + btn_gap + btn_text_y])
# 悔棋
self.BTN_RANGE_RB_X = [offset_x, offset_x + btn_w]
self.BTN_RANGE_RB_Y = [offset_y + (btn_h +
btn_gap) * 2,
offset_y + (btn_h + btn_gap) * 2 + btn_h]
pygame.draw.rect(self.screen, BLACK,
[offset_x, offset_y + (btn_h + btn_gap) * 2,
btn_w, btn_h])
self.surface_btn = self.panel_font.render(f'悔一步棋', False, WHITE)
self.screen.blit(self.surface_btn,
[offset_x + btn_text_x, offset_y + (btn_h + btn_gap) * 2 + btn_text_y])
def __do_move_(self, pos):
# 落子在棋盘之外无效
if pos[0] < self.RANGE_X[0] or pos[0] > self.RANGE_X[1] \
or pos[1] < self.RANGE_Y[0] or pos[1] > self.RANGE_Y[1]:
return G_ERR
# 判断当前落子的位置,需要吸附在最近的落棋点
s_x = round((pos[0] - self.BORDER_WIDTH) /
self.UNIT)
s_y = round((pos[1] - self.BORDER_WIDTH) /
self.UNIT)
x = self.BORDER_WIDTH + self.UNIT * s_x
y = self.BORDER_WIDTH + self.UNIT * s_y
# 先move,再draw
ret = self.move(s_x, s_y)
if ret < 0:
return G_ERR
# draw
last_move = self.get_last_move()
t_color = BLACK if last_move[0] == 1 else WHITE
pygame.draw.circle(self.screen, t_color, [x, y], int(self.UNIT / 2.5))
# pygame.draw.circle(self.screen, BLACK, [x, y],
int(self.UNIT / 2.5), 1)
self.__draw_panel_()
if self.get_status() >= 3:
return G_OK
def __do_rollback_(self):
if self.rollback() == G_OK:
self.__redraw_all()
def __do_new_start(self):
self.__init__()
self.start()
def __do_btn_(self, pos):
# 是否点击了按钮
if self.BTN_RANGE_NEW_START_X[0] < pos[0] < self.BTN_RANGE_NEW_START_X[1] \
and self.BTN_RANGE_NEW_START_Y[0] < pos[1] < self.BTN_RANGE_NEW_START_Y[1]:
self.__do_new_start()
return G_OK
elif self.BTN_RANGE_EXIT_GAME_X[0] < pos[0] < self.BTN_RANGE_EXIT_GAME_X[1] \
and self.BTN_RANGE_EXIT_GAME_Y[0] < pos[1] < self.BTN_RANGE_EXIT_GAME_Y[1]:
sys.exit()
elif self.BTN_RANGE_RB_X[0] < pos[0] < self.BTN_RANGE_RB_X[1] \
and self.BTN_RANGE_RB_Y[0] < pos[1] < self.BTN_RANGE_RB_Y[1]:
self.__do_rollback_()
return G_OK
else:
return G_ERR
def start(self):
self.start_move()
self.__draw_panel_()
# 程序主循环
while True:
# 获取事件
for event in pygame.event.get():
# 判断事件是否为退出事件
if event.type == QUIT:
# 退出pygame
pygame.quit()
# 退出系统
sys.exit()
# 落子事件
if event.type == MOUSEBUTTONUP:
if self.__do_btn_(event.pos) < 0:
# 非按钮事件,则处理走棋
self.__do_move_(event.pos)
# 绘制屏幕内容
pygame.display.update()
if __name__ == '__main__':
inst1 = TigerGoBang(map_unit=40)
inst1.start()
3. 小游戏运行界面(为了演示更加清楚,在本地进行运行)
如图所示
这时我们使用鼠标进行点击操作
经历一番战斗,黑棋获得最终胜利
4. 用putty在云服务器上运行该文件(游戏时间!!!!
3. 实验过程中遇到的问题和解决过程
- 问题1:在进行环境配置的时候,pygame库经常出现无法下载的情况
- 问题1解决方案:在同学们的帮助之下,这个问题得到了解决
- 问题2:在游戏运行时,无法显示游戏界面
- 问题2解决方案:在本地安装xming使其可以在桌面上显示界面
- 问题3:在安装完成xming之后无法进行界面显示
- 问题3解决方案:在putty的初始界面上进行对于xming的兼容和连接
- 问题4:在本地运行时的文字,在云端变成了乱码
- 问题4解决方案:知道由于文字在两个终端不同的编码方式,但是由于时间关系以及能力有限没能解决这个问题,是一个不小的遗憾吧
4. 课程小结
其实,当初选择计算机这个专业也是因为觉得那些程序员大佬在键盘上飞速进行代码的录入非常帅气,对于一行行的代码能组成这样高质量的程序软件也感到十分惊奇。但是在真正进入这个领域之后我才发现我实在是太天真了,还记得第一次上计导课的时候,那些纷繁复杂的概念搞的我一头雾水。自从那时候我就发现自己要学的东西还有很多很多。在这股动力的引导下,我报名了志强老师的python课程,希望能在这个课上学习到自己想要的东西。对于一个初次学习如何编写程序的小白来说同时学习两门语言其实是一个十分艰难的事情(python和c)。但我依旧觉得我可以迎难而上。在第一天上python课的时候我对于志强老师的讲课方法和讲课思路十分敬佩,也让我有幸结识了这门十分方便快捷的语言————python(后来才知道python是以编译器巨大负荷为代价给程序员带来便捷的一门语言)初期并未觉得它和c的不同。那节面向对象的编程方式可以说直接推翻了我对于编程的固有认知,可以说是开辟了一条新的道路。至于后面的socket和爬虫学习就是摆脱了单机的编程模式,尝试着和外界网络的互动和联通了。后几次作业难度非常高,但也同时激起了我们在ddl压迫下自我学习的动力。经历这几次作业特别是这最后一次作业之后,我对于python的理解更加深刻。或许这就是志强老师在有限的课时内想让我们学到更多东西的方式吧。在这里不得不说一句老师有心了。说实话在我写结课总结之前,肚子里面是怨气滔天,恨不得说老师你的课程设计不合理。但是在我写下自己的感悟体会的时候,回想起自己从什么都不会的小白成为一个程序员半吊子(或许也算不上)。逐渐能理解老师的良苦用心了,怨气自然消失之后,剩下的就是对老师的敬佩了吧,总之,这门课激起了我在暑假继续学习python的兴趣。也让我感激遇见了一个可以循循善诱的老师。如果下次有机会一定会再考虑上老师您的课程。人生苦短,我用python!!!
另外再给个小建议,希望每次课之后的源码都能发群里一份,这样我们自己理解起来也会更加方便。
参考资料