文章目录
- 常量
- Snake类
- GameController类
- GUI显示
- 游戏简图
为了能够最简单地完成程序,所以没有用类的继承等知识。感兴趣的朋友可以改写一下。
常量
const int FILE_SIZE = 30; //地图方格大小
const int FPS = 5000 / 33; //游戏运行帧率
enum Item{empty, wall, food, snake}; //方格的类型
//地图大小
const int mapWidth = 20;
const int mapHeight = 20;
Snake类
为了存储一条蛇, 我们需要的变量有:
- 蛇的头坐标
- 蛇的尾巴, 用一个链表存储
- 蛇的移动方向
下面是Snake类的定义:
class Snake
{
public:Snake(QObject* parent, iItem ** _map, int x, int y);void setDirection(const int x, const int y);void move();void grow();bool eatSelf();int length()const{return tail.size();}QPoint getPos()const{return head;}private:bool isMoving();QPoint head;QList<QPoint> tail;int moveX = 0;int moveY = 0;iItem **map;
};
贪吃蛇中的唯一难点, 就是蛇移动的逻辑了. 蛇移动不必更新身体每一个点的位置. 相反, 只要移动头, 并把头原来的位置加入身子, 并且去掉身体的最后就可以了.
函数的实现:
Snake::Snake(QObject *parent, iItem **_map, int x, int y) : Item(parent), map(_map)
{head = QPoint(x, y);map[x][y] = snake;
}void Snake::setDirection(const int x, const int y)
{moveX = x;moveY = y;
}void Snake::move()
{if(!isMoving()) return;QPoint newHead = head;//更新head的位置newHead.setX((newHead.x() + moveX) % mapWidth);newHead.setY((newHead.y() + moveY) % mapHeight);if(!tail.empty()){//如果有尾巴的话, 将原来head作为tail的头tail.push_front(head);//剁掉尾巴的最后QPoint t = tail.back();map[t.x()][t.y()] = empty;tail.pop_back();}else{//没有尾巴, 则把原来的地图位置置空map[head.x()][head.y()] = empty;}head = newHead; //更新headmap[head.x()][head.y()] = snake; //更新地图
}bool Snake::isMoving()
{return moveX != 0 || moveY != 0;
}bool Snake::eatSelf()
{// 如果tail链表中包含与head坐标一样的点, 说明蛇头撞到身子了// contains是QList类内置的函数return tail.contains(head);
}void Snake::grow() //变长
{QPoint t = head;//尾巴变长的方向和移动方向相反t.setX((t.x() - moveX) % mapWidth);t.setY((t.y() - moveY) % mapHeight);tail.append(t);map[t.x()][t.y()] = snake;
}
GameController类
class GameController : public QObject
{Q_OBJECT
public:Item** map; GameController(QObject *parent);~GameController();protected://这个函数由于把QMainWindow收到的键盘事件在handlerKeyPressed里处理virtual bool eventFilter(QObject *watched, QEvent *event) override;void handleKeyPressed(QKeyEvent* event);private:QTimer timer;bool paused = false; //游戏暂停Snake *snake;QPoint foodPos;void checkCollision();bool snakeEatFood();void snakeEatSelf();void snakeHitWall();void addFood();void initMap(); //初始化地图signals:void updateView(); //通知更新显示void over(); //通知游戏终止public slots:void updateGame(); //更新游戏状态void gameover(); //游戏结束void stop(); //游戏暂停void resume(); //游戏恢复进行void newGame(); //再开一局
};
void GameController::initMap()
{for(int i = 0; i < mapWidth; ++i){for(int j = 0; j < mapHeight; ++j){map[i][j] = empty;}}
}GameController::GameController(QObject *parent) : QObject(parent)
{//创建动态数组map = new Item*[mapWidth];for(int i = 0; i < mapWidth; ++i){map[i] = new iItem[mapHeight];}initMap(); //初始化地图//连接信号connect(&timer, &QTimer::timeout, this, &GameController::updateGame);timer.start(FPS); //开始计时//蛇初始化到地图中央snake = new Snake(this, map, mapWidth/2, mapHeight/2);addFood(); //生成食物
}GameController::~GameController()
{for(int i = 0; i < mapWidth; ++i){delete []map[i];}delete []map;delete snake;
}bool GameController::eventFilter(QObject *watched, QEvent *event)
{if (event->type() == QEvent::KeyPress) {handleKeyPressed((QKeyEvent *)event); //自定义的按键处理函数return true; //返回已处理} else {return QObject::eventFilter(watched, event); //不处理}
}void GameController::handleKeyPressed(QKeyEvent *event)
{if(paused){ if(event->key() == Qt::Key_Space){resume();}return;}//设置方向if(event->key() == Qt::Key_Up){snake->setDirection(0, -1);}else if(event->key() == Qt::Key_Down){snake->setDirection(0, 1);}else if(event->key() == Qt::Key_Left){snake->setDirection(-1, 0);}else if(event->key() == Qt::Key_Right){snake->setDirection(1, 0);}else if(event->key() == Qt::Key_Space){stop();}
}void GameController::updateGame()
{snake->move(); //蛇移动checkCollision(); //检查碰撞emit updateView(); //更新显示
}void GameController::checkCollision()
{if(snake->eatSelf()){ // head撞到身体gameover();}if(snakeEatFood()){ //吃食物addFood(); //生成下一个食物snake->grow(); //蛇生长}
}void GameController::addFood()
{int n, x, y;do{ //随机在不是蛇的地方生成食物n = rand() % (mapWidth * mapHeight);y = n / mapWidth;x = n % mapWidth;}while(map[x][y] == snake);map[x][y] = food; //更新地图foodPos = QPoint(x, y); //存储食物的位置
}void GameController::gameover()
{stop(); //游戏暂停emit over(); //更新UI
}void GameController::stop()
{timer.stop();paused = true;
}void GameController::resume()
{timer.start(FPS);paused = false;
}void GameController::newGame()
{delete snake;initMap();snake = new Snake(this, map, mapWidth/2, mapHeight/2);addFood();resume();
}bool GameController::snakeEatFood()
{return snake->getPos() == foodPos;
}
GUI显示
class MainWindow : public QMainWindow
{Q_OBJECTpublic:MainWindow(QWidget *parent = nullptr);~MainWindow();protected:virtual void paintEvent(QPaintEvent* event); //绘制地图private:GameController *game;public slots:void showMessageBox(); //死亡时显示的提示框void updateView(); //更新游戏显示
};
MainWindow::MainWindow(QWidget *parent): QMainWindow(parent)
{setFixedSize(FILE_SIZE * mapWidth, FILE_SIZE * mapHeight); //设置窗口大小game = new GameController(this); //新开始游戏this->installEventFilter(game); //对收到的事件不接收,让game接收//信号连接connect(game, &GameController::updateView, this, &MainWindow::updateView);connect(game, &GameController::over, this, &MainWindow::showMessageBox);
}MainWindow::~MainWindow()
{delete game;
}void MainWindow::paintEvent(QPaintEvent *event)
{QPainter painter(this);//画背景painter.fillRect(0, 0, mapWidth * FILE_SIZE, mapHeight * FILE_SIZE, Qt::gray);//画方格内的物体//蛇,食物,墙分别用不同颜色表示for(int i = 0; i < mapWidth; ++i){for(int j = 0; j < mapHeight; ++j){switch (game->map[i][j]) {case empty:break;case food:painter.fillRect(i * FILE_SIZE, j * FILE_SIZE, FILE_SIZE, FILE_SIZE, Qt::red);break;case snake:painter.fillRect(i * FILE_SIZE, j * FILE_SIZE, FILE_SIZE, FILE_SIZE, Qt::yellow);break;case wall:break;}}}
}void MainWindow::showMessageBox()
{if (QMessageBox::Yes == QMessageBox::information(NULL,tr("Game Over"), tr("Again?"),QMessageBox::Yes | QMessageBox::No,QMessageBox::Yes)) {game->newGame(); //如果选了Yes则重新开始}else{exit(0); //否则推出}
}void MainWindow::updateView()
{update(); //更新, 会自动调用paintEvent函数
}
游戏简图