C++贪吃蛇游戏开发实践
对象分析
我们首先需要确定一个像素点组成的地图(画布),要确定行数、列数和像素点大小。这个地图上将会有两个对象:蛇和食物。
蛇由头和身子组成,他们都有自己的位置,所以考虑使用位置点数组来存储。同时,还要存储蛇头方向和蛇身体节数。
而食物只要记录其位置即可。
地图上的内容就是蛇与食物,如果做图形界面,就是不断地在画布上绘制蛇与食物的过程。这里主要用到了绘制矩形与填充矩形的函数。
蛇的移动
蛇的移动就是对蛇身数组的操作,可以分类讨论。如果是蛇身,我们从蛇尾开始观察,会发现每一节的位置将是前一节的位置。即前一节的位置直接赋值给后一节即可,但要注意要从蛇尾开始操作。如果是蛇头,将取决于当前蛇的方向,但要注意不能掉头。
蛇吃食物
蛇吃食物的判定是蛇头与食物位置是否重叠,如果吃到了要增长蛇身和重新投放食物。增长蛇身其实就是在蛇身数组的末尾再复制一份蛇尾。重新投放食物时要注意不能放在蛇身上。
算法实现
图形界面
图形界面的代码在主函数里:
initgraph(WIDTH, HEIGHT);
while (true) {
...
}
closegraph();
整个绘制对象只用到了绘制矩形
setcolor(color);
设置线颜色setfillcolor(color);
设置填充颜色fillrect(x1, y1, x2, y2);
绘制矩形需要左上角和右下角的点位置
方向枚举
enum Direction
{
RIGHT = 39,
LEFT = 37,
DOWN = 40,
UP = 38,
};
为了方便与键盘输入对应,将上下左右的键值进行枚举。
小蛇移动
结合上面的示意图理解小蛇移动的算法实现。
void snakeMove()
{
// 更新蛇身
for (int i = snake.num-1; i > 0; i--) {
snake.body[i].x = snake.body[i-1].x;
snake.body[i].y = snake.body[i-1].y;
}
// 更新蛇头
switch (snake.dir) {
case RIGHT:
snake.body[0].x++;
break;
case LEFT:
snake.body[0].x--;
break;
case DOWN:
snake.body[0].y++;
break;
case UP:
snake.body[0].y--;
break;
}
}
蛇吃食物
结合上面的示意图理解蛇吃食物的算法实现。
void eatFood()
{
int x = snake.body[0].x;
int y = snake.body[0].y;
if (food.pos.x == x && food.pos.y == y) {
// 食物重新投放
bool flag;
do {
flag = false;
food.pos.x = rand()%(M-2) + 1;
food.pos.y = rand()%(N-2) + 1;
for (int i = 0; i < snake.num; i++) {
int x = snake.body[i].x, y = snake.body[i].y;
if (x == food.pos.x && y == food.pos.y) {
flag = true;
break;
}
}
} while (flag);
// 蛇身增长
snake.body[snake.num].x = snake.body[snake.num-1].x;
snake.body[snake.num].y = snake.body[snake.num-1].y;
snake.num++;
}
}
代码实现
这里使用的是EGE库,需要提前安装。
#include <stdio.h>
#include <stdlib.h>
#include <graphics.h>
// 画布参数
const int N = 10; // 行数
const int M = 10; // 列数
const int L = 40; // 像素点大小
const int T = 400; // 间隔
// 方向枚举
enum Direction
{
RIGHT = 39,
LEFT = 37,
DOWN = 40,
UP = 38,
};
// 点结构体
struct Point
{
int x, y;
int X() {return x*L;}
int Y() {return y*L;}
};
// 蛇结构体
struct Snake
{
int num;
Point body[N*M];
Direction dir;
} snake;
// 食物结构体
struct Food
{
Point pos;
} food;
// 功能函数
void initSnake(); // 初始化小蛇
void initFood(); // 初始化食物
void drawBoard(); // 绘制网格
void drawSnake(); // 绘制小蛇
void drawFood(); // 绘制食物
void drawAll(); // 全部绘制
void snakeMove(); // 小蛇移动
void eatFood(); // 吃到食物
bool snakeDie(); // 小蛇撞死
void keyDown(); // 按键处理
// -------------------主函数----------------------
int main()
{
initgraph(M*L, N*L);
begin:
initSnake();
initFood();
while (true) {
if (kbhit()) { // 监听键盘按键
keyDown();
}
snakeMove();
if (snakeDie()) {
MessageBox(getHWnd(),"Game Over!","rePlay",MB_OK);
goto begin;
}
eatFood();
drawAll();
Sleep(T);
}
closegraph();
return 0;
}
void initSnake()
{
snake.num = 3;
snake.dir = RIGHT;
snake.body[2].x = 1;
snake.body[2].y = 1;
snake.body[1].x = 2;
snake.body[1].y = 1;
snake.body[0].x = 3;
snake.body[0].y = 1;
}
void initFood()
{
bool flag;
do {
flag = false;
food.pos.x = rand()%(M-2) + 1;
food.pos.y = rand()%(N-2) + 1;
// 如果食物在蛇身上则重新投放
for (int i = 0; i < snake.num; i++) {
int x = snake.body[i].x, y = snake.body[i].y;
if (x == food.pos.x && y == food.pos.y) {
flag = true;
break;
}
}
} while (flag);
}
void drawBoard()
{
for (int i = 0; i < M; i++) {
for (int j = 0; j < N; j++) {
setcolor(BLUE);
setfillcolor(BLACK);
fillrect(i*L, j*L, i*L+L, j*L+L);
}
}
for (int i = 0; i < N; i++) {
setcolor(BLUE);
setfillcolor(EGERGB(0, 0, 139));
fillrect(0, i*L, L, i*L+L);
fillrect((M-1)*L, i*L, M*L, i*L+L);
}
for (int i = 0; i < M; i++) {
setcolor(BLUE);
setfillcolor(EGERGB(0, 0, 139));
fillrect(i*L, 0, i*L+L, L);
fillrect(i*L, (N-1)*L, i*L+L, N*L);
}
}
void drawSnake()
{
for (int i = 0; i < snake.num; i++) {
setcolor(BLUE);
setfillcolor(EGERGB(0,100,0));
fillrect(snake.body[i].X(),snake.body[i].Y(),snake.body[i].X()+L,snake.body[i].Y()+L);
}
}
void drawFood()
{
setcolor(BLUE);
setfillcolor(RED);
fillrect(food.pos.X(),food.pos.Y(),food.pos.X()+L,food.pos.Y()+L);
}
void drawAll()
{
drawBoard();
drawFood();
drawSnake();
}
void snakeMove()
{
// 更新蛇身
for (int i = snake.num-1; i > 0; i--) {
snake.body[i].x = snake.body[i-1].x;
snake.body[i].y = snake.body[i-1].y;
}
// 更新蛇头
switch (snake.dir) {
case RIGHT:
snake.body[0].x++;
break;
case LEFT:
snake.body[0].x--;
break;
case DOWN:
snake.body[0].y++;
break;
case UP:
snake.body[0].y--;
break;
}
}
void eatFood()
{
int x = snake.body[0].x;
int y = snake.body[0].y;
if (food.pos.x == x && food.pos.y == y) {
// 食物重新投放
bool flag;
do {
flag = false;
food.pos.x = rand()%(M-2) + 1;
food.pos.y = rand()%(N-2) + 1;
for (int i = 0; i < snake.num; i++) {
int x = snake.body[i].x, y = snake.body[i].y;
if (x == food.pos.x && y == food.pos.y) {
flag = true;
break;
}
}
} while (flag);
// 蛇身增长
snake.body[snake.num].x = snake.body[snake.num-1].x;
snake.body[snake.num].y = snake.body[snake.num-1].y;
snake.num++;
}
}
bool snakeDie()
{
int x = snake.body[0].x;
int y = snake.body[0].y;
// 超出边框
if (x >= (M-1) || y >= (N-1) || x <= 0 || y <= 0) {
return true;
}
// 迟到自身
for (int i = 1; i < snake.num; i++) {
if (x == snake.body[i].x && y == snake.body[i].y) {
return true;
}
}
return false;
}
void keyDown()
{
char userKey = 0;
userKey = getch();
switch (userKey) {
case LEFT:
if (snake.dir != RIGHT) {
snake.dir = LEFT;
}
break;
case RIGHT:
if (snake.dir != LEFT) {
snake.dir = RIGHT;
}
break;
case UP:
if (snake.dir != DOWN) {
snake.dir = UP;
}
break;
case DOWN:
if (snake.dir != UP) {
snake.dir = DOWN;
}
}
}
- snake