zoukankan      html  css  js  c++  java
  • C语言 贪吃蛇

    Youzg Logo

    贪吃蛇(单人版):

    实现过程:

    本人先来介绍一个函数 —— bioskey函数:
    int bioskey (int cmd)

    参数 (cmd) 基本功能
    0 返回下一个从键盘键入的值(若不键入任何值,则将等下一个键入)它返回一个16位的二进制数,包括两个不同的值:1.当按下一个普通键时,它的低8位数存放该字符的ASCII码,高8位存放该键的扫描码2.对于特殊键(如方向键、F1~F12等 等),低8位为0高8位字节存放该键的扫描码
    1 查询是否按下一个键,若按下一个键返回非零值否则返回0
    2 bioskey()返回Shift、Ctrl、Alt、ScrollLock、NumLock、CapsLock、Insert键的状态。各键状态存放在返回值的低8位字节中。

    在所有操作开始之前,本人先按照以往博文的惯例,编写家师所授的模仿Java中的boolean类型,自定义的伪 boolean型:

    typedef unsigned char boolean;
    
    #define TRUE  1
    #define FALSE 0
    

    本人先来构造一个可以用来表示一条蛇的结构体:

    typedef struct SNAKE{
    	int head;	//这个成员是为了我们之后判断蛇头方向用的
    	int len;	//这个成员用来记录蛇当前 “应该”有的长度
    	int curlen;	//这个成员用来记录蛇当前 “实际”的长度
    	int direct;	//这个成员表示键盘输入的指令
    	SNAKE_BODY *snake;	//这个成员是我们用来存储蛇的身体所在坐标用的
    }
    

    现在,我们构造一个能够存储蛇身体信息的结构体:

    typedef struct SNAKE_BODY{
    	int xPostion;
    	int yPostion;
    }SNAKE_BODY;
    

    那么,为了,根据我们上面的蛇头和蛇方向的成员,我们现在来给出两个数组来存储蛇头的形状 和 蛇的方向:
    首先,我们再来构造一个结构体,用来存储辅助蛇移动的结构体:

    typedef struct DELTA_MOVE{
    	int deltRow;
    	int deltCol;
    }DELTA_MOVE;
    

    那么,现在本人就可以根据以上的结构体来给出两个数组了:

    DELTA_MOVE delta[4] = {
    	{0, -1},			 //向上运动
    	{0, 1},              //向下运动           
    	{-1, 0},             //向左运动
    	{1, 0}				 //向右运动
    };
    
    char SnakeHead[4] = {
    	"^", "v", "<", ">"	//这个数组存储的蛇头方向的 “上下左右”,分别存在下标为0、1、2、3的单元中
    }
    

    现在,根据上面的数组,我们来编写一个获取蛇头形状的函数:

    char getHeadType(int snakeHeadIndex) {
        return snakeHead[snakeHeadIndex];
    }
    

    那么,现在我们就可以初始化一条蛇了:

    #define MAX_LEN 	1000
    //我们设定蛇最长为100(也可以设置地大一点,一般玩家都不会玩到100,所以本人设置最长长度是 100)
    //由于最长长度是我们用宏定义定义的,所以,我们之后如果想要改的话,在这里改也比较方便
    
    SNAKE player = {
    	0,		//设定 开始时 蛇头信息存储在下标为0的数组空间内
    	5,		//设定 开始时 蛇应该有5个长度
    	1,		//因为刚开始是一个点,所以初始长度是1
    	3,		//因为向右运动,所以设定运动方向是 右
    	NULL};	//因为我们还没有初始化身体,所以先令身体为NULL
    
    SNAKE_BODY snakeBody[MAX_LEN] = {{0,0}};	
    

    现在,我们来制定一下边界,使得我们做出来的游戏界面更加美观:

    #define WALL -2
    
    int screenPoint[Full_Screen_COUNT] = {0};	//整个屏幕一共分为25行、80列
    
    void init() {
        int i;
        int j;
        int tmp;
    
        for(i = 1; i <= 80; i++) {
            for(j = 1; j <= 25; j++) {
                if(!(i > 1 && i < 80 && j > 1 && j < 25)) {
                    tmp = (j - 1) * 80 +  i - 1;
                    screenPoint[tmp] = WALL;
                }
            }
        }
        clrscr();
        gotoxy(22, 12); /*这里的行、列值完全是一个点一个点测出来的,为了使下面的话能够在屏幕正中央显示*/
        printf("Enter any char to start the game!");
    }
    
    void showBorder() {
        int i;
    
        clrscr();
        for(i = 2; i < MAX_X; i++) {    /*将“上下墙壁”显示出来*/
            gotoxy(i, 2);
            putch(223);
            gotoxy(i, 25);
            putch(220);
        }
        for(i = 2; i <= MAX_Y; i++) {    /*将“左右墙壁”显示出来*/
            gotoxy(1, i);
            putch(219);
            gotoxy(80, i);
            putch(219);
        }
    }
    

    现在,蛇 和 边界 的问题我们就基本上解决了,现在,我们来处理一个更为重要的事——从键盘读取有效指令,并将在未获得指令时执行上一次的指令:

    #define UP	  				0x4800
    #define DOWN 		 		0x5000
    #define LEFT 				0x4b00
    #define RIGHT 				0x4d00
    
    //以下两行:设置右侧数字键盘那里的+、-为加速减速键
    #define PGUP  				0x4900
    #define PGDN  				0x5100
    
    #define ESC	  				0x11b
    
    #define MAX_COUNT 			20000
    #define MIN_COUNT 			125
    #define DEFAULT_COUNT 		5000
    //以上的宏定义,分别将各字符定义为其“键盘扫描码”,以便我们之后的函数的使用
    
    int readValidOrder(int key, int *tempCount, SNAKE *snake) {
        int tmp;
    
        tmp = snake->direct;
        switch(key) {
            case RIGHT: return tmp == 2 ? 2 : 3;
            case LEFT:  return tmp == 3 ? 3 : 2;
            case DOWN:  return tmp == 0 ? 0 : 1;
            case UP:    return tmp == 1 ? 1 : 0;    /*上面的返回值分别对应“上”、“下”、“左”、“右”在“蛇头类型数组”中的下标*/
    
            case PGUP:  if(*tempCount > MIN_COUNT) {
                            *tempCount = *tempCount / 2;
                        }
                        return -1;
            case PGDN:  if(*tempCount < MAX_COUNT) {
                            *tempCount = *tempCount * 2;
                        }
                        return -1;
        }
    }
    
    int main() {
    	int key;
    	int newKey;
    	int tempIndex;
    	int i = 0;					//因为编译软件运行地太快,所以,为了能让我们反应地过来,我们使得计算机需运行cd次,才能有效执行一次
    	int cdTime = DEFAULT_COUNT;	//(即:通俗来讲,就是为蛇的自动移动加一个cd,使得蛇移动速度降低)
    	
    	SNAKE_BODY snakeBody[MAX_LEN] = {{0,0}};
    
    	SNAKE *player = {
    		0,
    		5,
    		1,
    		3,
    		NULL};
    	hideCursor();
        
        init();
        getch();
        snakeBody[0].xPostion = headXPos;
        snakeBody[0].yPostion = headYPos;
        player.sb = snakeBody;
    
        clrscr();
        showBorder();
        while(//TODO 蛇未死亡) {
            i++;
            key = bioskey(1);
            if(key != 0){
                newKey = bioskey(0);
                if(newKey == ESC) {
                    break;
                }
                tempIndex = readValidOrder(newKey, &cdTime, &player);
                if(tempIndex >= 0 && tempIndex <= 3) {
                    player.direct = tempIndex;
                    snakeHeadType = getHeadType(player.direct);
                }
            }
            if(i > cdTime) {
                //TODO 生成食物
                //TODO 蛇移动
            	i = 0;
            }
        }
        //善后处理
    }
    

    现在,我们来实现下蛇移动的要求:

    #define BLOCK 0
    
    void move(int *headXPos, int *headYPos, DELTA_MOVE *delta, char headType, SNAKE *snake) {
        int tailRow;
        int tailCol;
        int tail;
        int tempX;
        int tempY;
        
        tempX = snake->sb[snake->head].xPostion;
        tempY = snake->sb[snake->head].yPostion;
        gotoxy(tempX, tempY);
        printf("*");
        tempX = tempX + delta->deltRow;
        tempY = tempY + delta->deltCol;
        gotoxy(tempX, tempY);
        printf("%c", headType);
        snake->head  = (snake->head + 1) % MAX_LEN;
        snake->sb[snake->head].xPostion = tempX;
        snake->sb[snake->head].yPostion = tempY;
        *headXPos = tempX;
        *headYPos = tempY;
        if(snake->curLen < snake->len) {
            (snake->curLen)++;
            return;
        }
            
        tail = (snake->head - snake->len + MAX_LEN) % MAX_LEN;
        tailRow = snake->sb[tail].xPostion;
        tailCol = snake->sb[tail].yPostion;
        screenPoint[(tailCol - 1) * MAX_X +  tailRow - 1] = 0;
        gotoxy(tailRow, tailCol);
        printf(" ");    
    }
    

    接下来就是产生食物的函数了:

    void creatFoodNum() {
        foodNum = rand()%2 ? 3 : 2;
    }
    
    void dealFood() {
        dealFoodIndex();
        showFood();
    }
    
    /*显示食物*/
    void showFood() {
        int i;
        int x;
        int y;
    
        for(i = 0; i < Full_Screen_COUNT; i++) {
            if(screenPoint[i] == USUAL_FOOD || screenPoint[i] == SUPER_FOOD) {
                x = (i + 1) % MAX_X;
                y = (i + 1) / MAX_X + 1;
                gotoxy(x, y);
                printf((screenPoint[i] == USUAL_FOOD) ? "#" : "$");
            }
        }
        eatUpFood = FALSE;
    }
    
    /*放置食物*/
    void dealFoodIndex() {
        int randNum;
        int i;
        int count = 0;
        int index[Full_Screen_COUNT] = {0};	/*这个数组是为我们之后的“发牌算法”的使用做准备*/
        int j = Full_Screen_COUNT-1;
    
        for(i = 0; i < Full_Screen_COUNT; i++) {
            if(screenPoint[i] == BLOCK) {
                index[count] = i;
                count++;
            }
        }
    
        for(i = 0; i < foodNum; i++) {
            srand(time(0) + i);
            randNum = rand() % count;
    
            screenPoint[index[randNum]] = rand()%2 ? USUAL_FOOD : SUPER_FOOD;
            index[randNum] = index[j--];
            count--;
        }
    }
    

    那么,现在基本的准备就准备好了,我们现在来解决一下生成食物和蛇吃食变长的问题:

    #define BLOCK  		 		0
    #define USUAL_FOOD   		2
    #define SUPER_FOOD	 		1
    #define BODY  				-1
    #define WALL  				-2
    
    boolean dealEat(SNAKE *snake) {
        int i;
        int k;
        int tmp;
        int tempHeadX;
        int tempHeadY;
    
        tempHeadX = snake->sb[snake->head].xPostion;
        tempHeadY = snake->sb[snake->head].yPostion;
        tmp = (tempHeadY - 1) * MAX_X +  tempHeadX - 1;
        if((screenPoint[tmp] == USUAL_FOOD) || (screenPoint[tmp] == SUPER_FOOD)) {	/*吃到食物*/
            foodNum--;
            screenPoint[tmp] = 0;
            (snake->len) += (screenPoint[tmp] == USUAL_FOOD) ? 1 : 2;
        }
    
        if(screenPoint[tmp] == BODY) {	/*吃到自身*/
            clrscr();
            gotoxy(40, 12);
            printf("Game over!");
            getch();
    
            return TRUE;
        }
    
        screenPoint[(tempHeadY - 1) * MAX_X +  tempHeadX - 1] = BODY;	/*将这个点转换为蛇身*/
        if(foodNum <= 0) {
            eatUpFood = TRUE;
            creatFoodNum();
        }
        return FALSE;
    }
    

    单人版完整代码:

    Single-Version


    那么,单人版的贪吃蛇我们就做好了。
    本人现在来展示以下运行结果:

    贪吃蛇 单人版

    那么,作为本人的最后一篇《数据结构与算法》专栏的博文,当然不能就这么草草了事,本人既然提到了单人版,那么,在这篇博文中就要讲到进阶版——贪吃蛇(双人版)

    贪吃蛇(双人版):

    因为上面有本人的单人版的说明,那么接下来的双人版的代码中所用到的算法知识,就都在单人版中讲解过了,那么,本人直接上代码:


    双人版完整代码:

    Supper-Tramper


    那么,本人来展示一下运行结果:

    贪食蛇 双人版


    那么,需要本篇博文两个版本贪吃蛇的完整代码的同学,请点击下方链接:
    Gluttonous-Snake

    做到这里,还是感慨良多的,这篇博文的内容,本应该在几个月前发布,但当时本人能力较弱,没完成这篇博文的内容,本人本来已经不打算编写这篇博文的内容了,但是,这篇博文的未完成,反而成了本人的一个心结,一直困扰着本人。于是,本人耗时一周课余时间,反复推敲、交流,终于完成了这篇博文的内容。
    那么,以这篇博文做为本专栏的正式结尾篇,本人也算是可以全心全意地投入Java的学习中了。还望各位在今后的学习中能够持续关注本人的博文,谢谢一直以来一直在关注的大家啦!
    那么,本专栏博文的全部内容本人就讲解完成了。
    若对本专栏博文有任何疑问或者意见以及建议,请在下方评论区提出,本人将尽早予以讲解以及答复。

  • 相关阅读:
    配置文件或者模板中的占位符替换工具类
    [C++设计模式] composite 组合模式
    集群通信组件tribes之集群的平行通信
    hibernate配置
    【Cocos2dx】Windows平台下Cocos2dx 2.x的下载、安装、配置,打造自己的Helloworld
    Codeforces Round #250 (Div. 2)
    一个不断复读且并没什么卵用的我这一种人的 学习观
    2015年创业中遇到的技术问题:81-90
    2015年创业中遇到的技术问题:81-90
    下载好一个android软件之后,怎样自动提示安装?
  • 原文地址:https://www.cnblogs.com/codderYouzg/p/12412042.html
Copyright © 2011-2022 走看看