学习完链表之后,利用所学的知识写了一个史上最挫版的贪吃蛇,虽然想再提升一下,加了BGM(背景音乐)但游戏体验还是极差。等学习了图形界面之后再加上游戏界面之后应该游戏体验应该会提升一丢丢吧!
贪吃蛇游戏的主要要解决的问题:
- 怎样控制蛇的移动
- 蛇吃到食物后蛇的身子怎样加长
- 蛇的活动区域
- 在蛇的活动区域内怎样随机生成食物
- 游戏的结束条件
主要要解决的就是这几个问题,那直接上代码,用代码来体现这几个问题是如何化解的。
项目中要用到的数据包含在头文件DataType里面
DataType.h
#pragma once
//在所在的坐标系的坐标
typedef struct Position
{
int x;
int y;
}Position;
//构成蛇的每一个节点
typedef struct SnakeNode
{
Position pos;//坐标
struct SnakeNode* Pnext;//指向下一个节点
}SnakeNode;
//蛇可能的前进方向
typedef enum Direction
{
UP,
DOWN,
LEFT,
RIGHT
}Direction;
//蛇本身
typedef struct Snake
{
SnakeNode *Phead;//蛇头
SnakeNode*Ptail;//蛇尾
Direction direction;//蛇要走的方向
}Snake;
//游戏本身所需要的数据
typedef struct Game
{
Snake snake;
int score;
int Perfoodscore;
Position Foodposition;
int Sleeptime;
int speed;
}Game;
game.c
#pragma once
#include"DataType.h"
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<Windows.h>
#include<time.h>
#include"View.h"
#define WIDTH 28
#define HEIGHT 27
#include<mmsystem.h>//音乐播放所需要的头文件
#pragma comment(lib,"winmm.lib")//音乐播放所需要的文件库
//蛇本身自己的初始化
void SnakeInit(Snake*Psnake)
{
assert(Psnake != NULL);
Position pos;
SnakeNode*Pnode;
Psnake->Phead =Psnake->Ptail= NULL;
int i = 0;
int x = 0;
int y = 0;
//蛇刚开始时所在的坐标为(5,5)、(6,5)、(7,5)
for (i = 0; i < 3; i++)
{
x = 5 + i;
y = 5;
pos.x = x;
pos.y = y;
Pnode = (SnakeNode*)malloc(sizeof(SnakeNode));
assert(Pnode != NULL);
Pnode->pos = pos;
Pnode->Pnext = Psnake->Phead;
Psnake->Phead = Pnode;
}
Psnake->Ptail = Psnake->Phead->Pnext->Pnext;
Psnake->direction = RIGHT;
}
//判断生成的食物的坐标是否和蛇身子重叠
int IsOverlapSnake(int x,int y,Snake* Psnake)
{
assert(Psnake != NULL);
SnakeNode*Pnode = Psnake->Phead;
//遍历蛇身的所有节点,对比每个节点的坐标是否与生成的食物坐标相同
while (Pnode!= NULL)
{
if (Pnode->pos.x == x&&Pnode->pos.y == y)
{
return 1;
}
Pnode = Pnode->Pnext;
}
return 0;
}
//生成的食物
/*
条件:
1.不能和蛇身子重叠
2.不能生成在边界上
3.随机生成
*/
void GenerateFood(Snake*Psnake,Position *Posfood,int width,int heigth)
{
assert(Psnake != NULL);
int x = 0;
int y = 0;
do
{
//不能生成在边界上且随机生成
x = rand() % width;
y = rand() % heigth;
} while (IsOverlapSnake(x,y,Psnake));
Posfood->x = x;
Posfood->y = y;
}
void GameInit(Game*Pgame)
{
assert(Pgame != NULL);
SnakeInit(&(Pgame->snake));
GenerateFood(&(Pgame->snake), &(Pgame->Foodposition), WIDTH, HEIGHT);
Pgame->score = 0;
Pgame->Perfoodscore = 10;
Pgame->Sleeptime = 500;
Pgame->speed = 0;
}
Position GetNextPos(Snake*Psnake)
{
Position NextPos = Psnake->Phead->pos;
switch (Psnake->direction)
{
case UP:
NextPos.y -= 1;
break;
case DOWN:
NextPos.y += 1;
break;
case LEFT:
NextPos.x -= 1;
break;
case RIGHT:
NextPos.x += 1;
break;
}
return NextPos;
}
//让蛇往前走
//在蛇的头部插入一个新节点
void AddSnakeHead(Snake*Psnake,Position NextPos)
{
assert(Psnake != NULL);
SnakeNode*Pnode = (SnakeNode*)malloc(sizeof(SnakeNode));
assert(Pnode != NULL);
Pnode->pos = NextPos;
Pnode->Pnext = Psnake->Phead;
Psnake->Phead = Pnode;
PrintSnakeBlock(NextPos);
}
//让蛇往前走
//删除蛇的尾巴
void RemoveSnakeTail(Snake*Psnake)
{
assert(Psnake != NULL);
SnakeNode*Pnode = Psnake->Phead;
while (Pnode->Pnext->Pnext != NULL)
{
Pnode = Pnode->Pnext;
}
CleanBlock(Pnode->Pnext->pos);
free(Pnode->Pnext);
Pnode->Pnext = NULL;
}
//判断蛇是否吃到了食物,比较蛇要前进的下一个坐标与食物坐标是否重叠,若重叠表示吃到食物,否则没有
int IsEatFood(Position NextPos, Position FoodPosition)
{
if (NextPos.x == FoodPosition.x&&NextPos.y == FoodPosition.y)
{
return 1;
}
return 0;
}
//判断蛇要前进的下一个坐标是否在边界
int IsKilledByWall(Position NextPos, int width, int height)
{
if (NextPos.x < 0)
{
return 1;
}
if (NextPos.x >= width)
{
return 1;
}
if (NextPos.y < 0)
{
return 1;
}
if (NextPos.y >= height)
{
return 1;
}
return 0;
}
//判断蛇要前进的下一个坐标是否与蛇身的某个节点相重合
int IsKilledBySelf(Snake*Psnake)
{
SnakeNode*Pnode = Psnake->Phead->Pnext;
Position head = Psnake->Phead->pos;
while (Pnode != NULL)
{
if (head.x==Pnode->pos.x&&head.y==Pnode->pos.y)
{
return 1;
}
Pnode = Pnode->Pnext;
}
return 0;
}
void Pause()//按space键暂停游戏
{
while (1)
{
if (GetAsyncKeyState(VK_SPACE))
{
break;
}
Sleep(300);
}
}
void Speed(Game *Pgame)//加速
{
if (GetAsyncKeyState(VK_RETURN))
{
Pgame->speed +=100;
}
}
//背景音乐
DWORD WINAPI Fun(LPVOID lpPrarmter)
{
while (1)
{
PlaySound(TEXT("D:\\代码Code\\贪吃蛇\\Debug\\贪吃蛇大作战-夺宝模式 - 小旭音乐003.wav"), NULL, SND_FILENAME | SND_SYNC);
}
}
void GameRun()
{
//初始化工作
Position NextPos;
Game game;
GameInit(&game);
PrintGameInformation(&game);
PrintFood(game.Foodposition);
PrintSnake(&(game.snake));
while (1)
{
//播放背景音乐
HANDLE hThread = CreateThread(NULL, 0, Fun, NULL, 0, NULL);
CloseHandle(hThread);
//玩游戏
//按方向键后蛇会改变方向
if (GetAsyncKeyState(VK_UP) && game.snake.direction != DOWN)
{
game.snake.direction = UP;
}
else if (GetAsyncKeyState(VK_DOWN) && game.snake.direction != UP)
{
game.snake.direction = DOWN;
}
else if (GetAsyncKeyState(VK_LEFT) && game.snake.direction != RIGHT)
{
game.snake.direction = LEFT;
}
else if (GetAsyncKeyState(VK_RIGHT) && game.snake.direction != LEFT)
{
game.snake.direction = RIGHT;
}
else if (GetAsyncKeyState(VK_SPACE))
{
Pause();
}
else if (GetAsyncKeyState(VK_RETURN))
{
Speed(&game);
}
else if (GetAsyncKeyState(VK_ESCAPE))
{
break;
}
NextPos = GetNextPos(&(game.snake));
//蛇吃到食物的时候
if (IsEatFood(NextPos, game.Foodposition))
{
//1.蛇身子要变长
AddSnakeHead(&(game.snake), NextPos);
//2.生成食物
GenerateFood(&(game.snake), &(game.Foodposition), WIDTH, HEIGHT);
PrintFood(game.Foodposition);
//3.加分
game.score += game.Perfoodscore;
PrintScore(&game);
}
//没吃到食物时让蛇前进,不动方向键的时候蛇会自己前进(擦除蛇身子的最后一个节点并且在头部插入一个新的节点相当于蛇往前走了)
else
{
RemoveSnakeTail(&(game.snake));
AddSnakeHead(&(game.snake), NextPos);
}
//退出游戏
//撞墙
if (IsKilledByWall(NextPos, WIDTH, HEIGHT))
{
break;
}
//撞到自己
if (IsKilledBySelf(&(game.snake)))
{
break;
}
Sleep(game.Sleeptime-game.speed);
}
}
int main()
{
srand((unsigned)time(NULL));
system("mode con cols=100 lines=35");
PrintWelcome();
PrintWall(WIDTH, HEIGHT);
GameRun();
PrintDeathMessage(WIDTH, HEIGHT);
system("pause");
return 0;
}
图形界面的头文件view.h
view.h
#pragma once
#include"DataType.h"
//设置文本颜色
void Color(int color);
void Move(int X, int Y);
//打印边界墙,这里的width和height值的指的是游戏内的坐标
void PrintWall(int width, int height);
//擦除一格
void CleanBlock(Position Pos);
//打印一节蛇
void PrintSnakeBlock(Position Pos);
//打印食物
void PrintFood(Position FoodPos);
//打印整条蛇
void PrintSnake(Snake*Psnake);
//打印欢迎界面
void PrintWelcome();
//打印死亡信息
void PrintDeathMessage(int width, int height);
//打印游戏相关信息
void PrintGameInformation();
//打印得分
void PrintScore(Game*pGame);
处理游戏界面 View.c
View.c
#include<Windows.h>
#include<stdio.h>
#include"DataType.h"
#pragma comment(lib,"Winmm.lib")
#define WIDTH 28
#define HEIGHT 27
#include <mmsystem.h>
//移动光标
//COORED:The COORD structure defines the coordinates of a character cell in a console screen buffer.
//The origin of the coordinate system (0,0) is at the top, left cell of the buffer.
void Move(int X, int Y)
{
HANDLE hStdout = GetStdHandle(STD_OUTPUT_HANDLE);
COORD coord;
coord.X = X;
coord.Y = Y;
SetConsoleCursorPosition(hStdout, coord);
}
void Color(int color)
{
HANDLE consolehwnd = GetStdHandle(STD_OUTPUT_HANDLE);
SetConsoleTextAttribute(consolehwnd, color);
}
//隐藏光标
void HideCursor()
{
CONSOLE_CURSOR_INFO cursor_info = { 1, 0 };
SetConsoleCursorInfo(GetStdHandle(STD_OUTPUT_HANDLE), &cursor_info);
}
//打印墙
void PrintWall(int width, int height)
{
Color(8);
int i;
Move(0, 0);
for (i = 0; i < width + 2; i++)
{
Sleep(30);
printf("█");
}
Move(0, height + 1);
for (i = 0; i < width + 2; i++)
{
Sleep(30);
printf("█");
}
for (i = 0; i < height; i++)
{
Sleep(30);
Move(0, i + 1);
printf("█");
}
for (i = 0; i < height; i++)
{
Sleep(30);
Move(2 * (width + 1), i + 1);
printf("█");
}
}
void PrintFood(Position Food)
{
Color(FOREGROUND_RED);
Move(2*(Food.x + 1), Food.y + 1);
printf("★");
}
void CleanBlock(Position pos)
{
Move(2 * (pos.x + 1), pos.y + 1);
printf(" ");
}
void PrintSnakeBlock(Position pos)
{
Color(FOREGROUND_GREEN);
Move(2 * (pos.x + 1), pos.y + 1);
printf("●");
}
void PrintSnake(Snake*Psnake)
{
SnakeNode*Pnode;
Pnode = Psnake->Phead;
while (Pnode != NULL)
{
PrintSnakeBlock(Pnode->pos);
Pnode = Pnode->Pnext;
}
}
void PrintWelcome()
{
Move(WIDTH / 2, HEIGHT/2);
printf("Welcome to Gluttonous Snake");
HideCursor();
system("pause");
system("cls");
}
void PrintDeathMessage(int width, int height)
{
Color(FOREGROUND_BLUE | FOREGROUND_GREEN);
Move(width / 2 +7 , height / 2);
printf("Game Over");
HideCursor();
}
void PrintScore(Game*pGame)
{
Color(FOREGROUND_BLUE | FOREGROUND_GREEN);
Move(65, HEIGHT / 2 + 6);
printf("得分:score=%d", pGame->score);
HideCursor();
}
void PrintGameInformation(Game*Pgame)
{
Color(FOREGROUND_BLUE | FOREGROUND_GREEN);
Move(65, HEIGHT / 2);
printf("↑:向上移动");
Move(65, HEIGHT / 2+1);
printf("↓:向下移动");
Move(65, HEIGHT / 2 + 2);
printf("→:向右移\n");
Move(65, HEIGHT / 2 + 3);
printf("←:向左移动");
Move(65, HEIGHT / 2 + 4);
printf("规则:每个食物加10分\n");
Move(65, HEIGHT / 2 + 5);
printf("回车键可以加速");
HideCursor();
}
这里在处理游戏界面时调用了一些Windows的系统接口函数来实现,还有就是我自己加的这个BGM是没有移植性的,所以要想加上BGM需要去调用系统中播放音乐的函数PlaySound(),在函数要将你的音乐所在的路径传过去,音乐的格式是.wav的格式,我自己用mp3格式试了好久都不行才转的wav格式。
效果图: