c语言贪吃蛇小游戏练习

c语言贪吃蛇小游戏练习

学了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-06-28 四 23:54

Validate

猜你喜欢

转载自www.cnblogs.com/recallfuture/p/9241332.html