贪吃蛇-EasyX实现

主要参考了《C/C++趣味编程》第七章代码。但是源代码是C语言实现,然后更改为C++代码面向对象实现。基于源代码,增加了多玩家操作、显示分数、速度随分数增加而增加等操作。

主要类

game类

存储了游戏的初始化函数,无关输入更新函数,有关输入更新函数,显示函数。

#pragma once
#include
#include
#include
#include
#include
#include"Snake.h"
#include"Food.h"
using namespace std;
​
const int BLOCK_SIZE = 20;    //块的大小
const int HEIGHT = 30;        //长度
const int WIDTH = 40;        //宽度
class Snake;
class Food;
class Game
{
private:
    int isFailure;  // 0 为没有失败, 1为失败
    int playnums;    //存储玩家个数
    
public:
    int times;  //记录食物出现的次数
    vector> Blocks;    //背景
​
    Game();    //构造函数
​
    void startup();     // 游戏初始化函数
    void show();    //显示函数
    
    void updateWithoutInput();    //无关输入更新函数
    void updateWithInput();    //有关输入更新函数
    void setIsFailure(int condition) {    // 设置游戏失败函数
        isFailure = condition;
    }
    vector snake;    // 存储了蛇的数量,使用vector容器存储
    class Food* food;        //存储食物类,每次吃了食物后都需要进行更新。
    ~Game();    //析构函数
​
};

 

贪吃蛇-EasyX实现_第1张图片

​
#include "game.h"
Game::Game() {
    vector> v(HEIGHT, vector(WIDTH));
    Blocks = v;
    isFailure = 0;
    playnums = 0;
    times = 0;      // 15帧更新一次
    // 新建窗口
    initgraph(WIDTH * BLOCK_SIZE, HEIGHT * BLOCK_SIZE);
}
​
void Game::startup() {  
​
    // 将蛇放入到类的容器中
    setbkmode(TRANSPARENT);
​
    settextcolor(RGB(255, 0, 0));
    settextstyle(40, 0, _T("宋体"));
    outtextxy(240, 220, _T("请输入玩家数"));  // 询问玩家数量
    char input = _getch();
    while (input > '3' || input < '1') {
        cleardevice();
        outtextxy(240, 220, _T("输入错误,请重新输入"));
        input = _getch();
    }
    this->playnums = input - '0';
    for (int i = 0; i < input - '0'; i++) { 
        snake.push_back(new Snake(i + 1, this));
    }
​
    food = new Food();
    cleardevice();
    setlinecolor(RGB(200, 200, 200));
    BeginBatchDraw();
}
​
void Game::show() { // 先显示底图,再显示蛇,最后显示食物
    cleardevice();
    setfillcolor(RGB(150, 150, 150));
    for (int i = 0; i < HEIGHT; i++) {  // 绘制底图
        for (int j = 0; j < WIDTH; j++) {
            fillrectangle(j * BLOCK_SIZE, i * BLOCK_SIZE, (j + 1) * BLOCK_SIZE, (i + 1) * BLOCK_SIZE);
        }
    }
​
    // 绘制蛇身
    
    for (auto s : snake) {
        for (auto s2 : s->snakePoint) {
            setfillcolor(s2->color);
            fillrectangle(s2->y * BLOCK_SIZE, s2->x * BLOCK_SIZE, (s2->y + 1) * BLOCK_SIZE, (s2->x + 1) * BLOCK_SIZE);
        }
        
    }
​
    //绘制食物
​
    setfillcolor(food->getColor());
    fillrectangle(food->getPointY() * BLOCK_SIZE, food->getPointX() * BLOCK_SIZE, (food->getPointY() + 1) * BLOCK_SIZE, (food->getPointX() + 1) * BLOCK_SIZE);
​
    //判断失败输出
    if (isFailure) {
        setbkmode(TRANSPARENT);
        settextcolor(RGB(255, 0, 0));
        settextstyle(80, 0, _T("宋体"));
        outtextxy(240, 220, _T("游戏失败"));
    }
​
    //输出各自分数
    int temp = WIDTH / 4 * BLOCK_SIZE;
    for (auto playnum : this->snake) {
        TCHAR s[20];
        _stprintf_s(s, _T("%d"), playnum->getScore());
        setbkmode(TRANSPARENT);
        settextstyle(50, 0, _T("Times"));
        settextcolor(RGB(50, 50, 50));
        outtextxy(temp, 200, s);
        temp += WIDTH / 4 * BLOCK_SIZE;
    }
​
    //test 测试速度  
    TCHAR s[20];
    _stprintf_s(s, _T("%.1f"), min((times * 0.5), 13));
    setbkmode(TRANSPARENT);
    settextstyle(50, 0, _T("Times"));
    settextcolor(RGB(50, 50, 50));
    outtextxy(200, 300, s);
    FlushBatchDraw();
}
​
void Game::updateWithInput() {
    if (_kbhit() && isFailure == 0) {
        char input = _getch();
        for (int i = 0; i < this->playnums; i++) {
            if (i == 0) {   // 第一个玩家的按键操作
                if ((input == 'A' && this->snake[i]->moveDirection != 'D') || (input == 'S' && this->snake[i]->moveDirection != 'W') || (input == 'D' && this->snake[i]->moveDirection != 'A') || (input == 'W' && this->snake[i]->moveDirection != 'S')) {
                    snake[i]->moveDirection = input;
                    snake[i]->moveSnake();
                }
            }
            else if (i == 1) {  // 第二个玩家的按键操作 使用方向键操作, 对应的ascii码为 <- 37,   ^ 38, --> 39,下  40
                if ((input == 'F' && this->snake[i]->moveDirection != 'H') || (input == 'G' && this->snake[i]->moveDirection != 'T') || (input == 'H' && this->snake[i]->moveDirection != 'F') || (input == 'T' && this->snake[i]->moveDirection != 'G')) {
                    snake[i]->moveDirection = input;
                }
            }
            else if (i == 2) {  // 第三个玩家的按键操作
                if ((input == 'J' && this->snake[i]->moveDirection != 'L') || (input == 'K' && this->snake[i]->moveDirection != 'I') || (input == 'L' && this->snake[i]->moveDirection != 'J') || (input == 'I' && this->snake[i]->moveDirection != 'K')) {
                    snake[i]->moveDirection = input;
                }
            }
        }
    }
}
​
void Game::updateWithoutInput() {
    if (isFailure) {
        return;
    }
    static int waitIndex = 1;
    waitIndex++;
    
    if (waitIndex >= 15 - min((times * 0.5), 13)) { // 取15 - 2 的值
        for (auto i : this->snake) {
            i->moveSnake();
            waitIndex = 1;
        }   
​
    }
}
​
Game::~Game() {
​
}


​

构造函数初始化了成员变量,并且新建了一个窗口。

startup函数首先确定玩家个数,将确定的玩家个数先生成存储进vector中,再new一个food类,然后清空窗口,开始画图。这里注意Begin BatchDraw()用于连续图像绘制,防止出现闪烁,主要功能可自行百度。

show()函数首先显示底图,再显示蛇,然后显示食物,并且还会显示各自玩家的分数。

updateWithInput() 函数进行输入更新,需要注意的是三个玩家的按键时不一样的,需要进行各自判断,并且还需注意输入的方向不能和已有的方向相反,否则会立即导致游戏失败。

updateWithoutinput() 函数主要进行无关输入更新。使用一个静态变量来存储访问次数,初始条件下访问15次才会进行移动,而随之分数的增加,达到访问条件的次数会不断减少,最小是访问两次就进行移动。

#pragma once
#include
class Point
{
public:
    int x, y;
    COLORREF color;
    int convertColor;
    Point() {
    }
    Point(int x, int y, COLORREF color) {
        this->x = x;
        this->y = y;
        this->color = color;
    }
    COLORREF getColor() {
        return this->color;
    }
    int getPointX() {
        return this->x;
    }
    int getPointY() {
        return this->y;
    }
};
​
​

简单的一个点类。仅仅需要注意x为高,y为高。

#pragma once
#include"Point.h"
#include"Game.h"
#include        // 使用vector存储蛇身
using namespace std;
class Snake:Point
{
private:
    int score;
    
    int num;            //第几条蛇的标记
    
public:
    class Game* game;
    char moveDirection;
    vector snakePoint;
    Snake(int num,Game* game);
    int getScore() {
        return this->score;
    }
    void moveSnake();
    bool isCollision(int x, int y); //遍历全部蛇的蛇身,查看是否发生碰撞,如果发生碰撞,返回true,否则返回false;
    void updateColor();
        
};
#include "Snake.h"
Snake::Snake(int num,Game* game) {
    this->num = num;
    this->moveDirection = 'S';
    Point* p = new Point();
    p->x = HEIGHT / 2 ;
    p->y = WIDTH / 4 * num; // 玩家分布
    snakePoint.push_back(p);
    score = 0;
    p->convertColor = score + 1;
    p->color = HSVtoRGB(p->convertColor * 10, 0.9, 1);
    this->game = game;
}
​
void Snake::moveSnake() {
    // 需要更新颜色,蛇的每个点的颜色已经固定了
    updateColor();
    //存储下一个位置 -- 首先读取最后一个点,也就是蛇头
    Point* snakeHead = snakePoint.back();           // y --> height
    if (moveDirection == 'W' || moveDirection == 'I' || moveDirection == 'T') {
        snakePoint.push_back(new Point(snakeHead->x - 1, snakeHead->y, snakeHead->color));
    }
    else if (moveDirection == 'S' || moveDirection == 'K' || moveDirection == 'G') {
        snakePoint.push_back(new Point(snakeHead->x + 1, snakeHead->y, snakeHead->color));
    }
    else if (moveDirection == 'A' || moveDirection == 'J' || moveDirection == 'F') {
        snakePoint.push_back(new Point(snakeHead->x, snakeHead->y - 1, snakeHead->color));
    }
    else if (moveDirection == 'D' || moveDirection == 'L' || moveDirection == 'H') {
        snakePoint.push_back(new Point(snakeHead->x, snakeHead->y + 1, snakeHead->color));
    }
​
    // 判断移动后游戏失败
    snakeHead = snakePoint.back();
    if (snakeHead->x >= HEIGHT || snakeHead->x <= 0 || snakeHead->y >= WIDTH || snakeHead->y <= 0 || isCollision(snakeHead->x, snakeHead->y)) {
        this->game->setIsFailure(1);
        return;
    }
​
    //判断蛇头和食物是否在同一个位置
    
    if (game->food->getPointX() == snakeHead->x && game->food->getPointY() == snakeHead->y) {
        score++;
        delete game->food;  // 删除旧食物
        game->food = new Food();        //更新新食物
​
        this->game->times++;
    }
    else {
        snakePoint.erase(snakePoint.begin());
    }
}
​
bool Snake::isCollision(int x, int y) {
    for (auto s : this->game->snake) {
        for (int i = 0; i < s->snakePoint.size() - 1; i++) {
            if (x == s->snakePoint[i]->x && y == s->snakePoint[i]->y) {
                return true;
            }
        }
    }
    
    return false;
}
​
void Snake::updateColor() {
    for (auto i : snakePoint) {
        i->convertColor++;
        color = HSVtoRGB(convertColor * 10, 0.9, 1);
        i->color = color;
    }
}

首先有个game类成员指针,方便调用game中的成员变量,随后有一个方向变量,存储当前的方向。snakePoint 为存储蛇所在点的指针向量. moveSnake()函数实现蛇的移动。isCollision()判断是否发生碰撞

#pragma once
#include"Point.h"
#include"Game.h"
class Food:Point
{
public:
    Food();
    COLORREF getColor() {
        return this->color;
    }
    int getPointX() {
        return this->x;
    }
    int getPointY() {
        return this->y;
    }
};
#include "Food.h"
Food::Food() {  // y--> width, x--> height
    y = rand() % (WIDTH - 5) + 2;
    x = rand() % (HEIGHT - 5) + 2;
    color = GREEN;
}

food类,主要注意随机位置不要超过最大范围和最小范围。

最后的主函数

#include
#include
#include"Game.h"
using namespace std;
//这里强制规定 x为横坐标 对应为HEIGHT,y为纵坐标-》WIDTH,
int main() {
    srand(time(0)); //初始化随机种子
    Game g;
    g.startup();
    while (1) {
        g.show();
        g.updateWithInput();
        g.updateWithoutInput();
    }
    _getch();
    return 0;
}

运行图

贪吃蛇-EasyX实现_第2张图片

 贪吃蛇-EasyX实现_第3张图片

 

你可能感兴趣的:(c++,开发语言)