目录
实验内容
编程语言与开发环境
游戏的逻辑设计
实验核心
一、实验内容
1、实现贪吃蛇游戏基本功能,屏幕上随机出现一个“食物”,称为豆子,上下左右控制“蛇”的移动,吃到“豆子”以后“蛇”的身体加长一点,得分增加,“蛇”碰到边界或蛇头与蛇身相撞,蛇死亡,游戏结束。为游戏设计初始欢迎界面,游戏界面,游戏结束界面。
2、进行交互界面的设计,要有开始键、暂停键和停止退出的选项。对蛇吃到豆子进行分值计算,可以设置游戏速度,游戏音乐等拓展元素。
二、编程语言与开发环境
本次开发采用C++与QT结合的形式。
三、游戏的逻辑设计
游戏预设:我想做一个游戏界面分为两个部分,在喜洋洋与灰太狼动画片的背景下左边的话是就是人物选择可以选择小灰灰、美羊羊、喜洋洋、懒洋洋四种人物选择,右边是地方选择,可以选择青青草原和地下古古怪界世界两种模式,通过吃香草饼干形成一个类似于“蛇身”一样的游戏模式,直至羊碰到边界或羊与香草饼干相撞,羊死亡,游戏结束,同时增加背景音乐以及游戏速度的设置。
准备工作:(定义几个重要变量)
1)定义一个headImage用来存放头位置;
2)定义一个bodyImage用来存放身体的位置;
3)定义一个foodImage用来存放香草饼干位置;
4)定义一个direction来存放蛇前进的方向;
5)定义一个score用来计分;
6)定义一个speed用来设置计时器的时间间隔,初始默认值是300ms;
7)定义一个isAI用来判断是否是AI人物
详细要点:
首先做的是游戏人物的生成,我先调出背景音乐,随机选取头的行与列,生成头位置,再把头位置写入坐标列表中,然后随机选择头前进的方向,最后调用香草饼干的方法。在生成香草饼干的时候,我采用随机选取豆子的行与列,从而能够随机生成香草饼干的位置,值得考虑一个点的是若随机生成的的位置不在头和身体的坐标列表中,则返回香草饼干的位置,否则重新生成。当在古古怪界世界的时候是AI蛇的世界,我采用bfs算法寻找最短路径放到的栈中。
四、实验核心:
①头和身体的移动,由direction变量为1,2,3,4分别代表上下左右,采用了入队和出队的核心思想,当小羊与香草饼干所形成的“蛇”,蛇头为队尾,蛇尾为队头,当蛇每前进一格,则所代表的是蛇头,增未进食情况下,蛇头+1,蛇尾-1,此时的+1,-1是通过格子的属性发生变化多得到的。
②对game win中keyPressEvent方法的重写
1)按下 ⬆️ 表示向上,direction = 0;
2)按下 ⬇️ 表示向下,direction = 1;
3)按下 ⬅️ 表示向左,direction = 2;
4)按下 ➡️ 表示向右,direction = 3;
③bfs算法核心
BFS算法从问题的初始状态(起点)出发,根据状态转换规则(图结构中的边),遍历所有可能的状态(其他节点),直到找到终结状态(终点),因此BFS算法的复杂度和状态集合的总数密切相关。
⑴如果是树的话,如图所示
Bfs就是一层一层向下寻找,从根节点A开始依次遍历为A、B、C、D、E、F、G、H、I
⑵如果是图的话,如图所示
那基本操作就是将a压入;
a出栈,并把a的邻接点压入(c,b);
b出栈,并把b的邻接点压入(d);
d出栈,并把d的邻接点压入(e,f);
f出栈,无点可压(从路径上看此刻发生了退回去的现象)e出栈,无点可压;
c出栈;
得到结果abdfec;
AI蛇的流程图
游戏人物移动的代码:
void gamewin::move(){
Grid *front = grid[hh -> x + dx[direction]][hh -> y + dy[direction]];
// 注意:这里可以跟着尾巴走
if (front -> type == BORDER || (front -> type == SNAKE && front != tt) ) {
gameOver();
return;
}
if (front -> type == FOOD) {
generateFood();
speed = max(50, speed - ((++score + 1) % 2) * 10);
// ui -> Score -> setText(QString::number(score));
ui -> Speed -> setText(QString::number(speed));
timer.start(speed);
}
else {
tt -> type = NORMAL;
tt -> label -> setStyleSheet("");
snake.removeFirst();
tt = snake.at(0);
}
hh -> label -> setStyleSheet(bodyImage);
hh = front;
hh -> type = SNAKE;
hh -> label -> setStyleSheet(headImage);
snake.append(hh);
isChange = false;
}
游戏人物进食代码:
void gamewin::generateFood() {
srand((unsigned)time(0));
do {
foodx = rand() % (MAX_X - 1) + 1;
foody = rand() % (MAX_Y - 1) + 1;
} while (grid[foodx][foody] -> type == SNAKE || grid[foodx][foody] -> type == FOOD);
grid[foodx][foody] -> type = FOOD;
grid[foodx][foody] -> label -> setStyleSheet(foodImage);
}
按键检测:
void gamewin::keyPressEvent(QKeyEvent *event) {
auto KEY = event -> key();
if (isOver && KEY != Qt::Key_Q) return;
switch (KEY) {
case Qt::Key_Up:
if (direction != 1 && direction != 3 && timer.isActive() && !isAI) {
if (isChange == true) move();
else isChange = true;
direction = 1;
}
break;
case Qt::Key_Down:
if (direction != 1 && direction != 3 && timer.isActive() && !isAI) {
if (isChange == true) move();
else isChange = true;
direction = 3;
}
break;
case Qt::Key_Right:
if (direction != 0 && direction != 2 && timer.isActive() && !isAI) {
if (isChange == true) move();
else isChange = true;
direction = 2;
}
break;
case Qt::Key_Left:
if (direction != 0 && direction != 2 && timer.isActive() && !isAI) {
if (isChange == true) move();
else isChange = true;
direction = 0;
}
break;
case Qt::Key_Space:
on_BtnStop_clicked();
break;
case Qt::Key_A:
on_BtnAI_clicked();
break;
case Qt::Key_Q:
on_BtnBack_clicked();
break;
default:
break;
}
}
Bfs算法的代码:
bool gamewin::bfs(Grid *st, Grid *ed) {
shortestPath.clear();
//建立一个保存前一个坐标的地图数组
struct Node {
int prevx = 0;//保存前一个的坐标
int prevy = 0;
int dist = 0;//权值,表示路径长度
bool visit = false;
};
Node Nodes[MAX_X + 1][MAX_Y + 1];
pair<int, int> q[300];
int qh = 0, qt = 0;
q[qt] = {st -> x, st -> y};
Nodes[st -> x][st -> y].visit = true;
Nodes[st -> x][st -> y].prevx = st -> x;
Nodes[st -> x][st -> y].prevy = st -> y;
srand((unsigned)time(0)); // 随机数,防止死循环=================================================
int Rand = rand() % 4;
while (qh <= qt) {
auto cur = q[qh++];
bool findTail = false;
for (int i = 0; i < 4; ++i) {
int j = (Rand + i) % 4;
int nx = cur.fi + dx[j];
int ny = cur.se + dy[j];
if (nx > 0 && nx < MAX_X && ny > 0 && ny < MAX_Y && (grid[nx][ny] -> type == NORMAL || grid[nx][ny] == ed) && !Nodes[nx][ny].visit) {
Nodes[nx][ny].visit = true;
Nodes[nx][ny].dist = Nodes[cur.fi][cur.se].dist + 1;
Nodes[nx][ny].prevx = cur.fi;
Nodes[nx][ny].prevy = cur.se;
if (nx == ed -> x && ny == ed -> y) {
findTail = true;
break;
}
q[++qt] = {nx, ny};
}
}
//若到达目的地,则回溯将路径储存起来,路径不包括蛇头和蛇尾
if (findTail) {
int x = Nodes[ed -> x][ed -> y].prevx;
int y = Nodes[ed -> x][ed -> y].prevy;
//qDebug() << x << ' ' << y << ' ' << st -> x << ' ' << st -> y;
while (x != st -> x || y != st -> y) {
shortestPath.push({x, y});
int tx = x;
x = Nodes[x][y].prevx;
y = Nodes[tx][y].prevy;
}
return true;
}
}
return false;
}
void gamewin::on_BtnAI_clicked()
{
if (isOver || !timer.isActive()) return;
if (!isAI) {
disconnect(&timer, &QTimer::timeout, this, nullptr);
connect(&timer, &QTimer::timeout, this, [=]() {
aiSnake();
});
ui -> BtnAI -> setStyleSheet("border-radius: 10px; border-width: 1; border-style: outset; border-image: url(:/res/AI2.png);");
isAI = true;
}
else {
disconnect(&timer, &QTimer::timeout, this, nullptr);
connect(&timer, &QTimer::timeout, this, [=]() {
move();
});
ui -> BtnAI -> setStyleSheet("border-radius: 10px; border-width: 1; border-style: outset; border-image: url(:/res/AI.png);");
isAI = false;
}
}