C语言 控制台版 贪吃蛇小游戏
// Snake.cpp : 定义控制台应用程序的入口点。
//
//1、构造蛇身:定义一个坐标数组,存放的是蛇的每一节蛇身所在的坐标位置。这样就将移动蛇身的操作转换为移动数组的操作,将吃食物增加蛇身体长度的操作转换为在数组后面追加元素的操作。
//
//2、移动效果:每次移动时,将每一节蛇身(蛇头除外)依次往前移动一节,然后擦去蛇的最后一节,最后确定蛇头的方向,再绘制一个蛇头。这样就会显示一个移动效果。
//
//3、身体增加效果:每次移动时候,判断蛇头是否碰到了食物,如果碰到了食物,则吃掉它,并且只进行前移蛇身和增加蛇头的操作,不进行擦除蛇尾的操作(可以用一个标记变量判断是否吃掉了食物,然后在擦除蛇尾那里判断是否需要擦除蛇尾),这就会显示蛇身体增加的效果。
//
#include "stdafx.h"
#define SNAKESIZE 100//蛇的身体最大节数
#define MAPWIDTH 60 //宽度
#define MAPHEIGHT 20//高度
//食物的坐标
struct _tagFood
{
int x;
int y;
} Food;
//蛇的相关属性
struct _tagSnake{
int x[SNAKESIZE]; //组成蛇身的每一个小方块中x的坐标
int y[SNAKESIZE]; //组成蛇身的每一个小方块中y的坐标
int speed; //蛇移动的速度
int length; //蛇的长度
} Snake;
/**
*控制台按键所代表的数字
*“↑”:72
*“↓”:80
*“←”:75
*“→”:77
*/
enum KeyCode
{
KEY_UP = 72,
KEY_DOWN = 80,
KEY_LEFT = 75,
KEY_RIGHT = 77,
KEY_SPACEBAR = 32
};
//绘制游戏边框
void drawMap();
//随机生成食物
void createFood();
//按键操作
void keyDown();
//蛇的状态
bool snakeStatus();
//从控制台移动光标
void gotoxy(int x, int y);
int key = KEY_UP;//表示蛇移动的方向,72为按下“↑”所代表的数字
int changeFlag = 0; //用来判断蛇是否吃掉了食物,这一步很重要,涉及到是否会有蛇身移动的效果以及蛇身增长的效果
int score = 0;//记录玩家的得分
bool bPause = false;
/*
* 将控制台光标移到(x,y)处
*/
void gotoxy(int x, int y)
{
HANDLE handleConsole = GetStdHandle(STD_OUTPUT_HANDLE);
COORD coord;
coord.X = x;
coord.Y = y;
SetConsoleCursorPosition(handleConsole, coord);
}
/*
* 绘制游戏边框和蛇
*/
void drawMap()
{
//打印上下边框
for (int i = 0; i <= MAPWIDTH; i += 2)//i+=2是因为横向占用的是两个位置
{
//将光标移动依次到(i,0)处打印上边框
gotoxy(i, 0);
printf_s("■");
//将光标移动依次到(i,MAPHEIGHT)处打印下边框
gotoxy(i, MAPHEIGHT);
printf_s("■");
}
//打印左右边框
for (int j = 0; j <= MAPHEIGHT; j++)
{
//将光标移动依次到(0,i)处打印左边框
gotoxy(0, j);
printf("■");
//将光标移动依次到(MAPWIDTH, i)处打印左边框
gotoxy(MAPWIDTH, j);
printf("■");
}
//随机生成初始食物
while (1)
{
srand((unsigned int)time(NULL));
Food.x = rand() % (MAPWIDTH - 4) + 2;
Food.y = rand() % (MAPHEIGHT - 2) + 1;
//生成的食物横坐标的奇偶必须和初试时蛇头所在坐标的奇偶一致,因为一个字符占两个字节位置,若不一致
//会导致吃食物的时候只吃到一半
if (Food.x % 2 == 0)
break;
}
//将光标移到食物的坐标处打印食物
gotoxy(Food.x, Food.y);
printf("⊙");
//初始化蛇的属性
Snake.length = 3;
Snake.speed = 300;
//在地图中间生成蛇头
Snake.x[0] = MAPWIDTH / 2 ; //x坐标为偶数
Snake.y[0] = MAPHEIGHT / 2;
//打印蛇头
gotoxy(Snake.x[0], Snake.y[0]);
printf("★");
//生成初始蛇身
for (int i = 1; i < Snake.length; ++i)
{
//蛇身的打印,纵坐标不变,横坐标为上一节蛇身的坐标值+2
Snake.x[i] = Snake.x[i - 1] + 2;
Snake.y[i] = Snake.y[i - 1];
gotoxy(Snake.x[i], Snake.y[i]);
printf("★");
}
//打印完蛇身后将光标移到屏幕最上方,避免光标在蛇身处一直闪烁
gotoxy(MAPWIDTH - 2, 0);
return;
}
void keyDown()
{
int pre_key = key;//记录前一个按键的方向
if (_kbhit())//如果用户按下了键盘中的某个键
{
fflush(stdin);//清空缓冲区的字符
//getch()读取方向键的时候,会返回两次,第一次调用返回0或者224,第二次调用返回的才是实际值
key = _getch();//第一次调用返回的不是实际值
key = _getch();//第二次调用返回实际值
}
/*
*蛇移动时候先擦去蛇尾的一节
*changeFlag为0表明此时没有吃到食物,因此每走一步就要擦除掉蛇尾,以此营造一个移动的效果
*为1表明吃到了食物,就不需要擦除蛇尾,以此营造一个蛇身增长的效果
*/
if (changeFlag == 0)
{
gotoxy(Snake.x[Snake.length - 1], Snake.y[Snake.length - 1]);
printf(" ");//在蛇尾处输出空格即擦去蛇尾
}
//将蛇的每一节依次向前移动一节(蛇头除外)
for (int i = Snake.length - 1; i > 0; i--)
{
Snake.x[i] = Snake.x[i - 1];
Snake.y[i] = Snake.y[i - 1];
}
//蛇当前移动的方向不能和前一次的方向相反,比如蛇往左走的时候不能直接按右键往右走
//如果当前移动方向和前一次方向相反的话,把当前移动的方向改为前一次的方向
//判断蛇头应该往哪个方向移动
if (pre_key == KEY_UP && key == KEY_DOWN)
key = KEY_UP;
if (pre_key == KEY_DOWN && key == KEY_UP)
key = KEY_DOWN;
if (pre_key == KEY_LEFT && key == KEY_RIGHT)
key = KEY_LEFT;
if (pre_key == KEY_RIGHT && key == KEY_LEFT)
key = KEY_RIGHT;
switch (key)
{
case KEY_LEFT:
Snake.x[0] -= 2; //往左
break;
case KEY_RIGHT:
Snake.x[0] += 2; //往右
break;
case KEY_UP:
Snake.y[0]--; //往上
break;
case KEY_DOWN:
Snake.y[0]++; //往下
break;
}
//打印出蛇头
gotoxy(Snake.x[0], Snake.y[0]);
printf_s("★");
gotoxy(MAPWIDTH - 2, 0);
//由于目前没有吃到食物,changFlag值为0
changeFlag = 0;
return;
}
void createFood()
{
if (Snake.x[0] == Food.x && Snake.y[0] == Food.y)//蛇头碰到食物
{
//蛇头碰到食物即为要吃掉这个食物了,
srand((unsigned int)time(NULL));
while (1)
{
int flag = 1;
Food.x = rand() % (MAPWIDTH - 4) + 2;
Food.y = rand() % (MAPHEIGHT - 2) + 1;
//随机生成的食物不能在蛇的身体上
for (int i = 0; i < Snake.length; i++)
{
if (Snake.x[i] == Food.x && Snake.y[i] == Food.y)
{
flag = 0;
break;
}
}
//随机生成的食物不能横坐标为奇数,也不能在蛇身,否则重新生成
if (flag && (Food.x % 2 == 0))
break;
}
//绘制食物
gotoxy(Food.x, Food.y);
printf_s("⊙");
Snake.length++; //吃到食物,蛇身长度加1
score += 10; //每个食物得10分
gotoxy(MAPWIDTH + 5, MAPHEIGHT + 5);
printf_s("得分: %d", score);
Snake.speed -= 5;//随着吃的食物越来越多,速度会越来越快
changeFlag = 1;//很重要,因为吃到了食物,就不用再擦除蛇尾的那一节,以此来造成蛇身体增长的效果
PlaySound(TEXT(".\\coin.wav"), NULL, SND_FILENAME | SND_ASYNC);
}
return;
}
bool snakeStatus()
{
//蛇头碰到上下边界,游戏结束
if (Snake.y[0] == 0 || MAPHEIGHT == Snake.y[0])
return false;
//蛇头碰到左右边界,游戏结束
if (Snake.x[0] == 0 || MAPWIDTH == Snake.x[0])
return false;
//蛇头碰到蛇身,游戏结束
for (int i = 1; i < Snake.length; i++)
{
if (Snake.x[i] == Snake.x[0] && Snake.y[i] == Snake.y[0])
return false;
}
return true;
}
int main()
{
SetConsoleTitle(TEXT("贪吃蛇"));
drawMap();
mciSendString(TEXT("open bg.mp3 alias mymusic"), NULL, 0, NULL);
mciSendString(TEXT("play mymusic repeat"), NULL, 0, NULL);
while (1)
{
keyDown();
gotoxy(0, MAPHEIGHT + 3);
printf_s("%d", key);
if (!snakeStatus())
break;
createFood();
Sleep(Snake.speed);
}
mciSendString(TEXT("stop mymusic"), NULL, 0, NULL);
gotoxy(0, MAPHEIGHT + 5);
printf_s("Game Over!\n");
gotoxy(0, MAPHEIGHT + 8);
printf_s("本次游戏得分为:%d\n", score);
Sleep(5000);
return 0;
}