最近将STL源码剖析、linux编程搁置了一段时间,简单做了个C语言版的贪吃蛇
涉及到的知识点如下:
1、随机数 srand((unsigned)time(NULL))种子、获取随机数rand()%100
2、enum枚举体变量
3、volatile关键字
4、BOOL关键字与C++中的bool关键字比较
5、sprintf(str,"…");str的长度要保证能够容纳后面的字符串,否则报错如下
6、文件操作fopen()、fscanf()
7、单链表的头插法与尾删除(本程序使用的是不带头结点的单链表)
8、window下的一下操作
9、多文件编程 #pragma once防止头文件重复包含
====================================================
模块分类:食物、蛇、随机数(食物位置)、地图加载
使用的全局宏定义如下 global.h
游戏设计介绍:(速度的快慢通过Sleep()实现)
路径 “ ” 1 MAP_ROAD
蛇身 □ 2 MAP_BODY
蛇头 ○ 3 MAP_HEAD
正常食物 ▲ 4 MAP_FOOD_NORMAL(吃了以后蛇长+1)
有毒食物 ★ 7 MAP_FOOD_SHORTEN(吃了以后蛇长-1,速度减慢)
致幻食物 ◆ 6 MAP_FOOD_ACCELERATE(吃了以后蛇长+1,速度加快)
障碍物 ╬ 5 MAP_OBSTACLE
注:由于代码设计问题,目前只能对宽和长相同的二维数组地图进行良好测试
补:补一个测试用例的二维数组(30为宽和高 1为蛇可以行走的路径 5代表站爱物)
30 30
1 1 1 1 1 1 1 1 5 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 5 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 5 1 1 1 1 1
1 1 1 1 1 1 1 1 5 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 5 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 5 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 5 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 5 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 5 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 5 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 5 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 5 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 5 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 5 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
用到的全局宏定义 global.h
#pragma once
#define TRUE 1
#define FALSE 0
#define OTHER -1
#define INIT_SNACK_LEN 4 //初始蛇长度
#define VICTORY_SNACK_LEN 10 //过关时要求的长度
#define MAX_LENGTH 40 //路径字符串数组长度(如果你的路径过长,把这个数据改大一点)
#define CONSOLE_MAX_WIDTH 80 //控制台最大宽度
#define DELAY_TIME 500 //延时
#define DELAY_TIME_ACCELERATE 100 //吃了致幻食物的延时
#define DELAY_TIME_SHORTEN 1000 //吃了有毒食物的延时
地图 map.h
#pragma once
#include "global.h"
//地图路径、蛇头、蛇身、正常食物、障碍物、致幻食物、有毒食物
enum EMap{
MAP_ROAD=1,
MAP_BODY,
MAP_HEAD,
MAP_FOOD_NORMAL,
MAP_OBSTACLE,
MAP_FOOD_ACCELERATE,
MAP_FOOD_SHORTEN,
};
enum EGameStatus{
GAME_LOOP, //循环
GAME_VICTORY, //胜利
GAME_FAILURE //失败
};
int width, height; //地图长宽
volatile enum EGameStatus status; //游戏状态
int map[MAX_LENGTH][MAX_LENGTH];
int mapTemp[MAX_LENGTH][MAX_LENGTH];
int selectNum; //所选关卡
//地图预处理
void PreviewMap();
//载入地图
void LoadMap(int scene);
//地图显示(多线程)
void DisplayMap();
map.c
#define _CRT_SECURE_NO_WARNINGS
#include "global.h"
#include "snack.h"
#include "food.h"
#include "map.h"
#include
//#include
#include
static FILE* fp = NULL;
volatile enum EGameStatus status = GAME_LOOP;
//加载地图
void LoadMap(int scene){
int i, j;
char str[MAX_LENGTH] = "";
sprintf(str, "D:\\Code\\project\\2_game1\\Map\\%d.txt", scene); //TODO...
//puts(str);
fp = fopen(str, "r");
fscanf(fp, "%d%d", &width, &height); //从文件中读取地图的宽和高
for (i = 0; i < height; ++i){
for (j = 0; j < width; ++j){
fscanf(fp, "%d", &map[j][i]); //读取地图的信息(一列一列的读取)
mapTemp[j][i] = map[j][i]; //存储
printf("%d ", map[j][i]);
}
printf("\n");
}
fclose(fp);
fp = NULL;
}
//预处理地图
void PreviewMap(){
int i, j;
for (i = 0; i < height; ++i){
for (j = 0; j < width; ++j){
map[i][j] = mapTemp[i][j];
}
}
//设置蛇头位置
Snack* pSnack = pHeader;
map[pSnack->x][pSnack->y] = MAP_HEAD; //3
pSnack = pSnack->pNext;
//设置蛇身位置
while (pSnack){
map[pSnack->x][pSnack->y] = MAP_BODY; //2
pSnack = pSnack->pNext;
}
//设置食物(类型)
map[food.x][food.y] = food.foodKind;
}
//显示地图
void DisplayMap(){
int i, j;
system("cls");
printf(" 贪吃蛇项目\n");
for (i = 0; i < height; ++i){
for (j = 0; j < width; ++j){
if (i == 0 || j == 0){ //二维数组
printf("回");
continue;
}
switch (map[j][i])
{
case MAP_ROAD: //路 1
printf(" ");
break;
case MAP_BODY: //蛇身 2
printf("□");
break;
case MAP_HEAD: //蛇头 3
printf("○");
break;
case MAP_FOOD_NORMAL: //正常食物 4
printf("▲");
break;
case MAP_FOOD_ACCELERATE://致幻食物 6
printf("◆");
break;
case MAP_FOOD_SHORTEN: //有毒食物 7
printf("★");
break;
case MAP_OBSTACLE: //障碍物 5
printf("╬");
break;
}
}
printf("回"); //地图右边边界
switch (i){
case 3:
printf("\t您的得分是:%d", snackLength);
break;
case 7:
printf("\t游戏过关需要得分10");
break;
case 8:
printf("\t目前蛇长:%d", snackLength);
break;
case 9:
printf("\t距过关还需要:%d",10-snackLength);
break;
case 13:
switch (food.foodKind){
case MAP_FOOD_NORMAL:
printf("\t出现的食物为:正常食物!");
break;
case MAP_FOOD_ACCELERATE:
printf("\t出现的食物为:致幻食物!");
break;
case MAP_FOOD_SHORTEN:
printf("\t出现的食物为:有毒食物!");
break;
default:
break;
}
break;
case 14:
switch (food.foodKind){
case MAP_FOOD_NORMAL:
printf("\t效果:蛇身+1!");
break;
case MAP_FOOD_ACCELERATE:
printf("\t效果:蛇身+1,且加速!");
break;
case MAP_FOOD_SHORTEN:
printf("\t效果:蛇身-1,且减速!");
break;
default:
break;
}
break;
default:
break;
}
printf("\n"); //一行打印完毕
}
for (j = 0; j <= width; ++j) //地图下边边界
printf("回");
printf("\n");
}
蛇 snack.h
#pragma once
enum Edirection{
SNACK_UP, //W
SNACK_LEFT, //A
SNACK_DOWN, //S
SNACK_RIGHT //D
};
typedef struct Snack{
int x; //对应于二维数组的坐标
int y;
struct Snack* pNext;
}Snack;
//蛇吃食物后的状态
enum ESnakeStatus{
SnakeNormal = 1, //正常
SnakeShorten, //有毒
SnakeAccelerate //兴奋
};
int snackLength; //蛇的长度
Snack *pHeader, *pTail; //蛇头、蛇尾
enum Edirection direction; //蛇的移动方向
enum ESnakeStatus SnakeStatus; //蛇吃食物后的状态
//初始化蛇
Snack* SnackInit();
//链表蛇移动(多线程)
void SnackMove();
//头插法链表增长
Snack* SnackInsert();
//某个关卡游戏的结束
void SnackDestory();
void SnackNormalFn(); //正常食物
Snack* SnackShorten(); //有毒食物、缩短
void SnackAccelerate(); //致幻食物,加速
snack.c
蛇移动分为两种情况:吃到食物与未吃到食物
蛇移动(不吃食物)的处理思想:
蛇移动(吃食物)的处理思想
#include "global.h"
#include "snack.h"
#include "map.h"
#include "food.h"
#include
#include
//蛇的初始化
Snack* SnackInit(){
direction = SNACK_RIGHT; //初始时蛇向右移动
snackLength = INIT_SNACK_LEN; //初始蛇长4
int cnt = INIT_SNACK_LEN; //当前蛇长
SnakeStatus = SnakeNormal; //蛇的状态为正常
//蛇头创建
pHeader = pTail = (Snack*)malloc(sizeof(Snack));
pTail->x = cnt; //蛇头的初始坐标(4,1)
pTail->y = 1;
//蛇身创建(尾插法) --长度为3
while (cnt-- > 1){
pTail->pNext = (Snack*)malloc(sizeof(Snack));
pTail=pTail->pNext; //pTail始终指向蛇尾(链表尾节点)
pTail->x = cnt; //蛇身的坐标(cnt,1)
pTail->y = 1;
}
pTail->pNext = NULL;
return pHeader;
}
//蛇头判定事件--返回TRUE表示蛇头可以向预定的方向前进,反之不可
static BOOL SnackJudge(int x, int y){
Snack* pSnack = pHeader;
if (x >= width || x <= 0 || y >= height || y <= 0) //蛇头撞墙
return FALSE;
switch (map[x][y]){
case MAP_ROAD:
return TRUE;
case MAP_BODY: //蛇头与蛇身是否有碰撞
//退出while的情况:
while (pSnack && (pSnack->x != x || pSnack->y != y))
pSnack = pSnack->pNext;
if (!pSnack) //蛇头的预定位置与现在蛇的全部位置不重合,则可以移动
return TRUE;
return FALSE; //预定位置与蛇目前有重合,不可移动
case MAP_FOOD_NORMAL: //蛇头的预定位置为正常食物
SnackNormalFn();
FoodRelease(); //此处实际新建了食物,等待下次重绘地推
return OTHER;
break;
case MAP_FOOD_ACCELERATE:
SnackAccelerate();
FoodRelease();
return OTHER;
break;
case MAP_FOOD_SHORTEN:
SnackShorten();
FoodRelease();
return OTHER;
break;
case MAP_OBSTACLE:
default:
return FALSE;
}
}
//蛇移动(多线程)
void SnackMove(){
BOOL result;
Snack* pSnack = NULL;
int x = pHeader->x, y = pHeader->y, newX = x, newY = y;;
switch (direction){
case SNACK_UP:
result = SnackJudge(x, y - 1);
if (result == TRUE)
newY = y - 1;
break;
case SNACK_LEFT:
result = SnackJudge(x-1, y);
if (result == TRUE)
newX = x - 1;
break;
case SNACK_DOWN:
result = SnackJudge(x, y + 1);
if (result == TRUE)
newY = y + 1;
break;
case SNACK_RIGHT:
result = SnackJudge(x+1, y );
if (result == TRUE)
newX = x + 1;
break;
}
//蛇不吃食物时的移动,因为蛇移动时遇到食物,result都等于OTHER
if (result == TRUE){
Snack* pTemp = pHeader;
pTail->pNext = pHeader; //尾节点前移
//map[pTail->x][pTail->y] = MAP_ROAD; //尾节点原来的位置变为路径
while (pTemp->pNext != pTail) //寻找尾节点的前一个节点
pTemp = pTemp->pNext;
pTemp->pNext = NULL; //pTemp指向尾节点
pHeader = pTail; //pHeader重新指向头节点
pHeader->x = newX; //设置蛇头的位置
pHeader->y = newY;
map[pTail->x][pTail->y] = MAP_ROAD; //尾节点原来的位置变为路径
pTail = pTemp;
}
else if (result == FALSE)
status = GAME_FAILURE;
}
//头插法链表增长
Snack* SnackInsert(){
Snack* pSnack = (Snack*)malloc(sizeof(Snack));
pSnack->x = food.x;
pSnack->y = food.y;
pSnack->pNext = pHeader; //
pHeader = pSnack; //pHeader始终指向蛇头
if (++snackLength == VICTORY_SNACK_LEN) //判断是否达到10(过关)
status = GAME_VICTORY;
return pHeader;
}
//正常食物作为新的蛇头,蛇尾不动,即可蛇增长
void SnackNormalFn(){
SnackInsert();
SnakeStatus = SnakeNormal;
}
Snack* SnackShorten(){
if (snackLength <= 2){ //长度<=2,游戏失败
status = GAME_FAILURE;
return pHeader;
}
Snack* pTempHeader = pHeader;
Snack* pTempTail = pTail;
//Snack* pTempBehTail =pTail;
//删除蛇的最后两个节点
while ((pTempHeader->pNext->pNext != pTempTail)) //寻找蛇尾:尾节点
pTempHeader = pTempHeader->pNext;
pTail = pTempHeader; //新尾节点
pTempHeader = pTempHeader->pNext; //新尾节点的后一个节点
pTail->pNext = NULL; //实现蛇前进并剪短2个蛇长
//将食物作为新的头结点(利用原链表的倒数第二个节点,修改数据即可,避免重复申请开辟空间)
pTempHeader->x = food.x;
pTempHeader->y = food.y;
pTempHeader->pNext = pHeader;
pHeader = pTempHeader;
//释放掉删除的节点的空间
//free(pTempHeader);
free(pTempTail);
--snackLength;
SnakeStatus = SnakeShorten; //修改蛇当前的状态为变短
return pHeader;
}
void SnackAccelerate(){
SnackInsert();
SnakeStatus = SnakeAccelerate; //修改蛇当前的状态
}
//蛇的销毁
void SnackDestory(){
Snack* pSnack = pHeader;
while (pHeader){
pHeader = pHeader->pNext;
free(pSnack);
pSnack = NULL;
}
pTail = NULL;
}
食物 food.h
#pragma once
#include "map.h"
typedef struct Food{
int x; //(x,y)事务的出现位置
int y;
enum EMap foodKind; //食物类型
}Food;
Food food;
//创建食物
Food FoodCreate();
//食物被吃掉
void FoodRelease();
food.c
#include
#include "global.h"
#include "random.h"
#include "food.h"
#include "map.h"
static BOOL bIsExisted = FALSE; //地图中是否存在食物
//确定食物种类
enum EMAP FoodKindFn(){
int xx = GetRandomNumber(1, 10);
if (xx > 0 && xx <= 8)
return MAP_FOOD_NORMAL;
else if (xx == 9)
return MAP_FOOD_ACCELERATE; //致幻食物
else
return MAP_FOOD_SHORTEN; //有毒食物
}
//创建食物
Food FoodCreate(){
int x, y;
BOOL result;
//因为要不断的刷屏(system("cls"),所以会可能再次显现原来的食物(位置以及是否有毒)
if (bIsExisted)
return food; //原来的食物(位置、种类)
//将食物刷新到路径之上
do{
result = TRUE;
x = GetRandomNumber(0, width - 1);
y = GetRandomNumber(0, height - 1);
if (map[x][y] != MAP_ROAD || x == 0 || y == 0) //刷新的食物不合规范
result = FALSE;
} while (!result);
bIsExisted = TRUE; //食物位置已更新
food.x = x;
food.y = y;
food.foodKind = FoodKindFn(); //获取更新后的食物的种类
return food;
}
void FoodRelease(){
if (bIsExisted){
bIsExisted = FALSE; //本轮食物销毁,但屏幕中还未刷新
FoodCreate();
}
}
随机数 random.h
#pragma once
//初始化随机数系统
void InitRandomSystem();
//产生(leftVal, int rightVal)之间的随机整数
int GetRandomNumber(int leftVal, int rightVal);
ramdom.c
#include "global.h"
#include "random.h"
#include
#include
#include
#include
static BOOL bIsInit = FALSE; //本文件内使用
void InitRandomSystem(){
if (!bIsInit){
time_t t;
bIsInit = TRUE;
srand((unsigned)time(&t));
}
}
//获取一个随机数
int GetRandomNumber(int leftVal, int rightVal){
return rand() % (rightVal - leftVal + 1) + leftVal;
}
主函数 main.c 此中创建了一个子线程处理用户输入
#define _CRT_SECURE_NO_WARNINGS
#include "food.h"
#include "global.h"
#include "map.h"
#include "random.h"
#include "snack.h"
#include
#include
#include //线程
#include //键盘输入
void InitGame(int n){
LoadMap(n); //加载地图
SnackInit(); //蛇初始化
//PreviewMap(); //预处理地图
FoodCreate(); //创建食物
PreviewMap(); //预处理地图
DisplayMap(); //显示地图
}
//子线程处理键盘的输入
void MainLoop(void* param){
char ch;
while (status == GAME_LOOP){
ch = _getch();
switch (ch)
{
case 'w':
case 'W':
direction = SNACK_UP; //向上
break;
case 'a':
case 'A':
direction = SNACK_LEFT;
break;
case 's':
case 'S':
direction = SNACK_DOWN;
break;
case 'd':
case 'D':
direction = SNACK_RIGHT;
break;
default:
break;
}
}
}
//游戏失败,打印游戏结束界面
void Failure(){
int i, tmp;
system("cls");
printf("\n\n\n");
for (i = 0; i < CONSOLE_MAX_WIDTH; ++i)
printf("#");
tmp = CONSOLE_MAX_WIDTH - 30;
for (i = 0; i < tmp / 2; ++i)
printf(" ");
printf("抱歉,你失败了!请再次开启游戏!\n");
for (i = 0; i < CONSOLE_MAX_WIDTH; ++i)
printf("#");
system("pause");
}
//打印胜利过关界面
void VictoryFn(){
int i, tmp;
system("cls");
printf("\n\n\n");
for (i = 0; i < CONSOLE_MAX_WIDTH; ++i)
printf("#");
tmp = CONSOLE_MAX_WIDTH - 30;
for (i = 0; i < tmp / 2; ++i)
printf(" ");
printf("恭喜你,顺利进入下一关!\n");
for (i = 0; i < CONSOLE_MAX_WIDTH; ++i)
printf("#");
Sleep(5000);
}
int main(){
HANDLE hThread;
hThread = (HANDLE)_beginthread(MainLoop,0, NULL);//创建一个子线程
int selectNum = 1;
InitRandomSystem(); //初始化随机数系统
AA:
InitGame(selectNum); //根据随机选择的关卡初始化游戏
while (status == GAME_LOOP){ //游戏一直执行
switch (SnakeStatus){
case SnakeShorten:
Sleep(DELAY_TIME_SHORTEN); //显示完地图后,停顿DELAY_TIME_SHORTEN时间
SnackMove();
break;
case SnakeNormal:
Sleep(DELAY_TIME);
SnackMove();
break;
case SnakeAccelerate:
Sleep(DELAY_TIME_ACCELERATE);
SnackMove();
break;
default:
SnackMove();
break;
}
PreviewMap();
DisplayMap();
}
if (status == GAME_FAILURE){
Failure();
SnackDestory();
}
else if (status == GAME_VICTORY){
VictoryFn();
status = GAME_LOOP;
selectNum = selectNum % 3 + 1;
goto AA;
}
}