这是我作为小白的第一篇博客。由于疫情宅在家里没事干,我重写了大二上的贪吃蛇ai,实现了吃满屏幕。效果图如下,即使是很大的地图(41*41=1681)也能近似吃满。
现在看来仍有许多不足之处。话不多说,直接开始。
软件用的是qt,下面我假设你已经熟悉了qt的特性,比如信号和槽机制和重写事件等。
游戏基本组件类
基本组件有地图类、蛇类、食物类这3个类。我把它们都放到component.h中。
由于地图、食物和蛇都是一个个的小方块,我用了一个unit_block类存位置。
地图类mapOfGame的设置随机地形:随机墙的数量和每个墙的方块数,先随机找一个空格(0-420,0-420)作为每个墙的初始位置,然后在这个空格附近随机找一个方向生成一个新的墙方块,再在这个新的墙方块附近随机找一个方向生成新的墙方块……这样生成的地形比较随机,但有一个问题,就是会出现一些“不好”的地形,比如空格附近4个方向都是墙或者空格附近3个方向是墙,这些位置蛇肯定走不到,所以我用了optimization把这些地方排除掉了。即使是这样,也会有一些地形不太合理,比如这种情况:
由于随机地形不是我关注的重点,这个小bug就留给读者来解决吧。
enum direction{
UP,DOWN,LEFT,RIGHT}; //蛇运动的方向
enum snakeState{
NORMAL,REVERSE};//蛇状态:正向、反向
struct unit_block{
//小正方形,边长为10
int x;
int y;
unit_block(){
}
unit_block(int m,int n):x(m),y(n){
}
bool operator==(const unit_block &obj)const{
if(x==obj.x&&y==obj.y) return true;
else return false;
}
};
class mapOfGame{
private:
QList<unit_block> wall;
private:
unit_block setWallBlocks(int x,int y);//用于随机生成墙的方块(上下左右随机生成)
void optimization();//地图优化
public:
mapOfGame(){
}
QList<unit_block> getWall(){
return wall;}
void setBasicMap(); //设置基本的四周的墙,横坐标0~420,纵坐标0~420
void setRandomMap();//随机地图
void setAiMap();
void setAiRandomMap();
};
class snake{
public:
direction dir;
QList <unit_block> body;
snakeState sState=NORMAL;
private:
void setRandomDir();//设置随机方向
void setRandomHeadPos(mapOfGame map);//设置随机蛇头位置
void setRandomHeadPos(snake sk,mapOfGame map);
bool snakeHeadConflict(int x,int y,mapOfGame map);//判断蛇头是否与地图冲突
public:
snake(){
}
~snake(){
body.clear();}
void initializeSnake(mapOfGame map); //初始化蛇需要显式调用该函数
void initializeSnake(snake sk,mapOfGame map);//初始化双人模式中的第二条蛇
bool collision(mapOfGame map); //判断蛇头是否撞地图和撞自己
bool collision(snake sk); //判断蛇头是否撞其他的蛇
};
class food{
public:
unit_block position;
private:
bool foodConflict(snake sk,mapOfGame map);//判断食物坐标是否和地图、蛇冲突
public:
food(){
}
void initializeFood(snake sk,mapOfGame map); //需要显式调用该函数以初始化食物,设置食物坐标与地图和蛇都不冲突,单人模式
void initializeFood(snake sk,snake sk2,mapOfGame map); //双人模式
void initializeFood(snake sk,mapOfGame map,food f);//单人模式设置特殊食物
};
snake类中的有一个sState,这是我实现的特殊食物的功能:正常食物吃了就只能加分、蛇变长的作用,特殊食物吃了后不仅能加分、蛇变长,还会给蛇持续一段时间的“反向”效果,这段时间内按下方向键蛇会反着方向跑,比如按“上”键,蛇往下跑。蛇最开始长度为1,方向随机,单人模式时需要地图来初始化(不然初始化蛇头可能在墙内),双人模式时还需要另一条蛇来初始化。通过collision可以判断游戏是否结束。
food类需要地图、蛇来初始化。
下面是"component.cpp"的代码。
#include "compoments.h"
//****************mapOfGame****************
void mapOfGame::setBasicMap(){
wall.clear();
for(int i=0;i<=420;i=i+10){
unit_block w(i,0);
wall.append(w);
}
for(int i=0;i<=420;i=i+10){
unit_block w(i,420);
wall.append(w);
}
for(int i=0;i<=420;i=i+10){
unit_block w(0,i);
wall.append(w);
}
for(int i=0;i<=420;i=i+10){
unit_block w(420,i);
wall.append(w);
}
}
void mapOfGame::setRandomMap(){
wall.clear();
setBasicMap();
int tmp=QRandomGenerator::global()->bounded(5)+20;//生成墙的数量
for(int i=0;i<tmp;++i){
int numOfBlocks=QRandomGenerator::global()->bounded(5)+10;
unit_block u,tmp;
u.x=QRandomGenerator::global()->bounded(41)*10+10;
u.y=QRandomGenerator::global()->bounded(41)*10+10;
wall.push_back(u);
for(int j=0;j<numOfBlocks;++j){
tmp=setWallBlocks(u.x,u.y);
u=tmp;
wall.push_back(tmp);
}
}
for(int i=0;i<10;++i)
optimization();
}
void mapOfGame::setAiMap(){
wall.clear();
setBasicMap();
for(int i=10;i<=150;i=i+10){
//i<=70
for(int j=i;j<=420-i;j=j+10){
unit_block u(j,i);
wall.append(u);
}
for(int j=i;j<=420-i;j=j+10){
unit_block u(j,420-i);
wall.append(u);
}
for(int j=i;j<=420-i;j=j+10){
unit_block u(i,j);
wall.append(u);
}
for(int j=i;j<=420-i;j=j+10){
unit_block u(420-i,j);
wall.append(u);
}
}
}
void mapOfGame::setAiRandomMap(){
wall.clear();
setAiMap();
int tmp=QRandomGenerator::global()->bounded(5)+10;//生成墙的数量
for(int i=0;i<tmp;++i){
int numOfBlocks=QRandomGenerator::global()->bounded(5)+8;
unit_block u,tmp;
u.x=QRandomGenerator::global()->bounded(28)*10+70;
u.y=QRandomGenerator::global()->bounded(28)*10+70;
wall.push_back(u);
for(int j=0;j<numOfBlocks;++j){
tmp=setWallBlocks(u.x,u.y);
u=tmp;
wall.push_back(tmp);
}
}
for(int i=0;i<10;++i)
optimization();
}
unit_block mapOfGame::setWallBlocks(int x, int y){
int tmp=QRandomGenerator::global()->bounded(4);
unit_block u;
switch (tmp) {
case 0:u.x=x; u.y=y+10;return u;
case 1:u.x=x; u.y=y-10;return u;
case 2:u.x=x+10; u.y=y;return u;
default:u.x=x-10; u.y=y;return u;
}
}
void mapOfGame::optimization(){
for(int i=10;i<420;i=i+10){
for(int j=10;j<420;j=j+10){
unit_block left(i-10,j),right(i+10,j),up(i,j-10),down(i,j+10),it(i,j);
if(wall.contains(left)&&wall.contains(right)&&wall.contains(up)&&!wall.contains(it)){
//将三面都是墙而本身不是墙的方块设置为墙
wall.append(it);
}
else if(wall.contains(left)&&wall.contains(right)&&wall.contains(down)&&!wall.contains(it)){
wall.append(it);
}
else if(wall.contains(left)&&wall.contains(up)&&wall.contains(down)&&!wall.contains(it)){
wall.append(it);
}
else if(wall.contains(right)&&wall.contains(up)&&wall.contains(down)&&!wall.contains(it)){
wall.append(it);
}
else if(wall.contains(left)&&wall.contains(right)&&wall.contains(up)&&wall.contains(down)&&!wall.contains(it)){
//将四面都是墙而本身不是墙的方块设置为墙
wall.append(it);
}
}
}
}
//****************snake****************
void snake::initializeSnake(mapOfGame map){
body.clear();
setRandomDir();
setRandomHeadPos(map);
}
void snake::initializeSnake(snake sk, mapOfGame map){
body.clear();
setRandomDir();
setRandomHeadPos(sk,map);
}
void snake::setRandomDir(){
int tmp=QRandomGenerator::global()->bounded(4);//生成随机方向
switch (tmp) {
case 0:dir=UP;break;
case 1:dir=DOWN;break;
case 2:dir=LEFT;break;
case 3:dir=RIGHT;break;
}
}
void snake::setRandomHeadPos(mapOfGame map){
int x,y;
do{
x=QRandomGenerator::global()->bounded(41)*10+10;
y=QRandomGenerator::global()->bounded(41)*10+10;
}while(snakeHeadConflict(x,y,map));
unit_block head(x,y);
body.append(head);
}
void snake::setRandomHeadPos(snake sk, mapOfGame map){
int x,y;
do{
x=QRandomGenerator::global()->bounded(41)*10+10;
y=QRandomGenerator::global()->bounded(41)*10+10;
}while(snakeHeadConflict(x,y,map)||(x==sk.body.front().x&&y==sk.body.front().y));
unit_block head(x,y);
body.append(head);
}
bool snake::snakeHeadConflict(int x,int y,mapOfGame map){
for(auto i:map.getWall()){
if(i.x==x&&i.y==y)return true;
}
return false;
}
bool snake::collision(mapOfGame map){
if(snakeHeadConflict(body.front().x,body.front().y,map))
return true;
else if(body.length()>=2){
QList<unit_block>::iterator i=body.begin();
++i;
for(;i!=body.end();++i){
if(i->x==body.begin()->x&&i->y==body.begin()->y)return true;
}
return false;
}
else return false;
}
bool snake::collision(snake sk){
QList<unit_block>::iterator i=sk.body.begin();
for(;i!=sk.body.end();++i){
if(i->x==body.begin()->x&&i->y==body.begin()->y)
{
return true; }
}
return false;
}
//****************food****************
void food::initializeFood(snake sk, mapOfGame map){
position.x=QRandomGenerator::global()->bounded(41)*10+10;
position.y=QRandomGenerator::global()->bounded(41)*10+10;
if(foodConflict(sk,map)){
initializeFood(sk,map);
}
}
void food::initializeFood(snake sk, snake sk2, mapOfGame map){
position.x=QRandomGenerator::global()->bounded(41)*10+10;
position.y=QRandomGenerator::global()->bounded(41)*10+10;
if(foodConflict(sk,map)||foodConflict(sk2,map)){
initializeFood(sk,map);
}
}
void food::initializeFood(snake sk, mapOfGame map, food f){
position.x=QRandomGenerator::global()->bounded(41)*10+10;
position.y=QRandomGenerator::global()->bounded(41)*10+10;
if(foodConflict(sk,map)||(position.x==f.position.x&&position.y==f.position.y)){
initializeFood(sk,map,f);
}
}
bool food::foodConflict(snake sk, mapOfGame map){
for(auto i:sk.body){
if(i.x==position.x&&i.y==position.y)return true;
}
for(auto i:map.getWall()){
if(i.x==position.x&&i.y==position.y)return true;
}
return false;
}