zoukankan      html  css  js  c++  java
  • c语言贪吃蛇小游戏练习

    c语言贪吃蛇小游戏练习

    c语言贪吃蛇小游戏练习

    完整代码已放到github,传送们:[[https://github.com/recallfuture/c_snack][贪吃蛇]]

    学了c之后的第一个游戏就是贪吃蛇,那时苦思冥想了一周的时间才做完,现如今两年过去了,再次做起贪吃蛇的时候想到了好多可以优化的地方,代码也更整洁了,心里还是挺高兴的。

    下面记录下这次的过程

    1 需求分析

    虽说只是一个贪吃蛇,但在写之前做好准备工作的话,也能在写的时候思路清晰很多。

    这个游戏是在控制台上游玩,意味着所有显示着的都是字符,也就是我们需要操控字符在固定位置的输出,读取键盘输入,以及字符的显示和隐藏。所以,在熟悉了c语言基础语法和贪吃蛇玩法的前提下,做这个游戏的难点只有三个:

    • 可以在控制台区域任意有效位置打印字符
    • 判断是否有键盘输入但不阻塞游戏
    • 游戏逻辑

    1.1 任意位置打印字符

    这个需要用到windows api,获取窗口句柄,然后定点输出。

    这里提一下,默认的cmd打开后窗口大小是80x24,而且横向最多也只能是80,纵向没有限制。

    1.2 不阻塞的判断键盘输入

    这个需要用到kbhit()函数,这个函数检测是否有键盘输入,有的话返回true,无则返回false,而且无论有或者没有输入,都会直接返回一个bool值,不会等待输入。

    这个函数在conio.h头文件中,如果你的编译器是vc6.0或以下,那你需要将kbhit改成kbhit

    1.3 游戏逻辑

    这部分看起来很大,但其实内容并不多,主要分为如下几块:

    • 初始化
    • 关卡循环
    • 前进循环
    • 吃东西判定,死亡判定和晋级判定
    • 游戏通关或结束提示

    这里就不一一描述了,直接看代码应该来的更好,里面也有比较详细的注释

    注:下面这个可能会在编译时候报错,如此的话,改成cpp后缀并当作c++程序来编译即可。

    #include <windows.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <conio.h>
    
    //地图行数
    #define ROW 23
    //地图列数
    #define COL 44
    //蛇的最大长度
    #define LEN 50
    
    //定义各个物体类别的枚举
    enum types{
      NONE,
      HEAD,
      BODY,
      FOOD,
      WALL
    };
    
    //定义移动方向枚举
    enum directions{
      LEFT,
      RIGHT,
      TOP,
      BOTTOM
    };
    //定义移动偏移量数组
    int offset[4][2] = {{0, -1}, {0, 1}, {-1, 0}, {1, 0}};
    
    //定义关卡枚举
    enum chapter{
      WIN,
      EASY,
      NORMAL,
      HARD
    };
    
    //定义地图内单个格子的结构体
    struct grid{
      types type;
    };
    //定义蛇结构体
    struct snack{
      int x, y; //坐标
    };
    
    
    //创建地图
    struct grid map[ROW][COL];
    //定义蛇头和蛇尾和移动方向
    struct snack head, tail;
    directions direction;
    //定义分数
    int score = 0;
    //本局游戏是否结束,1为晋级,2为失败
    int isFinish = 0;
    //定义行走路线栈,便于跟踪尾巴的路线
    struct snack route[LEN];
    int pHead = 0;
    int pTail = 0;
    
    //以下内容需要在关卡设定的时候初始化
    //当前关卡和下一级关卡
    chapter nowChapter = EASY;
    chapter nextChapter = EASY;
    //定义蛇的速度,范围是1-10,越大越快
    int speed = 4;
    //定义每个食物可以得到的分数
    int foodScore = 1;
    //定义完成本关卡所需的分数,需要小于蛇的最大长度
    int levelUpScore;
    
    //绘图和光标的相关函数
    void SetPos(COORD a);
    void SetPos(int i, int j);
    void HideCursor();
    void drawRow(int y, int x1, int x2, char ch);
    void drawRow(COORD a, COORD b, char ch);
    void drawCol(int x, int y1, int y2, char ch);
    void drawCol(COORD a, COORD b, char ch);
    void drawFrame(COORD a, COORD  b, char ch);
    void drawFrame(int x1, int y1, int x2, int y2, char ch);
    
    //游戏相关
    void initMap();
    void showMap();
    void showInfo();
    void initScene();
    void genFood();
    void move();
    void flushMap();
    void showScore();
    void levelUp();
    void gameOver();
    
    int main(){
      //隐藏光标
      HideCursor();
    
      //开始游戏循环
      while(1){
        system("cls");//清屏
        initScene();//初始化整个场景
    
        _getch();//等待玩家确定开始游戏
        genFood();//生成最初的食物
        while(!isFinish){
          move();
        }
    
        (isFinish == 1) ? levelUp() : gameOver();
      }
      return 0;
    }
    
    //下面这堆是从别的地方抄的,很好用就对了
    void SetPos(COORD a)// 移动光标(隐) 
    {
        HANDLE out=GetStdHandle(STD_OUTPUT_HANDLE);
        SetConsoleCursorPosition(out, a);
    }
    
    void SetPos(int i, int j)// 移动光标
    {
        COORD pos={i, j};
        SetPos(pos);
    }
    
    void HideCursor()//隐藏光标
    {
        CONSOLE_CURSOR_INFO cursor_info = {1, 0}; 
        SetConsoleCursorInfo(GetStdHandle(STD_OUTPUT_HANDLE), &cursor_info);
    }
    
    //把第y行,[x1, x2) 之间的坐标填充为 ch
    void drawRow(int y, int x1, int x2, char ch)
    {
      int i;
        SetPos(x1,y);
        for(i = 0; i <= (x2-x1); i++)
            printf("%c",ch);
    }
    
    //在a, b 纵坐标相同的前提下,把坐标 [a, b] 之间填充为 ch
    void drawRow(COORD a, COORD b, char ch)
    {
        if(a.Y == b.Y)
            drawRow(a.Y, a.X, b.X, ch);
        else
        {
            SetPos(0, 25);
            printf("error code 01:无法填充行,因为两个坐标的纵坐标(x)不相等");
            system("pause");
        }
    }
    
    //把第x列,[y1, y2] 之间的坐标填充为 ch
    void drawCol(int x, int y1, int y2, char ch)
    {
        int y=y1;
        while(y!=y2+1)
        {
            SetPos(x, y);
            printf("%c",ch);
            y++;
        }
    }
    
    //在a, b 横坐标相同的前提下,把坐标 [a, b] 之间填充为 ch
    void drawCol(COORD a, COORD b, char ch)
    {
        if(a.X == b.X)
            drawCol(a.X, a.Y, b.Y, ch);
        else
        {
            SetPos(0, 25);
            printf("error code 02:无法填充列,因为两个坐标的横坐标(y)不相等");
            system("pause");
        }
    }
    
    //左上角坐标、右下角坐标、用row填充行、用col填充列
    void drawFrame(COORD a, COORD  b, char ch)
    {
        drawRow(a.Y, a.X, b.X, ch);
        drawRow(b.Y, a.X, b.X, ch);
        drawCol(a.X, a.Y+1, b.Y-1, ch);
        drawCol(b.X, a.Y+1, b.Y-1, ch);
    }
    
    void drawFrame(int x1, int y1, int x2, int y2, char ch)
    {
        COORD a={x1, y1};
        COORD b={x2, y2};
        drawFrame(a, b, ch);
    }
    
    //从这里开始都是自己的内容
    //初始化地图,从下面的三个函数中选,此处可自定义
    void initMap(){
      //重置地图
      void clearMap();
      clearMap();
    
      //声明关卡函数
      void easyMap();
      void normalMap();
      void hardMap();
    
      //判断当前关卡
      switch(nowChapter){
      case EASY:
        easyMap();
        break;
      case NORMAL:
        normalMap();
        break;
      case HARD:
        hardMap();
        break;
      }
    }
    
    void clearMap(){
      int i,j;
      for(i=0; i<ROW; i++)
        for(j=0; j<COL; j++)
          map[i][j].type = NONE;
    }
    
    void easyMap(){
      int i,j;
    
      //初始化墙的位置
      i = 0;
      for(j=0; j<COL; ++j) map[i][j].type = WALL;
      i = ROW-1;
      for(j=0; j<COL; ++j) map[i][j].type = WALL;
      j = 0;
      for(i=0; i<ROW; ++i) map[i][j].type = WALL;
      j = COL-1;
      for(i=0; i<ROW; ++i) map[i][j].type = WALL;
    
      //初始化蛇的位置
      head.x = tail.x = 20;
      head.y = tail.y = 10;
      tail.y++;
      map[head.x][head.y].type = HEAD;
      map[tail.x][tail.y].type = BODY;
    
      //初始化其他变量
      nextChapter = NORMAL;
      speed = 4;
      foodScore = 1;
      levelUpScore = 3;
    }
    
    void normalMap(){
      //自行添加代码
      int i,j;
    
      //初始化墙的位置
      i = 0;
      for(j=0; j<COL; ++j) map[i][j].type = WALL;
      i = ROW-1;
      for(j=0; j<COL; ++j) map[i][j].type = WALL;
      j = 0;
      for(i=0; i<ROW; ++i) map[i][j].type = WALL;
      j = COL-1;
      for(i=0; i<ROW; ++i) map[i][j].type = WALL;
    
      //初始化蛇的位置
      head.x = tail.x = 20;
      head.y = tail.y = 10;
      tail.y++;
      map[head.x][head.y].type = HEAD;
      map[tail.x][tail.y].type = BODY;
    
      //初始化其他变量
      nextChapter = HARD;
      speed = 6;
      foodScore = 2;
      levelUpScore = 6;
    }
    
    void hardMap(){
      //自行添加代码
      int i,j;
    
      //初始化墙的位置
      i = 0;
      for(j=0; j<COL; ++j) map[i][j].type = WALL;
      i = ROW-1;
      for(j=0; j<COL; ++j) map[i][j].type = WALL;
      j = 0;
      for(i=0; i<ROW; ++i) map[i][j].type = WALL;
      j = COL-1;
      for(i=0; i<ROW; ++i) map[i][j].type = WALL;
    
      //初始化蛇的位置
      head.x = tail.x = 20;
      head.y = tail.y = 10;
      tail.y++;
      map[head.x][head.y].type = HEAD;
      map[tail.x][tail.y].type = BODY;
    
      //初始化其他变量
      nextChapter = WIN;
      speed = 8;
      foodScore = 3;
      levelUpScore = 3;
    }
    
    //将地图和蛇显示出来
    void showMap(){
      int i,j;
      for(i=0; i<ROW; i++)
        for(j=0; j<COL; j++){
          if(map[i][j].type == WALL){
            SetPos(j, i);
            putch('04');
          }
          else if(map[i][j].type == HEAD || map[i][j].type == BODY){
            SetPos(j, i);
            putch('02');
          }
          else if(map[i][j].type == FOOD){
            SetPos(j, i);
            putch('03');
          }
          else{
            SetPos(j, i);
            putch(' ');
          }
        }
    }
    
    //显示帮助和说明
    void showInfo()
    {
      SetPos(45,2);
      printf("Now chapter: %d", (int)nowChapter);
      SetPos(45,5);
      printf("Use w,a,s,d to control your snack.");
      SetPos(45,6);
      printf("Eat food to be stronger.");
      SetPos(45,7);
      printf("Break wall? You die.");
      SetPos(45,15);
      printf("Any key to start!");
    }
    
    //初始化整个场景,调用了其他初始化函数
    void initScene(){
      initMap();
      showMap();
      showInfo();
    
      score = 0;
      isFinish = 0;
      pHead = 0;
      pTail = 0;
    
      direction = LEFT;
    
      if(speed>10 || speed<1)
        speed = 4;
    }
    
    void genFood(){
      //此处可以增加是否地图已满的判定代码,以防止后面无限循环
    
      //随机生成食物位置
      int x,y;
      while(1)
      {
        x = rand()%23;
        y = rand()%44;
        if(map[x][y].type == NONE)
          {
            map[x][y].type = FOOD;
            break;
          }
      }
    
      //打印食物
      SetPos(y, x);
      putch('03');
    }
    
    //显示成绩
    void showScore(){
      SetPos(45,10);
      printf("Score: %d, target: %d", score, levelUpScore);
    }
    
    //蛇移动的核心代码
    void move(){
      int curSpeed = 11-speed;
      struct snack next = head;
    
      while(curSpeed--){
        if(_kbhit()){ //如果检测到有键盘输入的话
          char c;
          c = _getch();
    
          switch(c){
          case 'w':
          case 'W':
            next.x += offset[(int)TOP][0];
            next.y += offset[(int)TOP][1];
            direction = TOP;
            break;
          case 's':
          case 'S':
            next.x += offset[(int)BOTTOM][0];
            next.y += offset[(int)BOTTOM][1];
            direction = BOTTOM;
            break;
          case 'a':
          case 'A':
            next.x += offset[(int)LEFT][0];
            next.y += offset[(int)LEFT][1];
            direction = LEFT;
            break;
          case 'd':
          case 'D':
            next.x += offset[(int)RIGHT][0];
            next.y += offset[(int)RIGHT][1];
            direction = RIGHT;
            break;
          }
        }
        Sleep(20);
      }
    
      //结算
      if(map[next.x][next.y].type == HEAD){
        next.x += offset[(int)direction][0];
        next.y += offset[(int)direction][1];
      }
    
      if(map[next.x][next.y].type == NONE){
        map[next.x][next.y].type = HEAD;
        map[head.x][head.y].type = BODY;
        map[tail.x][tail.y].type = NONE;
    
        route[pHead++] = head;
        tail = route[pTail++];
        head = next;
      }
      else if(map[next.x][next.y].type == BODY || map[next.x][next.y].type == WALL){
        isFinish = 2;
        return;
      }
      else if(map[next.x][next.y].type == FOOD){
        map[next.x][next.y].type = HEAD;
        map[head.x][head.y].type = BODY;
    
        route[pHead++] = head;
        head = next;
    
        score += foodScore;
        genFood();
      }
    
      pHead = (pHead==LEN ? 0 : pHead);
      pTail = (pTail==LEN ? 0 : pTail);
    
      flushMap();
      showScore();
    
      if(score >= levelUpScore){
        isFinish = 1;
        return;
      }
    }
    
    
    void flushMap(){
      showMap();
    }
    
    void levelUp(){
      //自行添加代码
      nowChapter = nextChapter;
      SetPos(45,10);
      if(nowChapter == WIN){
        printf("Congratulations, you Win!");
        nowChapter = EASY;
      }
      else
        printf("Congratulations, you Level Up!");
    
      _getch();
    }
    
    void gameOver(){
      //自行添加代码
      nowChapter = EASY;
      SetPos(45,11);
      printf("Oh, no, you DIE!");
    
      _getch();
    }
    

    Date: 2018-06-28 19:50

    Author: su

    Created: 2018-07-01 日 08:27

    Validate

  • 相关阅读:
    关于代码中写日志的一些思考
    2018 年 深度学习框架 盘点 比较 推荐
    深度学习----实现一个博弈型的AI,从五子棋开始
    深度学习笔记----Anaconda及开发环境搭建
    彩票玩法介绍
    EditPlus 3.41 p1115 0728注册码
    丹--支付宝二维码
    C#类、方法的访问修饰符
    VS2010单元测试入门实践教程
    Nunit中如何进行事务性单元测试
  • 原文地址:https://www.cnblogs.com/recallfuture/p/9241332.html
Copyright © 2011-2022 走看看