【 声明:版权所有,欢迎转载,请勿用于商业用途。 联系信箱:[email protected]】
大家都玩过游戏,游戏一定有一个限制框来限制我们游戏主人公的行动,说白了就是地图,这就是我们的游戏窗口。见如下代码
void MakeFrame(HANDLE hOut)
{
//打印边框
SetPosition(hOut, FRAMEX, FRAMEY); //设置光标为左上角坐标处
printf("┏");
SetPosition(hOut, FRAMEX + 2 * FRAMEWIDTH - 2, FRAMEY); //设置光标为右上角坐标处
printf("┓");
SetPosition(hOut, FRAMEX, FRAMEY + FRAMEHEIGHT); //设置光标为左下角坐标处
printf("┗");
SetPosition(hOut, FRAMEX + 2 * FRAMEWIDTH - 2, FRAMEY + FRAMEHEIGHT); //设置光标为右下角坐标处
printf("┛");
//水平-顶端
for (int i = 2; i < 2 * FRAMEWIDTH - 2; i+=2)
{
SetPosition(hOut, FRAMEX + i, FRAMEY);
printf("━");
}
//水平-底端
for (int i = 2; i < 2 * FRAMEWIDTH - 2; i += 2)
{
SetPosition(hOut, FRAMEX + i, FRAMEY + FRAMEHEIGHT);
printf("━");
}
//竖直-左端
for (int i = 1; i < FRAMEHEIGHT; i++)
{
SetPosition(hOut, FRAMEX, FRAMEY + i);
printf("|");
}
//竖直-右端
for (int i = 1; i < FRAMEHEIGHT; i++)
{
SetPosition(hOut, FRAMEX + 2 * FRAMEWIDTH - 2, FRAMEY + i);
printf("|");
}
//打印游戏名称
SetPosition(hOut, FRAMEX + FRAMEWIDTH - 5, FRAMEY - 2);
printf("贪吃蛇游戏");
//打印游戏操作
SetPosition(hOut, FRAMEX, FRAMEY + FRAMEHEIGHT + 2);
printf("游戏操作: 上: ↑ 左: ← 右: → 下: ↓");
SetPosition(hOut, FRAMEX, FRAMEY + FRAMEHEIGHT + 4);
printf("加速: 长按方向键 退出: ESC 暂停:Space");
}
以上代码需要说明的有
1. SetPosition(HANDLE hOut, int x, int y)函数:
void SetPosition(HANDLE hOut, int x, int y)
{
COORD pos;
pos.X = x;
pos.Y = y;
SetConsoleCursorPosition(hOut, pos);
}
从上式代码可以看出,SetPosition(HANDLE hOut, int x, int y)函数是用于把光标放在我们需要的(x, y)上,从而能在(x, y)处打印我们需要的东西。
**2. 常量:FRAMEX和FRAMEY和FRAMEHEIGHT和FRAMEWIDTH的含义 **
正如常量的名字一样,我们在主函数的前面定义了这四个常量,其含义和数值如下:
#define FRAMEX 4 //窗口左上角横坐标
#define FRAMEY 4 //窗口左上角纵坐标
#define FRAMEWIDTH 25 //游戏窗口宽度
#define FRAMEHEIGHT 25 //游戏窗口高度
根据蛇在本游戏中具有的性质,我们将这些性质打包到名为蛇的结构体中,同理也定义了一个食物的结构体。代码如下:
//蛇
typedef struct SNAKE
{
int x[100]; //蛇的横坐标, x[0]蛇尾横坐标
int y[100]; //蛇的纵坐标, y[0]蛇尾纵坐标
int nCount; //蛇吃食物总数
int nLength; //蛇的长度
int nSpeed; //蛇的移动速度
}Snake;
//食物
typedef struct FOOD
{
int x; //食物的横坐标
int y; //食物的纵坐标
}Food;
//初始化蛇
void InitSnake(Snake* pSnake)
{
pSnake->x[0] = FRAMEX + 2; //初始化蛇尾的横坐标
pSnake->y[0] = FRAMEX + FRAMEHEIGHT / 2; //初始化蛇尾的纵坐标
pSnake->nSpeed = 80; //初始化蛇的移动速度
pSnake->nCount = 0; //食物个数
pSnake->nLength = 3; //蛇身长度
for (int i = 1; i < pSnake->nLength; i++)
{
pSnake->x[i] = pSnake->x[i - 1] + 2;
pSnake->y[i] = pSnake->y[i - 1];
}
}
//打印蛇
void PrintfSnake(HANDLE hOut, Snake* pSnake)
{
for (int i = 0; i < pSnake->nLength; i++)
{
SetPosition(hOut, pSnake->x[i], pSnake->y[i]);
//打印蛇尾
if (i == 0)
{
SetConsoleTextAttribute(hOut, FOREGROUND_INTENSITY | FOREGROUND_BLUE);
printf("○");
SetConsoleTextAttribute(hOut, FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE);
}
//打印蛇头
else if (i == pSnake->nLength - 1)
{
SetConsoleTextAttribute(hOut, FOREGROUND_INTENSITY | FOREGROUND_GREEN);
printf("¤");
SetConsoleTextAttribute(hOut, FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE);
}
//打印蛇身
else
{
SetConsoleTextAttribute(hOut, FOREGROUND_INTENSITY | FOREGROUND_GREEN | FOREGROUND_RED);
printf("⊙");
SetConsoleTextAttribute(hOut, FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE);
}
}
}
作为一个心系玩家的开发者。我们需要将玩家在玩游戏时所产生的一些信息包括等级、得分等打印到屏幕上。这样可以让玩家在游戏得分中得到鼓舞,不断挑战自己。
void PrintfMenu(HANDLE hOut, Snake* pSnake, Food* pFood)
{
//游戏等级
SetPosition(hOut, FRAMEX + FRAMEWIDTH * 2 + 5, FRAMEY + 1);
printf("游戏等级:%d", pSnake->nCount / 5 + 1); //每吃五个食物升一级
//游戏得分
SetPosition(hOut, FRAMEX + FRAMEWIDTH * 2 + 5, FRAMEY + 3);
printf("游戏得分:%d分", pSnake->nCount); //每吃一个食物得一分
//食物个数
SetPosition(hOut, FRAMEX + FRAMEWIDTH * 2 + 5, FRAMEY + 5);
printf("所吃食物总个数:%d个", pSnake->nCount); //玩家所吃的食物总数
//游戏速度
if (pSnake->nSpeed == 80)
{
SetPosition(hOut, FRAMEX + FRAMEWIDTH * 2 + 5, FRAMEY + 7);
printf("贪吃蛇的速度级别为:1");
}
else if (pSnake->nSpeed == 70)
{
SetPosition(hOut, FRAMEX + FRAMEWIDTH * 2 + 5, FRAMEY + 7);
printf("贪吃蛇的速度级别为:2");
}
else if (pSnake->nSpeed == 60)
{
SetPosition(hOut, FRAMEX + FRAMEWIDTH * 2 + 5, FRAMEY + 7);
printf("贪吃蛇的速度级别为:3");
}
else if (pSnake->nSpeed == 50)
{
SetPosition(hOut, FRAMEX + FRAMEWIDTH * 2 + 5, FRAMEY + 7);
printf("贪吃蛇的速度级别为:4");
}
else if (pSnake->nSpeed == 40)
{
SetPosition(hOut, FRAMEX + FRAMEWIDTH * 2 + 5, FRAMEY + 7);
printf("贪吃蛇的速度级别为:5");
}
else if (pSnake->nSpeed == 30)
{
SetPosition(hOut, FRAMEX + FRAMEWIDTH * 2 + 5, FRAMEY + 7);
printf("贪吃蛇的速度级别为:6");
}
else
{
SetPosition(hOut, FRAMEX + FRAMEWIDTH * 2 + 5, FRAMEY + 7);
printf("贪吃蛇的速度级别为:逆天级别");
}
//食物坐标
SetPosition(hOut, FRAMEX + FRAMEWIDTH * 2 + 5, FRAMEY + 9);
printf("食物的坐标:(%d, %d)", pFood->x, pFood->y);
//温馨提示:
SetPosition(hOut, FRAMEX + FRAMEWIDTH * 2 + 5, FRAMEY + 12);
SetConsoleTextAttribute(hOut, FOREGROUND_INTENSITY | FOREGROUND_BLUE);
printf("游戏规则:吃到食物,碰到自身则蛇死亡。");
SetPosition(hOut, FRAMEX + FRAMEWIDTH * 2 + 5, FRAMEY + 14);
printf("PS:这不是普通的墙哦,可以穿过去的(*^▽^*)");
SetConsoleTextAttribute(hOut, FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE);
}
关于食物的生成,有一下几点需要注意的:
根据以上两点,可写出如下代码:
void MakeFood(HANDLE hOut, Snake* pSnake, Food* pFood)
{
srand((unsigned)time(NULL));
/*
1.在游戏框架内
2.不在蛇身上
*/
while(1)
{
pFood->x = rand() % (FRAMEWIDTH - 1); //0 - FRAMEWIDTH-1;
pFood->y = rand() % FRAMEHEIGHT; //0 - FRAMEHEIGHT-1;
if (pFood->x == 0 || pFood->y == 0) //食物不能再边界处
continue;
pFood->x = 2 * pFood->x + FRAMEX;
pFood->y += FRAMEY;
//判断食物是不是在蛇的身上
int temp;
for (temp = 0; temp < pSnake->nLength; temp++)
{
if (pFood->x == pSnake->x[temp] && pFood->y == pSnake->y[temp])
break;
}
if (temp == pSnake->nLength)
{
SetPosition(hOut, pFood->x, pFood->y);
SetConsoleTextAttribute(hOut, FOREGROUND_INTENSITY | FOREGROUND_RED);
printf("◎");
SetConsoleTextAttribute(hOut, FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE);
break;
}
}
}
看到这里,大家想想要如何让蛇移动起来呢?我们从前面的代码知道了整条蛇由众多的小蛇块组成,其中x[0]和y[0]是蛇尾的坐标。那么我们只要将让蛇尾去代替蛇尾前一块蛇的位置,以此不断推前,就能完成蛇的移动。在移动蛇的位置我们不考虑蛇头的位置,蛇头的运动由我们的玩家来控制,在StartGame()函数里面执行,现在可先不管。
如图,假设这条蛇的长度为6,x[5]为蛇头,x[0]为蛇尾。那么下一个时刻,除去蛇头,其余部位的位置应该如上图。x[0]所在的位置应该没有蛇的部位,反而,x[0]的位置挪到了x[1]的位置,以此类推,完成蛇的移动。至于蛇头的位置去哪了呢。不急~且看后文。看懂这个移动的原理后我们就可以写出下面的代码:
void MoveSnake(HANDLE hOut, Snake* pSnake)
{
SetPosition(hOut, pSnake->x[0], pSnake->y[0]);
printf(" ");
//后一个坐标代替前一个坐标
for (int i = 1; i < pSnake->nLength; i++)
{
pSnake->x[i - 1] = pSnake->x[i];
pSnake->y[i - 1] = pSnake->y[i];
}
}
这部分没什么好讲的,大家看看代码就能理解了~
void EatFood(HANDLE hOut, Snake* pSnake, Food* pFood)
{
if (pSnake->x[pSnake->nLength - 1] == pFood->x && pSnake->y[pSnake->nLength - 1] == pFood->y)
{
//如果蛇头位置与食物位置相同,吃食物
pSnake->nLength++;
for (int i = pSnake->nLength - 1; i > 0; i--)
{
pSnake->x[i] = pSnake->x[i - 1];
pSnake->y[i] = pSnake->y[i - 1];
}
pSnake->x[0] = tail[0]; //得到蛇尾移动前的横坐标
pSnake->y[0] = tail[1]; //得到蛇尾移动前的纵坐标
//重新产生食物
MakeFood(hOut, pSnake, pFood);
pSnake->nCount++; //食物的数量加一
//当蛇吃Up_level个食物时,速度加快Up_speed毫秒并且升一级
if (pSnake->nCount % 6 == 0)
pSnake->nSpeed -= 10;
}
}
以上代码需要说明的有一点。
tail数组的含义
在主函数的前面,我们定义了一个全局变量tail[2]数组,来记录蛇尾移动前的位置
int tail[2]; //用于记住蛇尾坐标,其中tail[0]、tail[1]分别表示横、竖坐标
##瞬间移动——穿墙
大家看成品图应该看到了,那一句蓝色的提示语,是的,在本游戏中,这条蛇是可以穿墙的,这穿墙不是指穿过墙跑到界外。而是,比如蛇撞到右边的墙壁中,则会从左边的墙壁出现。
void ThroughWall(HANDLE hOut, Snake* pSnake, char ch)
{
//如果蛇在上框且向上移动,穿墙
if (ch == 72 && pSnake->y[pSnake->nLength - 1] == FRAMEY)
{
pSnake->y[pSnake->nLength - 1] = FRAMEY + FRAMEHEIGHT - 1;
}
//如果蛇在下框且向下移动,穿墙
if (ch == 80 && pSnake->y[pSnake->nLength - 1] == FRAMEY + FRAMEHEIGHT)
{
pSnake->y[pSnake->nLength - 1] = FRAMEY + 1;
}
//如果蛇在右框且向右移动,穿墙
if (ch == 75 && pSnake->x[pSnake->nLength - 1] == FRAMEX)
{
pSnake->x[pSnake->nLength - 1] = FRAMEX + 2 * FRAMEWIDTH - 4;
}
//如果蛇在左框且向左移动,穿墙
if (ch == 77 && pSnake->x[pSnake->nLength - 1] == FRAMEX + 2 * FRAMEWIDTH - 2)
{
pSnake->x[pSnake->nLength - 1] = FRAMEX + 2;
}
}
##生命凋零——蛇死亡
在本游戏中,蛇死亡的条件很简单,碰到自身就死了。
bool IfSnakeDie(Snake* pSnake)
{
//当蛇头碰到自身时,蛇死,返回值为TRUE
for (int i = 0; i < pSnake->nLength - 1; i++)
{
if (pSnake->x[pSnake->nLength - 1] == pSnake->x[i] && pSnake->y[pSnake->nLength - 1] == pSnake->y[i])
return TRUE;
}
return FALSE;
}
##一切的开始——游戏开始
bool StartGame()
{
unsigned char chOld = 77;
unsigned char chNow = 77;
HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);
//定义蛇
Snake snake;
//定义食物
Food food;
//制作游戏窗口
MakeFrame(hOut);
//初始化蛇
InitSnake(&snake);
//产生食物
MakeFood(hOut, &snake, &food);
while (1)
{
//打印菜单信息
PrintfMenu(hOut, &snake, &food);
tail[0] = snake.x[0]; //记住蛇尾的横坐标
tail[1] = snake.y[0]; //记住蛇尾的纵坐标
bool speedUp = FALSE;
//判断键盘是否按下
if (kbhit())
{
chNow = getch();
//Sleep(10);
if (kbhit())
speedUp = TRUE;
}
switch (chNow)
{
case 72: //向上
{
MoveSnake(hOut, &snake);
if (chOld == 80) //如果蛇调头,则无视调头继续走
{
snake.y[snake.nLength - 1] += 1;
chNow = chOld;
break;
}
chOld = chNow;
snake.y[snake.nLength - 1] -= 1;
break;
}
case 80: //向下
{
MoveSnake(hOut, &snake);
if (chOld == 72)
{
snake.y[snake.nLength - 1] -= 1;
chNow = chOld;
break;
}
chOld = chNow;
snake.y[snake.nLength - 1] += 1;
break;
}
case 75: //向左
{
MoveSnake(hOut, &snake);
if (chOld == 77)
{
snake.x[snake.nLength - 1] += 2;
chNow = chOld;
break;
}
chOld = chNow;
snake.x[snake.nLength - 1] -= 2;
break;
}
case 77: //向右
{
MoveSnake(hOut, &snake);
if (chOld == 75)
{
snake.x[snake.nLength - 1] -= 2;
chNow = chOld;
break;
}
chOld = chNow;
snake.x[snake.nLength - 1] += 2;
break;
}
case 27: //ESC键
break;
case 32: //暂停
{
break;
}
default: //去掉其他按键的影响
chNow = chOld;
break;
}
//判断是否要出暂停提示
if (chNow != 32) //清空暂停的提示
{
SetConsoleTextAttribute(hOut, FOREGROUND_INTENSITY | FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE);
SetPosition(hOut, FRAMEX + FRAMEWIDTH * 2 + 5, FRAMEY + 16);
printf(" ");
SetConsoleTextAttribute(hOut, FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE);
}
else//显示暂停指示
{
SetConsoleTextAttribute(hOut, FOREGROUND_INTENSITY | FOREGROUND_RED);
SetPosition(hOut, FRAMEX + FRAMEWIDTH * 2 + 5, FRAMEY + 16);
printf("已暂停,按方向键可继续游戏");
SetConsoleTextAttribute(hOut, FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE);
}
//穿墙
ThroughWall(hOut, &snake, chNow);
//判断有无吃食物
EatFood(hOut, &snake, &food);
//打印蛇
PrintfSnake(hOut, &snake);
/*
游戏结束条件:
1. 蛇碰到自身
2. 按ESC键
*/
if (IfSnakeDie(&snake) == TRUE || chNow == 27)
{
SetPosition(hOut, FRAMEX + FRAMEWIDTH - 2, FRAMEY + FRAMEHEIGHT / 2 - 1);
bool State = OverGame(hOut, &snake);
return State;
}
PrintfMenu(hOut, &snake, &food);
if (speedUp == FALSE)
{
//SetPosition(hOut, FRAMEX + FRAMEWIDTH * 2 + 5, FRAMEY + 9);
//printf(" ");
Sleep(snake.nSpeed);
}
else
{
//SetPosition(hOut, FRAMEX + FRAMEWIDTH * 2 + 5, FRAMEY + 9);
//printf("正在加速...");
Sleep(10);
}
}
}
##一切的结束——结束游戏
bool OverGame(HANDLE hOut, Snake* pSnake)
{
system("cls");
SetPosition(hOut, FRAMEX + FRAMEWIDTH + 10, FRAMEY + FRAMEHEIGHT / 4);
printf("Game Over");
//打印得分
if (pSnake->nCount < 20)
{
SetPosition(hOut, FRAMEX + FRAMEWIDTH, FRAMEY + FRAMEHEIGHT / 4 + 2);
printf("好可惜哦!你的游戏得分为:%d分, 要不再来一盘突破自己?", pSnake->nCount);
}
else if (pSnake->nCount < 30)
{
SetPosition(hOut, FRAMEX + FRAMEWIDTH, FRAMEY + FRAMEHEIGHT / 4 + 2);
printf("不错哦!你的游戏得分为:%d分, 我猜你还没拿出实力吧", pSnake->nCount);
}
else
{
SetPosition(hOut, FRAMEX + FRAMEWIDTH, FRAMEY + FRAMEHEIGHT / 4 + 2);
printf("哇好腻害(⊙o⊙)!你的游戏得分为:%d分, 你的手速已经超越地球人了", pSnake->nCount);
}
SetPosition(hOut, FRAMEX + FRAMEWIDTH, FRAMEY + FRAMEHEIGHT/4 + 4);
printf("重新开始游戏:Enter 退出游戏:ESC");
int userKey = 0;
while (1)
{
if ((userKey = getch()) == 13)
return FALSE;
else if ((userKey = getch()) == 27)
return TRUE;
else
continue;
}
}
##上帝控制台——主函数
int main()
{
while(1)
{
//开始游戏
system("cls");
bool State= StartGame();
if (State)
break;
}
return 0;
}