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的学习中了。还望各位在今后的学习中能够持续关注本人的博文,谢谢一直以来一直在关注的大家啦!
    那么,本专栏博文的全部内容本人就讲解完成了。
    若对本专栏博文有任何疑问或者意见以及建议,请在下方评论区提出,本人将尽早予以讲解以及答复。

  • 相关阅读:
    什么样的代码称得上是好代码?
    九年程序人生 总结分享
    Docker入门 第一课 --.Net Core 使用Docker全程记录
    阿里云 Windows Server 2012 r2 部署asp.net mvc网站 平坑之旅
    Visual studio 2015 Community 安装过程中遇到问题的终极解决
    Activiti6.0 spring5 工作流引擎 java SSM流程审批 项目框架
    java 进销存 库存管理 销售报表 商户管理 springmvc SSM crm 项目
    Leetcode名企之路
    24. 两两交换链表中的节点
    21. 合并两个有序链表
  • 原文地址:https://www.cnblogs.com/codderYouzg/p/12412042.html
Copyright © 2011-2022 走看看