登录

在这个站点登录

保存我的登录记录

<<忘记密码?

还没有账号?点此注册>>

Jerry

C语言实现2048游戏

分享到:

本文已被浏览2553

齐鲁软件大赛暂时告一段落,网站也荒废了几个月没有更新,网站都长草了。在齐软互评的时候,恰巧碰上被评审的队伍作品所在的容器崩溃。容器崩溃的提示页面上有个官方挂在上边的HTML版本的2048,玩过之后发现游戏的逻辑不是很困难,就动手搞了一波2048。
2048

20482014年Gabriele Cirulli利用周末的时间写2048这个游戏的程序,仅仅只是好玩而已。他想用一种不同的视觉展现效果和更快速的动画来创造属于我自己的游戏版本
AsherVollmer和GregWohlwend作为Threes的开发者,他们在开发这款游戏的时候投入了大量的时间和精力。在制作2048的过程中,他决定把项目托管到Github上去,并且通过Github完成项目。完成了这个游戏之后,通过GithubPages发布,并发表在DesignerNews上。

首先上一波图,因为这里只是在用C语言验证算法,所以没有对界面做很好的优化,丑是理所应当的。
项目效果图
了解了游戏的工作原理,实际上可以将游戏描述为四个带有方向的同一操作:

  1. 将所有数字向一个方向移动至中间没有空位
  2. 将相邻的两个相同的数字加和然后放在更靠近移动方向前部的一个位置上

另外需要判断一下玩家当前输入的内容是否可以执行,如果不可以执行等待用户下一条记录。

同时需要对游戏的进程进行控制,如果可以继续游戏,那么运行玩家继续输入下一条指令,而如果不可以进行,那么提示无法继续游戏的提示。

首先的问题就是光标键的输入。光标键属于功能键,使用常规的scanf当然是无法进行读取的,而使用更加接近硬件的getch()进行以字节为单位的标准输入。当使用getch()函数进行标准输入时,如果用户输入了一个功能键,例如光标键、Home、PgUp、PgDn、End之类的键,getch()将能够读取得到两个字符。当遇到功能键输入的时候,可以编写一个检测程序以获取对应按键的数据:
按键数据提取工具

#include<stdio.h>
int main(){
    while(1){
       printf("%d\n",getch());
    }
}

随后运行这个数据提取程序,程序将按照一个字节一行,以整型的格式输出getch得到的数据。这里我查询到2048需要用到的四个按键↑↓←→对应的两个字节为:

按键 第一字节 第二字节
224 72
224 80
224 75
224 77

然后就是游戏的主要的代码

#include<stdio.h>		//标准输入输出
#include<stdlib.h>		//基本工具函数
#define bool int		//C里边没有布尔类型,就自己造
#define true 1			//bool的两种值
#define false 0
int MAP[4][4]= {0};		//地图,默认0认为是空位
typedef enum {			//定义一个方向类型的枚举变量
    UNKNOW,
    UP,
    DOWN,
    LEFT,
    RIGHT
} Direction;

void printMap();		//绘制图形
Direction getNextDirection();	//从键盘读入下一个用户操作
bool canMove(Direction direction);	//判断是否可以进行指定方向的操作
void doAction();			//游戏事件
void move(Direction direction);		//移动数字
void putNew();				//放入一个新的数字
int main() {				//主函数
    Direction nextStep;			//下一步
    int i,j;
    srand(time(0));
    putNew();				//游戏开始默认放两个数字
    putNew();
    printMap();				//打印格子
    while(1) {
        if(!canMove(UP)&&!canMove(LEFT)&&!canMove(DOWN)&&!canMove(RIGHT)) break;	//任意方向都不能移动,那么终止游戏
        nextStep=getNextDirection();		//获取下一个用户操作
        if(nextStep==UNKNOW) continue;		//如果不知道用户按了个什么键或者用户胡乱按的,那么进入新的循环
        if(!canMove(nextStep)) continue;	//如果下一步不可继续操作,进入新的循环
        system("cls");				//对于Windows来说,执行命令行命令cls清屏
        doAction(nextStep);			//执行操作
        putNew();				//放新的数字
        printMap();				//打印格子
    }
    printf("You Died!");			//提示游戏结束
    while(1);					//等待游戏结束
}

void printMap() {
    int i,j;
    printf("*-------*-------*-------*-------*\n");
    for(i=0; i<4; i++) {
        printf("|");
        for(j=0; j<4; j++) {
            MAP[i][j]?printf("%d",MAP[i][j]):printf(" ");
            printf("\t|");
            if(j>2)
                printf("\n");
        }
        printf("*-------*-------*-------*-------*\n");
    }
}
void doAction(Direction direction){
    int i,j,k;
    /**
     * 为了方便处理问题,将每个方向的运动操作简化为三步
     * 1.将数字归并到一个方向
     * 2.处理相同数字可消,并将消掉的数据定为0
     * 3.再次将数字归并到一个方向
     */
    //1.移动数字,取消数字之间的空位
    move(direction);
    //2.按照方向处理相同数字
    switch(direction){
    case UP:
	//按列枚举
        for(i=0;i<4;i++){
	    //对于每一行的每一个元素
            for(j=0;j<3;j++){
		//如果元素非零,并且当前和下一个相同,当前的翻倍,下一个置零
                if(MAP[j][i]&&MAP[j][i]==MAP[j+1][i]){
                    MAP[j][i]+=MAP[j+1][i];
                    MAP[j+1][i]=0;
                }
            }
        }
        break;
    case LEFT://同上
        for(i=0;i<4;i++)
            for(j=0;j<3;j++)
                if(MAP[i][j]&&MAP[i][j]==MAP[i][j+1]){
                    MAP[i][j]+=MAP[i][j+1];
                    MAP[i][j+1]=0;
                }
        break;
    case DOWN://同上
        for(i=0;i<4;i++)
            for(j=3;j>0;j--)
                if(MAP[j][i]&&MAP[j][i]==MAP[j-1][i]){
                    MAP[j][i]+=MAP[j-1][i];
                    MAP[j-1][i]=0;
                }
        break;
    case RIGHT://同上
        for(i=0;i<4;i++)
            for(j=3;j>0;j--)
                if(MAP[i][j]&&MAP[i][j]==MAP[i][j-1]){
                    MAP[i][j]+=MAP[i][j-1];
                    MAP[i][j-1]=0;
                }
        break;
    }
    //3.移动数字,取消因为上一步置零过程中新产生的空位
    move(direction);
}
void move(Direction direction) {	//移动数字
    int i,j,k;
    switch(direction) {
    case UP:
        //按列枚举
        for(i=0;i<4;i++)
	    //对于每一行的每一个元素
            for(j=0;j<4;j++)
		//如果非零,那么应当取消当前位置,后边元素向前移动
                if(!MAP[j][i]){
                    for(k=j;k<3;k++){
                        MAP[k][i]=MAP[k+1][i];
                    }
		    //新产生的空位置零
                    MAP[k][i]=0;
                }
        break;
    case LEFT://同上
        for(i=0;i<4;i++)
            for(j=0;j<4;j++)
                if(!MAP[i][j]){
                    for(k=j;k<3;k++){
                        MAP[i][k]=MAP[i][k+1];
                    }
                    MAP[i][k]=0;
                }
        break;
    case DOWN://同上
        for(i=0;i<4;i++)
            for(j=3;j>=0;j--)
                if(!MAP[j][i]){
                    for(k=j;k>0;k--){
                        MAP[k][i]=MAP[k-1][i];
                    }
                    MAP[k][i]=0;
                }
        break;
    case RIGHT://同上
        for(i=0;i<4;i++)
            for(j=3;j>=0;j--)
                if(!MAP[i][j]){
                    for(k=j;k>0;k--){
                        MAP[i][k]=MAP[i][k-1];
                    }
                    MAP[i][k]=0;
                }
        break;
    }
}
bool canMove(Direction direction) {	//判断是否可以进行指定方向的操作
    int i,j;
    switch(direction) {
    case UP:
	//依次检查每一列
        for(i=0;i<4;i++){
	    //首先排除在远端的一串空位,直接将j指向第一个非零元素
            for(j=3;j>=0;j--)
                if(MAP[j][i])
                    break;
	    //j>0代表这一列并非全部为0
            if(j>0)
		//依次检查每一个剩余元素,遇见空位直接返回true
                for(;j>=0;j--)
                    if(!MAP[j][i])
                        return true;
	    //依次检查相邻的元素是否存在相同的非零数字
            for(j=3;j>0;j--)
                if(MAP[j][i]&&MAP[j][i]==MAP[j-1][i])
                    return true;
        }
        break;
    case DOWN://同上
        for(i=0;i<4;i++){
            for(j=0;j<4;j++)
                if(MAP[j][i]) break;
            if(j<4)
                for(;j<4;j++)
                    if(!MAP[j][i]) return true;
            for(j=0;j<3;j++)
                if(MAP[j][i]&&MAP[j][i]==MAP[j+1][i])
                    return true;

        }
        break;
    case LEFT://同上
        for(i=0; i<4; i++){
            for(j=3;j>=0;j--)
                if(MAP[i][j])
                    break;
            if(j>=0)
                for(;j>=0;j--)
                    if(!MAP[i][j])
                        return true;
            for(j=0;j<3;j++)
                if(MAP[i][j]&&MAP[i][j]==MAP[i][j+1])
                    return true;
        }
        break;
    case RIGHT://同上
        for(i=0; i<4; i++){
            for(j=0;j<4;j++)
                if(MAP[i][j])
                    break;
            if(j<4)
                for(;j<4;j++)
                    if(!MAP[i][j])
                        return true;
            for(j=0;j<3;j++){
                if(MAP[i][j]&&MAP[i][j]==MAP[i][j+1])
                    return true;
            }
        }
        break;
    }
	//当允许条件都被检查过后,返回不可执行的结果
    return false;
}
Direction getNextDirection() {
    //第一个字节必须是224,否则判定输入的不是功能键
    if(getch()!=224) return UNKNOW;
    //根据第二字节对应出来用户的操作
    switch(getch()) {
    case 72:
        return UP;
    case 80:
        return DOWN;
    case 75:
        return LEFT;
    case 77:
        return RIGHT;
    default:
        return UNKNOW;
    }
}
void putNew(){
    //为了方便操作,临时存储一下所有空闲格子的指针,这样可以用一个线性的内存随机访问实现对所有空位中任一空位的随机访问.
    int* boxes[16]={NULL};
    //用来临时保存目标格子的地址
    int* target;
    //统计一共有多少个有效空格
    int count=0;
    int i,j;
    //统计空位,发现空位即保存地址并累加计数器
    for(i=0;i<4;i++)
        for(j=0;j<4;j++)
            if(!MAP[i][j]){
                boxes[count]=&MAP[i][j];
                count++;
            }
    if(count){
        //如果有空位,那么对这一位进行随机赋值操作,对于每一位可能性是相同的
        target=boxes[rand()%count];
        //50%可能出现2 50% 可能出现4
        *target=rand()%2?2:4;
    }
}

po主认为,俄罗斯方块、2048这些稍微偏算法的小游戏是程序员必写的几个小程序。
为了表示对原版的信仰,奉上原著HTML版《2048》这里就不用iframe嵌套了,套了几把发现整个页面被github卡出翔,于是只能挂个连接资瓷一下了.

 手机扫描左边的二维码,立刻将文章收入手机!
 微信扫描左边二维码,点击右上角即可分享到朋友圈!
严禁任何非授权的采集与转载,转载须经站长同意并在文章显著位置标注本文连接,站长保留追究法律责任的权利.

评论

 您需要 先登录 才可以回复.