0 概况
招新小游戏是使用C++与Python设计的几款小游戏,并使用Pyside2设计GUI界面。对于萌新们来说,短时间学会游戏开发并不是很实际,本文旨在让大家简单体会游戏的开发过程,如何设计框架、调用模块、编写游戏以及实现代码封装,并不要求大家看完就能完全理解并具备开发游戏的能力。我们还是要从程序设计基础开始,经过APA团队2-3个月的C语言零基础训练,掌握编写程序的能力,感受程序底层的逻辑思维。学成之后再回过头来,自然会发现这样的游戏设计原来是这么简单且有趣的过程呀。
tips:关于密钥生成的规律在章节4.2
1 2048
该游戏是基于pygame库设计的一款简单小游戏。
关于pygame的介绍以及基本用法,详情可见:
游戏设计的主体模块包括:
- 可视化界面和键盘交互
- 随机生成新的数字方块
- 数字方块的合并
- 输赢判定
(1)可视化界面和键盘交互
通过pygame提供的创建窗口的方法,我们可以很方便地创建出和用户交互的可视化窗口。
class Game2048:
def __init__(self):
# 初始化 Pygame 库
pygame.init()
# 创建游戏窗口
self.screen = pygame.display.set_mode((WIDTH, HEIGHT))
# 设置窗口标题
pygame.display.set_caption("2048")
# 创建一个 4x4 的游戏棋盘
self.board = [[0] * 4 for _ in range(4)]
定义run()方法,将用户按键盘操作或点击关闭窗口转化为pygame的事件对象,最后在主函数中进行调用。
class Game2048:
def run(self):
while True:
# 遍历当前的事件列表
for event in pygame.event.get():
# 如果事件类型是关闭窗口
if event.type == pygame.QUIT:
# 退出 Pygame
pygame.quit()
return
# 如果事件类型是键盘按键按下,移动游戏棋盘
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_UP:
self.move("up")
elif event.key == pygame.K_DOWN:
self.move("down")
elif event.key == pygame.K_LEFT:
self.move("left")
elif event.key == pygame.K_RIGHT:
self.move("right")
(2)随机生成新的数字方块
使用random模块在随机位置生成数字。
class Game2048:
def add_random_number(self):
# empty_cells是判定出来的空单元格位置
if empty_cells:
i, j = random.choice(empty_cells) # 随机选择一个空单元格
self.board[i][j] = random.choice([2, 4]) # 在选择的空单元格中随机放置一个数字(2或4)
(3)数字方块的合并
class Game2048:
def merge(self, line):
# 标记是否已经合并过
merged = False
# 存储合并后的新行
new_line = []
for i in range(len(line)):
# 如果当前位置不为0
if line[i] != 0:
# 如果新行不为空且新行的最后一个元素未合并
if len(new_line) > 0 and new_line[-1] == line[i] and not merged:
new_line[-1] *= 2 # 将新行的最后一个元素翻倍
merged = True # 标记已经合并过
else:
# 将当前元素添加到新行中
new_line.append(line[i])
# 标记未合并过
merged = False
# 补充0,使新行与原行长度相同
new_line += [0] * (len(line) - len(new_line))
# 返回合并后的新行
return new_line
(4)输赢判定
如果有方块达到了2048或者所有方块布满了游戏界面而无法再移动了,游戏结束。
flag标记在__init__()初始化方法中中被定义,在check_game_over()通过类属性返回给run()函数,实现游戏结束。
class Game2048:
def check_game_over(self):
for i in range(4):
for j in range(4):
# 某个格子数字达到了胜利数字WIN_NUMBER
if self.board[i][j] == WIN_NUMBER:
self.game_over = True
Game2048.flag = True
return
# 判定格子是否满且可以移动
for i in range(4):
for j in range(4):
if self.board[i][j] == 0:
Game2048.flag = False
return
for i in range(3):
for j in range(3):
if self.board[i][j] == self.board[i + 1][j] or self.board[i][j] == self.board[i][j + 1]:
Game2048.flag = False
return
for i in range(3):
if self.board[i][3] == self.board[i + 1][3]:
Game2048.flag = False
return
for j in range(3):
if self.board[3][j] == self.board[3][j + 1]:
Game2048.flag = False
return
# 格子满且无法移动
self.game_over = True
2 加入APA(逃离CPPU)
第二个游戏用的是C++,并调用了图形引擎EGE(Easy Graphics Engine)。EGE是 windows 下的简易绘图库,是一个面向 C/C++ 语言新手的图形库。EGE的安装与使用可以参考
从官网上下下来之后简单改一下配置就可以用了。
EGE非常简单友好,容易上手,而且接口意义直观,程序主要用了EGE的几个函数:
//一、界面操作
initgraph(800,600);//初始化一个800*600的界面
closegraph();//关闭图形界面
//二、图形操作
setfillcolor(BLACK);//设置颜色
bar(1,1,800,600);//在指定位置画矩阵(参数为左上、右下坐标)
circle(1,1,100);//在指定位置画圆(参数为圆形坐标、半径)
//三、文字操作
setfont(40,0,"幼圆");//设置字体大小40,幼圆格式
outtextxy(200, 350,"XXX");//在指定位置输出文本
完整代码放在文末链接里,这边用伪代码简单展示一下程序整体思路
void doing(){
while (1){
//每0.1秒为一帧
Sleep(100);
//移动小红,并重新打印小红的新位置
for (i=1;i<=30;i++)
删除原位置小红;
move(red[i]);
打印新位置小红;
// 当识别到WASD被按下时,改变小蓝方向
if (kbhit()) {
n=getch();
if (n=='d') blue.w=0;
if (n=='a') blue.w=1;
if (n=='s') blue.w=2;
if (n=='w') blue.w=3;
}
// 移动小蓝,重新打印
删除原位置小蓝;
move(blue);
打印新位置小蓝
//逐一检测移动后小蓝有没有撞到小红
for (i=1;i<=30;i++) {
if 如果小蓝撞到了小红 {
hp--; //扣血
if (hp==0)
gameover;
cout<<0;
return;
else
显示扣血提示;
getch();
//撞到的小红要移到其他地方,不然复活后会秒寄
rebuild(red[i])
break;
}
}
if (小蓝达到终点)
congratulations;
cout<<1;
return;
//打印地图其他信息
打印终点;
在左上角打印HP值;
}
}
int main(){
//标记是否通关
freopen("flag.txt","w",stdout);
//初始化地图;
initgraph(800,600);
生成起点终点的位置;
生成小蓝坐标与方向速度;
for (i=1;i<=30;i++) {
生成小红坐标与方向速度;
if (有重合) 重新生成;
}
//游戏运行函数
doing();
//关闭图形界面
closegraph();
}
3 五子棋
游戏同样使用的pygame库,主体模块包括:
- 可视化界面和键盘交互
- 人机交互
- 生成AI落子点
- 输赢判定
(1)可视化界面
定义Checkerboard棋盘类进行棋盘控制与人际交互
class Checkerboard:
# 初始化棋盘界面
def __init__(self, line_points):
self._line_points = line_points
self._checkerboard = [[0] * line_points for _ in range(line_points)]
def _get_checkerboard(self):
return self._checkerboard
checkerboard = property(_get_checkerboard)
# 进行落子
def drop(self, chessman, point):
self._checkerboard[point.Y][point.X] = chessman.Value
# 棋盘绘制
def _draw_checkerboard(screen):
screen.fill(Checkerboard_Color)
# 画棋盘网格线外的边框
pygame.draw.rect(screen, BLACK_COLOR, (Outer_Width, Outer_Width, Border_Length, Border_Length), Border_Width)
# 画网格线
for i in range(Line_Points):
pygame.draw.line(screen, BLACK_COLOR,(Start_Y, Start_Y + SIZE * i), (Start_Y + SIZE * (Line_Points - 1), Start_Y + SIZE * i), 1)
for j in range(Line_Points):
pygame.draw.line(screen, BLACK_COLOR, (Start_X + SIZE * j, Start_X), (Start_X + SIZE * j, Start_X + SIZE * (Line_Points - 1)), 1)
(2)人机交互
# 根据鼠标点击位置,返回游戏区坐标
def _get_clickpoint(click_pos):
pos_x = click_pos[0] - Start_X
pos_y = click_pos[1] - Start_Y
if pos_x < -Inside_Width or pos_y < -Inside_Width:
return None
x = pos_x // SIZE
y = pos_y // SIZE
if pos_x % SIZE > Stone_Radius:
x += 1
if pos_y % SIZE > Stone_Radius:
y += 1
if x >= Line_Points or y >= Line_Points:
return None
return Point(x, y)
(3)生成AI落子点
AI整体是一套贪心的思路,对于每一个落子点,计算在此处落子的价值(将八个方向的价值累加),最终选择价值最高的落子点落子
class AI:
# 初始化
def __init__(self, line_points, chessman):
self._line_points = line_points
self._my = chessman
self._opponent = BLACK_CHESSMAN if chessman == WHITE_CHESSMAN else WHITE_CHESSMAN
self._checkerboard = [[0] * line_points for _ in range(line_points)]
# 判断落子价值
def AI_drop(self):
point = None
score = 0
for i in range(self._line_points):
for j in range(self._line_points):
if self._checkerboard[j][i] == 0:
_score = self._get_point_score(Point(i, j))
if _score > score:
score = _score
point = Point(i, j)
elif _score == score and _score > 0:
r = random.randint(0, 100)
if r % 2 == 0:
point = Point(i, j)
self._checkerboard[point.Y][point.X] = self._my.Value
return point
# 累加八个方向的价值分
def _get_point_score(self, point):
score = 0
for os in offset:
score += self._get_direction_score(point, os[0], os[1])
return score
def _get_direction_score(self, point, x_offset, y_offset):
# 根据该方向下双方连续落子数、有无空格以及两端有无阻挡,判定落子与该点的方向价值分数
(4)输赢判断
在棋盘类型中添加输赢判断函数
def _win(self, point):
cur_value = self._checkerboard[point.Y][point.X]
for os in offset:
if self._get_count_on_direction(point, cur_value, os[0], os[1]):
return True
def _get_count_on_direction(self, point, value, x_offset, y_offset):
count = 1
for step in range(1, 5):
x = point.X + step * x_offset
y = point.Y + step * y_offset
if 0 <= x < self._line_points and 0 <= y < self._line_points and self._checkerboard[y][x] == value:
count += 1
else:
break
for step in range(1, 5):
x = point.X - step * x_offset
y = point.Y - step * y_offset
if 0 <= x < self._line_points and 0 <= y < self._line_points and self._checkerboard[y][x] == value:
count += 1
else:
break
return count >= 5
4 程序封装
4.1 可视化界面
使用Pyside2进行封装,安装可参考
安装后使用插件Qt Designer进行可视化编辑界面,生成ui文件(背景图好像只支持png格式)
将ui文件导入python中:
app = QApplication([])
ui = QUiLoader().load('./b.ui')
ui.show()
app.exec_()
将五个按钮与python函数一一连接:
ui.pushButton.clicked.connect(game1)
ui.pushButton_2.clicked.connect(game2)
ui.pushButton_3.clicked.connect(game3)
ui.pushButton_4.clicked.connect(loadin)
ui.pushButton_5.clicked.connect(get_key)
4.2 秘钥生成
秘钥其实只用了一个最简单的凯撒加密,即将明文中的所有字母按字母表上向后进行偏移K位后,被替换成密文。例如,当K=3时,A将被替换成D,B变成E,Z则变成C。本次加密的K值选取了用户名的长度,确保每一个用户名对应的秘钥是唯一的。
将用户名凯撒加密后,与通关数(0关-A; 1关-B; 2关-C; 3关-D)和K值相拼接,即得到最终秘钥。
举例来说:假设用户“CPPUAPA”通过了2关。“CPPUAPA”的长度为7,则对“CPPUAPA”进行7位凯撒加密,得到“JWWBHWH”;通过2关,代码为“C”;K值为7,对应字母为“H"。最后的秘钥应为”JWWBHWHCG“(字母均默认转换为大写)
cin>>name;
for (int i=0;i<s.length();i++){
if (name[i]>='a' && name[i]<='z'){
name[i] = char(name[i]+32)// 小写转大写
}
key[i]=char((name-'A'+K)%26+'A')// 加K位
}
key = key + chr(grade+'A') + chr(K+'A')
cout<<key;
4.3 备份json文件
对所有玩家的游戏记录和秘钥进行备份,存储于game_record.json中
{"user": "Wielttgest", "game1": 1, "game2": 0, "key": "GSOVDDQOCDBK"}
load按钮用于生成新的json对象,放于文件中。
def loadin():
# 读入文本框内的用户名
user = ui.textEdit.toPlainText()
# 同名检查
with open('game_record.json', 'r') as f:
data = f.readlines()
for d in data:
dict = json.loads(d)
if (dict["user"]==user):
print("已经有同名的人了,请在用户名后加字母后缀~")
return
# 同名检查通过,添加至json中
dict = {"user": user}
with open('game_record.json', 'a') as f:
f.write('\n')
json.dump(dict, f)
print(user, "has load in")
之后每一次游戏结束后,将通关与否的标记自动添加至对应json对象中。如果玩家同一关卡已经有记录,说明已经是第二次玩了,则不做记录。
def add_value(user,name,value
for i in range(len(data)):
d = data[i]
dict = json.loads(d)
if (dict["user"]==user): # 找到该用户
if name not in dict: # 判断是否玩过
dict[name] = value # 添加
elif (name != "key"):
print("你已经玩过一次啦,成绩不算哦")
判断用户通关了几关,有跳关则不做记录。
def get_grade(user):
grade = 0
if ("game1" in data and now["game1"]):
grade = 1
if ("game2" in data and now["game2"]):
grade = 2
if ("game3" in data and now["game3"]):
grade = 3
return grade
Author:张霁阳 熊天诚
标签:招新,point,self,2023APA,range,pygame,讲解,line,def From: https://www.cnblogs.com/rachel0701/p/17760585.html