主要参考了《C/C++趣味编程》第七章代码。但是源代码是C语言实现,然后更改为C++代码面向对象实现。基于源代码,增加了多玩家操作、显示分数、速度随分数增加而增加等操作。
存储了游戏的初始化函数,无关输入更新函数,有关输入更新函数,显示函数。
#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(); //析构函数
};
#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; }
运行图