贪吃蛇升级!

继上一次的贪吃蛇之后,今天下午我又继续开发了贪吃蛇。我只编辑了游戏端,控制端还行,没什么需要修改的。我做的几个主要改动:

  • 调整像素长宽比
  • 蛇超出边界会从另一边回来
  • 边界标识出来
  • 空地用空格标识,减少刷新时的闪烁
  • 标注蛇头为’@’
  • 加了一个疯狂加苹果的彩蛋。

基本上就这些了,也不是很多。把源码贴在这里,我去吃饭了!

#include <iostream>
#include <cstdio>
#include <cstring>
#include <ctime>//计时器,在控制刷新时间的时候有用
#include <cstdlib>//调用系统函数system();以及随机数函数。
#define SCREEN_WIDTH 20//显然,屏幕宽度
#define SCREEN_HEIGHT 20//屏幕高度
#define REFRESH_RATE 5.0
//刷新率,单位为Hz。这里需要一个float,所以必须加上".0",不然会当成int来看。
#define UP 72
//定义向上的键位。上箭头会拆成两个char输入,一个是-32,一个是72。这个在控制端也会讲。下同。
#define LEFT 75
#define DOWN 80
#define RIGHT 77
#define APPLES 10//定义保证地图上生成的苹果数量。
using namespace std;
int mat[SCREEN_HEIGHT][SCREEN_WIDTH],mx[]={0,1,0,-1},my[]={1,0,-1,0},
    apples=0,target=APPLES,l,direction[SCREEN_HEIGHT][SCREEN_WIDTH];
/*
mat:这储存了当前的地图情况,每个元素储存一个像素点。
-1表示苹果,0表示空地,>0蛇神,具体数值表示距离蛇尾的距离。
这个是我的程序的核心算法,使用TTL(Time to Live)值来保存蛇的身体,
避免了储存蛇链的麻烦。用这种储存像素点的方法虽然方便,但也有缺点。
在像素比较多的时候而信息比较少的时候,用数组储存会显得稍浪费空间。
mx、my:这个是我个人的编程习惯。
在矩阵中蛇头移动有四个方向,上右下左。
数组里面储存的就是向这四个方向移动的时候蛇头坐标需要进行的变换。
在DirectX中,这好像是通过矩阵来实现的。现在才发现矩阵的强大以及应用的必要性。
apples:当前在地图上的苹果树。用来判断是否还继续需要继续生成苹果。
l:蛇身长度 
*/
char op;
/*
这个储存的是当前的操作字符。
*/
bool readop(){//从文件中读取字符,实现程序间的信息交流。
    FILE *fop=fopen("op","r");
    fscanf(fop,"%c",&op);
    fclose(fop);
    if(op!='0'){//读到了东西!
        fop=fopen("op","w");
        fprintf(fop,"0");
        /*
        更新一遍文件,防止多次改变方向。
        现在想想,好像并没有这个必要,因为
        一直往这个方向并没有什么影响啊……
        */
        fclose(fop);//关掉文件,防止占用。
    }
    return op!='0'; 
}
int getdir(){//通过当前的操作字符判断方向。
    switch(op){
        case UP:{//这些是前面常量中定义了的。
            return 3;
            break;
        }
        case LEFT:{//用起来很像Java中的Enum类型。
            return 2;
            break;
        }
        case DOWN:{
            return 1;
            break;
        }
        case RIGHT:{
            return 0;
            break;
        }
    }
}
//正如前面说的,解释每个像素点,然后输出相应的字符。
char getdottype(int x,int y){
    if(mat[x][y]==-1){
        return 'A';//苹果Apple
    }else if(mat[x][y]==0){
        return ' ';//空地。为了看得见屏幕边界,使用了下划线。
    }else if(mat[x][y]>0){
        mat[x][y]--;
        /*
        因为刷新了一次,所以蛇前进了一格。
        所以TTL-1,即离蛇尾又近了一步。
        */
        if(mat[x][y]==l-1){
            return '@';//蛇头 
        }else{
            return '*';//蛇身
        }
    }
}
void refresh(){//每次刷新屏幕的时候调用这个函数。
    system("cls");//清屏。在CMD中输入也会清屏,因为这就是CMD的命令。
    /*for(int i=0;i<SCREEN_WIDTH*SCREEN_HEIGHT;i++){
        printf("\b");//不知道为什么失效了。可以删掉。
    }*/
    for(int j=0;j<SCREEN_WIDTH;j++){//场地边缘 
        printf(" -");
    }
    printf("\n");
    for(int i=0;i<SCREEN_HEIGHT;i++){
        printf("|");
        for(int j=0;j<SCREEN_WIDTH;j++){//枚举每一个点。
            printf("%c ",getdottype(i,j));//然后处理它!
        }
        printf("\b|\n");//输出完一行之后换行。
    }
    for(int j=0;j<SCREEN_WIDTH;j++){
        printf(" -");
    }
    cout<<endl;
}
inline bool valid(int x,int y){//检查当前蛇头位置是否合法,不合法就死!
    return mat[x][y]<=0;
    /*
    首先检查是否在地图边界内,
    然后看是否这里是否有蛇身。
    当然这样的算法比较有趣的
    一点是当你往右走时按左,
    就会把自己吃掉。
    */
}
void generateApple(){//生成苹果
    int tx=rand()%SCREEN_HEIGHT,ty=rand()%SCREEN_WIDTH;
    //随便生成一个苹果位置
    while(apples<target){//要生成足够多的苹果
        while(mat[tx][ty]!=0){//不能生成在苹果上,也不能生成在蛇上面
            tx=rand()%SCREEN_HEIGHT;//生成的随机数范围应该是int范围内,
            ty=rand()%SCREEN_WIDTH;//所以为了限定到屏幕范围内,模边长。
        }
        /*
        这里算法有一点问题。当蛇很长很长的时候,
        生成苹果大部分位置都是非法的。
        这样要生成出一个苹果,需要大量的时间。
        */
        mat[tx][ty]=-1;//苹果出现!
        apples++;
    }
}
int main(){//好了,到主函数了。
    char yn;//这个是接收是否继续游戏的字符。
    do{
        apples=0;//每次游戏开始时都应该是没有苹果,所以初始化为0.
        target=APPLES;//苹果数量修改为设定的值 
        //一开始做的时候忘了加这一句。
        clock_t timer;//计时器。用来检测刷新间距是否已达到。
        memset(mat,0,sizeof(mat));
        l=1;
        int d=0,x=0,y=0;
        /*
        d:方向
        x,y:蛇头的位置
        */
        mat[x][y]=1;//一开始蛇在左上角
        refresh();//预先输出一次屏幕
        srand(time(NULL));//声明随机种子,用的是时间。
        generateApple();//生成苹果
        readop();
        while(true){
            timer=clock();
            while(clock()-timer<1000/REFRESH_RATE){
                /*
                计算是否已经到了刷新时间,没到
                就一直读取输入。这是堵塞的行为!
                有一个小问题就是clock()的返回值
                不一定是1/1000s,在不同的环境下
                不一样,这是我在《算法竞赛入门经典》
                上看到的。正确的做法是使用
                CLOCKS_PER_SECONDE代替1000.
                */
                if(readop()){
                    d=getdir();
                }
            }
            x+=mx[d];//移动蛇头
            y+=my[d];
            if(!valid(x,y)){//如果蛇头出现在了神奇的位置,死!
                break;
            }
            if(x<0){
                x=SCREEN_HEIGHT-1;
            }else if(x==SCREEN_HEIGHT){
                x=0;
            }
            if(y<0){
                y=SCREEN_WIDTH-1;
            }else if(y==SCREEN_WIDTH){
                y=0;
            }
             
            if(mat[x][y]==-1){//吃到了苹果就加长蛇身
                l++;
                apples--;
            }
            if(l==10||l==50||l==100){//小彩蛋! 
                cout<<"Apples coooooming!"<<endl;
                target++;
            }else{
                target=APPLES;
            }
            generateApple();
            mat[x][y]=l;
            direction[x][y]=d;
            refresh();
            cout<<"Current length:"<<l<<endl;
        }
        cout<<"You died!"<<endl;
        cout<<"Continue? (UP/DOWN)"<<endl;
        readop();
        while(op=='0'){
            readop(); 
        }
    }while(op==UP);
}
avatar
Kerry Su