这一步是最简单的,没错,我的习惯就是从最简单的开始。
我们的目标是画一个框框,然后这个框框里有条蛇, 还有个星星,根据这个目标,最后的代码就会书写成这样子:
void draw(int **map, int *star, int height, int weight){
// 这里有一个需要关注的地方, 那就是边界是墙,那么到时候在判定的时候,
// 我们要将墙体的大小算进到整个地图大小中,也就是height是包含了墙体了的
/* 清空屏幕 */
system("cls");
for(int i = 0; i < height; i++){
for(int j = 0; j < weight; j++){
if(map[i][j] == 1){
cout << "■"; // 蛇身和墙体
}else if(map[i][j] == 2){
cout << "●"; // 蛇首
}else if(i == star[0] && j == star[1]){
cout << "★"; // 星星
}else{
cout << "□"; // 可移动区域
}
}
cout << endl;
}
return;
}
这里,多介绍一些各个参数是什么, map是地图数组,star是星星的所在位置,height是地图的高,weight是地图的宽。
有一个细节我想你也注意到了,没错就是清空屏幕这个操作,这是为了什么要这么做呢,其实一开始是没有这个操作的,每次移动都会多画一个图在原来的图下面,这就会导致我们的地图位置不固定,影响我们的发挥,所以,我们要把这个地图固定住,这里明确一点,我们只会使用cmd这个窗口作为我们的游戏界面。
清空屏幕之后,就会开始画图,这样就会保证每次画的图都是新的。不过由于用了清屏的操作,所以会一闪一闪的,即便如此,我也玩得很嗨,没办法,原始人的快乐就是这么朴实。
不过上面的代码是完整版的代码,实际上, 如果你是从零开始搭建,开始的画图代码应该是只有地图,也就是一堵墙和地砖, 然后加上了蛇之后,画上蛇,然后有了小星星后, 画上小星星。
有个比较需要注意的细节, 由于蛇是用链表实现的,出于减少计算量的强迫症,我会先将蛇融合到地图中去,如下:
void drawSnake(int **map, struct snake snake){
struct snake *p = snake.next;
map[p->i][p->j] = 2; // 蛇头
p = p->next;
while(p){
map[p->i][p->j] = 1;
p = p->next;
}
return;
}
然后绘图时,就还是一次画完就可以了, 不用不断遍历蛇是否在某个点上,没错,强迫症真的很可怕。
以上代码你可以简单理解为 map = map + snake(当然,这个加可不是真的数值运算哈,我再偷偷提醒你一下吧。)。
也许你会问我, 那为什么不把星星也直接加到map里呢?问得好,这是因为蛇是变长的, 蛇最长能达到map的大小,假如我们不把蛇先用以上方式加到map中的话,在绘图时进行检查,最糟糕情况会是O(N*N),N=height*weight的计算复杂度,这我能忍吗?这我不能忍,直接融入地图吧, 你这个蛇儿。那星星呢?星星只是一个点,他永远都是一个点而已,按照时间复杂度的计算法来看, 都没有改变O(N),所以我就把它保留了,免得再写个函数。
没想到第一步就要知道这么多东西吧。(看到这里的都是热爱学习的好宝宝呀)。
这个地图刷新其实上面已经讲过了, 并且也说了为什么要那么做,我们就不再细讲,我们可以在绘制好地图和蛇之后做这个地图刷新,至于星星,如果你现在就想画可以直接跳到后面,不过因为星星的特殊功能,我们现在可以先不管他。
我想,动起来,是整个游戏最最最重要的事情,不会动, 有什么用呢?没错,没有任何用处。
我们先来明确运动部分的代码我们需要做的事情。
确定方向键:w:上,s:下,a:左,d:右。
确定ascii值:w:119, s:115,a:97, d:100。
ok,确定之后就可以直接撸代码了。
int move(struct snake *s, int *star){
int sign = 0;
switch(ch){
case 119:
/* 向上走 */
sign = _move(s, star, -1, 0);
break;
case 115:
/* 向下走 */
sign = _move(s, star, 1, 0);
break;
case 97:
/* 向左走 */
sign = _move(s, star, 0, -1);
break;
case 100:
/* 向右走 */
sign = _move(s, star, 0, 1);
break;
}
if(sign == 2){ // 吃到了星星
return 2;
}
/* 死亡检查 */
return check_dead(s);
}
看到这个如果你不去深究_move这个函数到底怎么做的话,我相信你还是相当清晰到底是怎么回事的,那么我就接着将_move这个函数吧。
int _move(struct snake *s, int* star, int x, int y){
/* 运动检查 */
if(!check_action(s, x, y)){
// return 0;
x = direct_x;
y = direct_y;
}
/* 修改目前运动方向 */
direct_x = x;
direct_y = y;
/* 增加新节点 */
struct snake *top = s->next;
struct snake *p = s->next;
struct snake *Ntop;
Ntop = (struct snake*)malloc(sizeof(struct snake));
Ntop->i = top->i + x;
Ntop->j = top->j + y;
s->next = Ntop;
Ntop->next = top;
/* 检查是否吃到星星 */
if(check_star(s, star) == 1){
return 2;
}
/* 删除旧节点 */
while(p->next->next != NULL){
p = p->next;
}
p->next = NULL;
return 0;
}
简单讲一下我没有提出来的代码(我也不准备后面贴出来),运动检查是看看这个输入的运动方向是否合理,比如不能倒退。还有更上面的死亡检查,就是看看是否这么走之后,蛇会不会撞死,我们知道它不能撞到自己,也不能撞到墙。
ok,这部分讲完我们开始讲上面贴出来的代码,如果方向错误的话,我们会选择使用原前进方向,也就说,如果你要倒退走的话,我们就直接照着之前的方向往前走,那么运动到底是怎么做到的,其实这里就需要你有个更加大局的看法了,我们之前把蛇融入到地图中去了,那么蛇的每个节点中的元素i,j其实代表的就是map中i,j坐标有蛇,且值是1或者2(当时蛇头时),所以,我们只需要把蛇前进方向的格子的坐标变成蛇头,变成蛇的新节点,然后删除尾节点就可以了。(这是没有吃到星星的情况下),如果吃到了星星,尾节点是不删除的,这样就可以实现长度+1。
好了,第一部分的运动我们说完了, 那么怎么做一个自动运动的蛇呢?其实这也很简单,只需要定时运动一下就好了, 前面我们知道我们有一个获取运动方向的函数,如下:
void getAction(){
int sign = 0; // 是否已输入按键
double duration; // 已检测时长
clock_t start, finish;
start = clock();
while(1){
if(_kbhit() && sign == 0){
int _ch = _getch();
if(_ch == 119 || _ch == 115 || _ch == 97 || _ch == 100){
ch = _ch;
sign = 1;
}
}else if(_kbhit() && sign == 1){
_getch();
}
finish = clock();
duration = (double)(finish - start);
if(duration > speed)
break;
}
return;
}
我们监视一定时长的键盘输入,并且将第一个有效输入保留到全局变量中的方向键ch中去,然后一直等到时间到了, 如果没有有效输入,就不做任何修改,我们会使用上次的方向键ch。这样子我们就得到了一个会执行一定时长的获取方向的模块了, 接下来就是调用这个模块的事情了,(没错,循环调用就好了, 这样就会没过一定时间获取一次,然后获取后运动,也就可以实现一段时间运动一次的目的了)。
一开始是想用多线程的, 后来发现定时的方式更加稳定且简单(毕竟多线程是一个很麻烦的东西,弱鸡伤不起)。
####4. 生成星星
生成星星的方式可以采用随机生成坐标的方式,然后检查是否会不会撞墙,也可以采用我代码中的方式,时间复杂度是相同的。
/* 生成星星 */
void generateStar(int **map, int *star, int height, int weight){
int sum = 0;
int index = 0;
// 计算有多少空格
for(int i = 0; i < height; i++)
for(int j = 0; j < weight; j++)
if(map[i][j] == 0)
sum += 1;
// 随机生成空格下标
index = random(sum);
// 选中index个空格作为星星的生成位置
for(int i = 0; i < height; i++)
for(int j = 0; j < weight; j++){
if(map[i][j] == 0)
index -= 1;
if(index == 0){
star[0] = i;
star[1] = j;
cout << i << " " << j << endl;
return;
}
}
return;
}
增加蛇长上面已经有讲到了,就是吃到星星后, 头部增加,尾部不删,就可以实现增加蛇长。
其实这个上面也已经有提到了。
github: https://github.com/iajqs/pratice-c/tree/master/snake