哈哈,巴拉巴拉一堆,终于可以讲一讲自己是怎么用c语言实现贪吃蛇。
每一个好的想法都得需要一个合适的平台。同样平台也决定了你会有怎样的上下限。
例如,python的pygame就很强,实现的贪吃蛇就会更美观点。当然c语言的话。我使用的是turbo C(意思是涡轮增压后的C)。似不似很强力?可惜我用的是2.0版本(1989年),不支持鼠标操作。都0202年了,不能用鼠标玩电脑,也是个脑力活。
注意点:
1.turbo C 中int是两字节。
2.个人写的.h文件需要放出D:\DISK_C\TC20\INCLUDE 文件夹里。
3.由于运行过快,需要使用system(“pause”); 语句或者getchar(); 语句来接收下一个用户命令。此时才能看到自己的输出。但是还得提前使用clrscr();语句用来清空屏幕。需要头文件stdlib.h。
任务要求: 在turbo c 上实现hello world 和 简单排序。
大家觉得制作游戏第一步干嘛?是确定核心玩法?还是剧情感人?这些都很重要,但都绕不开显示(即输出)。关了门,不给别人打开一扇窗户,永远不会有人走进你的世界。
贪吃蛇,贪吃蛇,起码得显示是个蛇吧。来看看我的snake!
呸呸呸,我的蛇怎么可能这么花里胡哨(美丽动人)。你看我的蛇,又长又灵活,还带着指导员。
很简陋,但确实是个人自己实现的。我希望初学者一样可以通过自己实现这个游戏。因为复制再多的代码也不能得到别人的经历。所以我更倾向于在本篇文章中讲思路,讲自己的经验。代码不难,学习不易。
这里使用的是gotoxy(int ,int)函数。在这个显示器是25 * 80的小小屏幕上,(从一开始)。使用这个函数就能将光标指哪打哪。但是得注意第一参数是列坐标,也就是xy坐标中的y坐标。到了位置之后,就可以让那里显示东西了。例如printf()函数输出“ * ”号。代表蛇身。如此如此,我们就实现了输出这一难关。
不讨论输入输出谁更关键。实现才是最重要的。
我喜欢输入,因为我感受到自己的想法的实现。
这里我们就得介绍bioskey()函数。简单来说,这个函数就是实现用户控制。当用户输入上下左右是,屏幕会显示蛇向相应方向走动。这是我认为的交互式输入输出。
具体关于bioskey(),可以看我的另一篇文章。
链接:bioskey()函数使用
要点:
1.输出得掌握好gotoxy()函数,记住第一个实参是列坐标,第二个是行指标。
任务: 在屏幕指定16行,24列处输出&号。
2.输入得掌握好bioskey()函数。
任务: 掌握bioskey()的参数0、1和2的不同作用。
什么是动画?还记将不同画像快速翻阅带来的视觉动态吗?眼睛有时候传递的信息,是错误的。
顾名思义:就是一个“ * ”号在一行屏幕移动。
这个不难。主要由两个步骤:
1)显示移动后的星号。
2)将原先显示的星号删除。
关键代码就是:行坐标的增加或者减少。
想要星号向上,向下,想左,向右移动,这是就需要键盘输入控制了。
我们知道,
一般的想法是当我们得到输入时,通过得到的值再将星号的行、列坐标改变。但是通过图片我们发现规律。可以将行、列变化值写成一个数据类型是结构体的数组。如下:
typedef struct DEL_POSITION {
int deltaRow;//行改变值
int deltaCol;//列改变值
char snakeHand;//蛇头形状
}DEL_POSITION;//方向
使用时:
DEL_POSITION delPos[4] = {
{
-1, 0, '^'},//up--0
{
1, 0, 'v'},//down--1
{
0, 1, '>'},//right--2
{
0, -1,'<'},//left--3
};
使用bioskey()函数;
#define UP 0x4800
#define DOWN 0x5000
#define LEFT 0x4b00
#define RIGHT 0x4d00
#define ESC 0x011B
这里只要将bioskey函数使用好,准确得到键盘输入,配合坐标的变化和移动的实现就能实现多方向移动。
这时候一定要明白,有关蛇头蛇身体的关键性数据。例如蛇长,蛇移动方向等等。
我是这样定义的:(有一些数据暂时可用不到,可以先跳过)
typedef struct SNAKE_BODY {
u8 col;//蛇身体列坐标
u8 row;//蛇身体行坐标
}SNAKE_BODY;
typedef struct SNAKE {
boolean finished;//结束判断
boolean died;//死亡判断
int head;//指向蛇头的指针
int tail;//指向蛇尾的指针
SNAKE_BODY body[MAX_COUNT];//蛇身数量
u8 directe;//蛇行进方向
double time;//控制蛇速度
u8 realSnakeLen;//真正的蛇长
u8 curSnakeLen;//当前的蛇长
FOOD food[1];//生成的食物
}SNAKE;//蛇的控制元素
首先说一说到底用什么来表示定长蛇。一般有两种想法。
1)地图法: 将整个屏幕看成一个有行列坐标的二维数组。没有蛇身存在的点都是0,有蛇身存在的是1,在扫描数组,就为1的地方显示星号,那么就可以将这条贪吃蛇 “画出来”
2)蛇身数据法: 一般采用将蛇身的行列坐标用数组保存下来。每次“画蛇”是根据蛇身数组将蛇画出来。
明白了蛇身的表示,那么要模拟出蛇的移动。重点来了
蛇是如何移动的?
蛇的移动从蛇身整体数据来说,只做了三个步骤。
第一:蛇头移动一格,得到新的蛇头数据。画出蛇头的方向感。
第二:旧的蛇头数据处画出星号。
第三:将旧的蛇尾销毁。
如图:
所以如果每次只关注:旧蛇头、新蛇头和旧蛇尾位置,就可以将蛇的移动展现出来,而且不用管其他蛇身数据。
同时,问题来了:采用普通数组的话,需要删除和复制蛇身数据,很是麻烦。而且蛇的轨迹都被记录下来的话,数组长度得很长很长。
所以对于定长蛇,采用的是循环数组。具体知识暂无。
基本步骤如下:
1)在数组空间中,当蛇移动后,蛇头指针、蛇尾指针自动加一,蛇长不变。
2)当头尾指针遇到最大空间值时,又从下标为0的空间开始。
实现:head = (head + 1) % MAX_COUNT;
蛇的显示是二维化的,但是蛇的数据是一维的。我称为蛇轨迹一维化
图片说明:上面的一维数组,head是蛇头指针,tail为蛇尾指针。空的是轨迹但也是垃圾数据。
这样做的好处很多:
1)数组空间循环使用,不用申请太大空间。
2)当定长蛇增长的,只要不超过申请空间,都可以显示下来。例如我申请200空间,那么玩家需要将蛇长到200.
3)使用头尾指针可以配合循环数组特点。又可以更具头尾指针得到当前蛇的数据。
先将食物的产生放一放,怎么使定长蛇增长?
其实也不难。
就相当于定长蛇的移动后,旧蛇尾不要删除。注意:此时蛇尾指针依然指向旧蛇尾处
所以到此,蛇的移动就实现了。等等,似乎还有一个知识点:
蛇刚产生时,假设蛇长是3,我们从蛇头开始,一个点。如何实现一个点(盘起来的蛇)变成一个舒展开的蛇?
这是就用上了: u8是unsigned char类型
u8 realSnakeLen;//真正的蛇长
u8 curSnakeLen;//当前的蛇长
效果是:
在输出前加一个判断,如果当前的蛇长小于真正的蛇长,那么就不删除蛇尾。
如果当前的蛇长大于等于真正蛇长,删除蛇尾。
这时候,如果蛇吃了食物,那么真正蛇长就会加一。从而不会通过判断,不删除蛇尾。
double time;//控制蛇速度
由于程序运行是神快的,所以我们在游戏循环中加入一个限制时间,
while (!snake.finished && !snake.died) {
//不死也不结束
delayTime ++;//延迟时间
snake.time = LIMITETIME;//限制的时间
if (delayTime >= snake.time) {
//只有当延迟时间到达限制时间时,小蛇才移动
...}
同时,我们通过修改限制时间,就可以修改小蛇的速度。而且可以根据用户输入来修改小蛇速度。这个不难。
C语言一般都会知道,随机数的生成。但是由于随机种子的问题,其实这样就形成了伪随机。而且,食物被吃后,那么该食物需要被清理。所以最初想法是:
snake->food[0].row = rand() % 25 + 1;//1--25范围
snake->food[0].col = rand() % 80 + 1;//1--80范围
draw(snake->food[0].row, snake->food[0].col);//生成
snake->food[0].eat = FALSE;//标记:未吃
但是,怎么说吧,如果可以实现随机的话,为啥用伪随机?
洗牌算法简介的话,暂无;
使用洗牌算法就是产生一个随机数池子。每一个数出现在这个随机数池子的概率都相等。
同时,将80*25的屏幕的行列坐标值,看成一个数值,即从0–1999;那么每一数值,可以转化为一个行列坐标。
如:1 => row = 1;col = 2;
25 => row = 2; col = 1;
所以通过洗牌算法生成得随机数,是随机的。再将之转变为随机食物坐标。那么生成得食物就是随机的。
t为随机数
snake->food[0].row = t / 80 + 1;//转化的行坐标
snake->food[0].col = t % 80 + 1;//转化的列坐标
draw(snake->food[0].row, snake->food[0].col);//生成
snake->food[0].eat = FALSE;//标记:未吃
目前视频仅支持三种,上传不了o(╥﹏╥)o。
不过我确实没实现太多功能。不够美观。
这不是一个完善的贪吃蛇游戏,界面,游戏体验都不好。简陋。但却是个人第一个自制小游戏。(▽)
笔者水平有限,目前只能描述以上问题,如果有其他情况,可以留言,有错误,请指教,有继续优化的,请分享,谢谢!
完整代码如下:
snake.h
#ifndef _STICKER_SNAKE_H
#define _STICKER_SNAKE_H
typedef unsigned char boolean;
typedef boolean u8;
typedef struct DEL_POSITION {
int deltaRow;/*行改变值*/
int deltaCol;/*列改变值*/
char snakeHand;/*蛇头形状*/
}DEL_POSITION;/*方向*/
#define MAX_COUNT 100
#define NUM_COUNT 2000 /*25 * 80*/
#define TRUE 1
#define FALSE 0
#define LIMITETIME 3000
#define TIME 1000
#define UP 0x4800
#define DOWN 0x5000
#define LEFT 0x4b00
#define RIGHT 0x4d00
#define ESC 0x011B
#define UPSPEED 0x4f31
#define LOWSPEED 0x5032
#define RETURNSPEED 0x5133
#define YES 0x1579
#define NO 0x316E
typedef struct SNAKE_BODY {
u8 col;/*蛇身体列坐标*/
u8 row;/*蛇身体行坐标*/
}SNAKE_BODY;
typedef struct FOOD {
u8 col;
u8 row;
u8 eat;
}FOOD;
typedef struct SNAKE {
boolean finished;/*结束判断*/
boolean died;/*死亡判断*/
int head;/*指向蛇头的指针*/
int tail;/*指向蛇尾的指针*/
SNAKE_BODY body[MAX_COUNT];/*蛇身数量*/
u8 directe;/*蛇行进方向*/
double time;/*控制蛇速度*/
u8 realSnakeLen;/*真正的蛇长*/
u8 curSnakeLen;/*当前的蛇长*/
FOOD food[1];/*生成的食物*/
}SNAKE;
#endif
snake.c
#include
#include
#include
#include
#include "SNAKE.H"
void draw(u8 row, u8 col);
void getEvent(u8 oldEvent, SNAKE *snake);
void startGame();
void move(SNAKE *snake);
void isEnd(SNAKE *snake);
void dealEnd(SNAKE *snake);
void initFood(SNAKE *snake);
void eatFood(SNAKE *snake);
int creatRandNum();
/*洗牌出随机数*/
int creatRandNum() {
int numPool[NUM_COUNT];
int t;
int temp;
int count = NUM_COUNT;
static boolean isCreatFoodPool = FALSE;
static int food = 0;
/*洗牌算法*/
if (isCreatFoodPool == FALSE) {
for (temp = 0; temp < count; temp++) {
numPool[temp] = temp;
}
for (; count > 1; count--) {
srand(time(NULL));
t = rand() % count;
temp = numPool[t];
numPool[t] = numPool[count - 1];
numPool[count - 1] = temp;
}
isCreatFoodPool = TRUE;
}
return numPool[food++];
}
/*食物是否被吃?*/
void eatFood(SNAKE *snake) {
if (snake->body[snake->head].row == snake->food[0].row
&& snake->body[snake->head].col == snake->food[0].col) {
snake->realSnakeLen++;
snake->food[0].eat = TRUE;
}else {
snake->food[0].eat = FALSE;
}
}
/*生成食物*/
void initFood(SNAKE *snake) {
int t;
int i;
if (TRUE == snake->food[0].eat) {
t = creatRandNum();
for (i = snake->tail;(i % MAX_COUNT) != snake->head; i++) {
if ((snake->body[snake->tail].row - 1) * 80 + snake->body[snake->tail].col - 1 == t
|| (snake->body[snake->head].row - 1) * 80 + snake->body[snake->head].col - 1 == t) {
t = creatRandNum();/*如果食物产生在蛇身上,重新产生*/
}
}
snake->food[0].row = t / 80 + 1;
snake->food[0].col = t % 80 + 1;
draw(snake->food[0].row, snake->food[0].col);
snake->food[0].eat = FALSE;
}
}
/*解决死亡情况*/
void dealEnd(SNAKE *snake) {
if (TRUE == snake->died) {
clrscr();
gotoxy(32, 12);
printf("you are died!");
}
if (TRUE == snake->finished) {
clrscr();
gotoxy(32, 12);
printf("goodbye!");
}
}
/*解决结束情况*/
void isEnd(SNAKE *snake) {
int i;
if (snake->body[snake->head].row < 1 || snake->body[snake->head].row > 25
|| snake->body[snake->head].col < 1 || snake->body[snake->head].col > 80) {
snake->died = TRUE;
}
/*erdogicSnake(snake);*/
for (i = snake->tail ; (i % MAX_COUNT) != snake->head; i++) {
if (snake->body[snake->head].row == snake->body[i].row
&& snake->body[snake->head].col == snake->body[i].col) {
snake->died = TRUE;
}
}
}
/*移动*/
void move(SNAKE *snake) {
DEL_POSITION delPos[4] = {
{
-1, 0, '^'},/*up*/
{
1, 0, 'v'},/*down*/
{
0, 1, '>'},/*right*/
{
0, -1,'<'},/*left*/
};
if (snake->curSnakeLen >= snake->realSnakeLen) {
gotoxy(snake->body[snake->tail].col, snake->body[snake->tail].row);
printf(" ");
snake->tail = (snake->tail + 1) % MAX_COUNT;
}else {
snake->curSnakeLen++;
}
draw(snake->body[snake->head].row, snake->body[snake->head].col);/*旧蛇头变星号*/
snake->body[((snake->head + 1) % MAX_COUNT)].row = snake->body[snake->head].row + delPos[snake->directe].deltaRow;/*生成新蛇头行坐标*/
snake->body[((snake->head + 1) % MAX_COUNT)].col = snake->body[snake->head].col + delPos[snake->directe].deltaCol;/*生成新蛇头列坐标*/
snake->head = (snake->head + 1) % MAX_COUNT;
gotoxy(snake->body[snake->head].col, snake->body[snake->head].row);
printf("%c", delPos[snake->directe].snakeHand);/*画出新蛇头*/
}
/*开始游戏*/
void startGame() {
SNAKE snake = {
FALSE, FALSE, 0, 0, {
{
10,10} }, 2, LIMITETIME, 3, 1, {
0, 0, TRUE},
};
u8 oldEvent;
double delayTime = 0;
draw(snake.body[snake.head].row, snake.body[snake.head].col);
initFood(&snake);
while (!snake.finished && !snake.died) {
delayTime ++;
snake.time = LIMITETIME;
if (delayTime >= snake.time) {
if (bioskey(1) != 0) {
oldEvent = snake.directe;
getEvent(oldEvent, &snake);/*处理键盘输入*/
}
eatFood(&snake);
if (TRUE == snake.food[0].eat) {
initFood(&snake);/*处理是否吃食物*/
}
move(&snake);
delayTime = 0;
}
isEnd(&snake);
}
dealEnd(&snake);
}
/*键盘输入*/
void getEvent(u8 oldEvent, SNAKE *snake) {
double newEvent;/*新的控制*/
newEvent = bioskey(0);/*接受键盘输入*/
if (UP == newEvent && oldEvent != 1) {
snake->directe = 0;/*向上*/
}else if (DOWN == newEvent && oldEvent != 0) {
snake->directe = 1;/*向下*/
}else if (RIGHT == newEvent && oldEvent != 3) {
snake->directe = 2;/*向右*/
}else if (LEFT == newEvent && oldEvent != 2) {
snake->directe = 3;/*向左*/
}else if (ESC == newEvent) {
snake->finished = TRUE;/*退出*/
}else if (UPSPEED == newEvent) {
snake->time -= TIME;/*加速*/
}else if (LOWSPEED == newEvent) {
snake->time += TIME;/*减速*/
}else if (RETURNSPEED == newEvent) {
snake->time = LIMITETIME;/*恢复正常速度*/
}else {
snake->directe = oldEvent;/*没有键盘输入,按照原来情况进行*/
}
}
/*显示*/
void draw(u8 row, u8 col) {
gotoxy(col ,row);
printf("*");
}
int main() {
clrscr();
startGame();
getchar();
return 0;
}
2020年02.26 家