用c++实现游戏2048---核心算法

用c++实现2048---核心算法

写在前面

这一篇主要讲如何相应操纵者给出的指令,对地图进行相应的操作。

这里我最开始的思路很简单,就是将地图的移动方式分为四种,即上(w)下(s)左(a)右(d)。

但是如果分为四个函数的话,那就太繁琐了,复用性太差,相同的代码复制四次只修改一点,十分难看,所以通过思考将四个移动方式合并成为一个移动方式就是本篇所要思考的内容。

根据指令移动地图

最外层思路

在最外层的MoveMap()函数就是用来识别wasd这四个指令。

void MoveMap(int(&MAP)[MAP_SIZE][MAP_SIZE], const char command, int &score) {
    /*根据指令移动地图*/
    switch (command) {
    case 'w':
        Move(MAP, 0, 0, score);
        break;
    case 's':
        Move(MAP, 0, MAP_SIZE - 1, score);
        break;
    case 'a':
        Move(MAP, 1, 0, score);
        break;
    default:
        Move(MAP, 1, MAP_SIZE - 1, score);
    }
}

用一个switch来识别四个方向,通过修改函数Move()内的参数来识别是哪个方向的。

通过观察可以发现:

  • 向上移动的参数为0, 0
  • 向下移动的参数是0, MAP_SIZE-1
  • 向左移动个参数是1, 0
  • 向右移动的参数是1, MAP_SIZE-1

也就是说,两两之间存在联系。下面我就详细解释一下怎么将其通过转换将相似之处合并的。

Move函数

函数的代码实现如下

void Move(int(&MAP)[MAP_SIZE][MAP_SIZE], int direction, int position, int &score) {
    /*竖直移动合并*/
    //函数声明
    void DeleteZero(int(&MAP)[MAP_SIZE][MAP_SIZE], int position);
    void Transpose(int(&MAP)[MAP_SIZE][MAP_SIZE]);

    //如果是水平方向上的话, 先转置
    if (direction == 1) {
        Transpose(MAP);
    }

    //首先移动所有格子,让其靠在一起
    DeleteZero(MAP, position);

    for (int j = 0; j < MAP_SIZE; j++) {
        int start = abs(MAP_SIZE - 1 - position);
        int curPos = -start;
        //合并
        while (curPos-start < MAP_SIZE-1) {
            int cur = MAP[abs(curPos)][j];
            int next = abs(curPos + 1);
            if ((cur != 0) && (cur == MAP[next][j])) {
                MAP[next][j] = 0;
                MAP[abs(curPos)][j] *= 2;
                curPos += 2;
                score++;
            }
            else {
                curPos++;
            }
        }
    }

    //补充因合并产生的空位
    DeleteZero(MAP, position);

    //如果是水平方向上的话, 最后再转置一下
    if (direction == 1) {
        Transpose(MAP);
    }
}

函数总共传入四个参数,第一个就是地图,第二个是移动的方向,第三个为扫描的起始位置,第四个为分数。

转置地图

首先我考虑的是,如何将上下移动和左右移动合并到一起。

因为地图MAP是一个二维数组,所以我们可以这样思考:

  1. 向上移动是令所有格子从每一标号为MAP_SIZE-1的位置移向0位置。
  2. 向左移动是令所有格子从每一标号为MAP_SIZE-1的位置移向0位置。
  3. 向下移动是令所有格子从每一标号为0的位置移向MAP_SIZE-1位置。
  4. 向右移动是令所有格子从每一标号为0的位置移向MAP_SIZE-1位置。

那么这样一分就应该很明白了,参数direction用来分辨是对行操作还是对列操作(分辨上下还是左右),参数posititon是区分从哪里移动到哪里(分辨上左还是下右)。

那么如果将左右通过变换为上下操作,那么我们就可以只考虑position了。所以我这里用了转置。

通过将整个地图作为一个矩阵进行转置,即通过对对角线进行反转,那么就可以将左右变换为上下。

所以通过direction分辨是上下还是左右,上下为0,左右为1。如果为左右,就对地图进行转置,即函数Transpose()。通过转置以后,我们就可以只考虑移动的问题了。

移动问题

因为地图中可能所有的格点没有全部都靠在一起,这样会对我们识别相同格子造成困扰,所以我们需要首先移动格子,让所有格子靠在一起,移动的方向就是通过操纵者决定。这里通过DeleteZero()函数决定。

void DeleteZero(int(&MAP)[MAP_SIZE][MAP_SIZE], int position) {
    /*移动地图(竖直移动)而进行的靠拢, 即删除0*/
    for (int j = 0; j < MAP_SIZE; j++) {
        for (int i = 0 - position; i < MAP_SIZE - 1 - position; i++) {
            if (MAP[abs(i)][j] == 0) {
                //找到从i往后第一个不为0的坐标,然后把其填到当前位置
                for (int zeroPos = i + 1; zeroPos < MAP_SIZE - position; zeroPos++) {
                    if (MAP[abs(zeroPos)][j] != 0) {
                        MAP[abs(i)][j] = MAP[abs(zeroPos)][j];
                        MAP[abs(zeroPos)][j] = 0;
                        break;
                    }
                }
            }
        }
    }
}

原理就是通过position参数将上下移动合并。从0到MAP_SIZE-1不变,从MAP_SIZE-1到0则转化为-MAP_SIZE到0,这样就可以统一了两者的移动方向,移动的话只需要+1即可。然后在寻找坐标的时候取绝对值。

之后的合并也是同理,通过将其在坐标系上化为从-MAP_SIZE-1到MAP_SIZE-1再去绝对值即可。然后对每一个格点判断移动方向上是否有相同的格子,如果有的话就合并。

合并结束后再合并一下因为合并产生的空位,让所有格点靠在一起。

最后再将水平方向上的移动转置以下,回归成原来的水平方向。

判断是否结束

合并操作完以后,就需要判断游戏是否结束了。

bool IsOver(int(&MAP)[MAP_SIZE][MAP_SIZE]) {
    /*判断游戏是否结束*/
    for (int i = 0; i < MAP_SIZE; i++) {
        for (int j = 0; j < MAP_SIZE; j++) {
            if (MAP[i][j] == 0) {
                return false;
            }
        }
    }
    return true;
}

逻辑也很简单,如果MAP中没有0了,那就返回已经空了,否则跳出返回还没空。

最后

以上就是实现游戏2048的一个简易过程。其实过程比较粗糙,很多地方还能够再优化一下,不过目前的程度的确已经足够了,所以就不再继续深入了。

下一步的目标是以这个程序为基础,编写一个可以自动玩2048的AI。

所有代码都放在我的GitHub中,喜欢的麻烦点个赞或者关注噢。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,793评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,567评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,342评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,825评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,814评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,680评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,033评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,687评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,175评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,668评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,775评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,419评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,020评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,206评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,092评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,510评论 2 343

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,596评论 18 139
  • 新年快乐。 感谢所有偶然的必然。2016很特别 在2016 我们互相认识啦。去年遇到很多不好的人和事情、绝望的人生...
    今天也再见阅读 459评论 0 0
  • 李笑来说:时间在惩罚愚蠢的时候,只能毫不留情。 这个说法很有震撼力,想明白这句话传达的意思后,真的挺让人震惊的。当...
    时间与区间阅读 629评论 0 0
  • 又要到这周做周检视的时候了,周检视最重要的三件事就是 1. 查看日历 2. 查看清单 3. 清空收件箱 周见识不仅...
    Ivanlu666阅读 227评论 0 1