2048是一款数字益智游戏,玩家需要通过滑动方块来合并相同数字的方块,最终得到数字2048的方块。下面是2048小游戏的核心思路:

概要:

  1. 游戏面板:2048游戏面板是一个4x4的方格网格,初始状态有两个随机生成的数字方块,每个方块上有数字2或4。

  2. 移动方块:玩家可以通过滑动方向键或触摸屏来控制方块的移动方向。每一次移动操作,所有的方块都会在所选择的方向上移动尽可能远的距离,直到遇到其他方块或边界。

  3. 合并方块:当两个相同数字的方块在移动过程中碰撞时,它们会合并为一个新的方块,数字是原来两个方块数字的和。

  4. 生成新方块:每次移动之后,游戏会生成一个新的数字方块,随机出现在空白格子中。新生成的数字方块只可能是2或4。

  5. 游戏结束判断:当所有的格子都被填满,且相邻的方块不能合并时,游戏结束。

  6. 得分计算:每次合并方块时,玩家会获得对应合并数字的分数。最终得分是所有合并操作得分的总和。

  7. 游戏目标:玩家的目标是合并数字方块,尽可能得到一个2048数字方块。

核心思路

 1.游戏面板

        2048游戏面板是一个4x4的方格网格,初始状态有两个随机生成的数字方块,每个方块上有数字2或4。

这里可以使用一个二维数组存储1~12的数字,用switch语句可代表不同的方块,用for循环双重嵌套输出

void show_mb(lv_obj_t* bg) {
    uint8_t margin_x = 0, margin_y = 0;

    for (uint8_t i = 0; i < WIDTH; i++) {
        margin_x = 0;
        for (uint8_t j = 0; j < WIDTH; j++) {
            //printf("%d ", sz[i][j]);
            show_color_block(sz[i][j], margin_x + j * BLOCKH, margin_y + i * BLOCKH, bg, i, j);
            margin_x += 5;
        }
        margin_y += 5;
        //printf("\n");
    }
}
switch (n) {
    case 0:
        lv_obj_set_style_bg_color(block, lv_color_hex(0xc6b8ab), LV_STATE_DEFAULT); break;
    case 1:
        lv_obj_set_style_bg_color(block, lv_color_hex(0xece2d8), LV_STATE_DEFAULT); break;
    case 2:
        lv_obj_set_style_bg_color(block, lv_color_hex(0xeadec6), LV_STATE_DEFAULT); break;
    case 3:
        lv_obj_set_style_bg_color(block, lv_color_hex(0xf0af76), LV_STATE_DEFAULT); break;
    case 4:
        lv_obj_set_style_bg_color(block, lv_color_hex(0xec8d54), LV_STATE_DEFAULT); break;
    case 5:
        lv_obj_set_style_bg_color(block, lv_color_hex(0xf67c5f), LV_STATE_DEFAULT); break;
    case 6:
        lv_obj_set_style_bg_color(block, lv_color_hex(0xea5937), LV_STATE_DEFAULT); break;
    case 7:
        lv_obj_set_style_bg_color(block, lv_color_hex(0xf2d76b), LV_STATE_DEFAULT); break;
    case 8:
        lv_obj_set_style_bg_color(block, lv_color_hex(0xf1d04b), LV_STATE_DEFAULT); break;
    case 9:
        lv_obj_set_style_bg_color(block, lv_color_hex(0xe4c02a), LV_STATE_DEFAULT); break;
    case 10:
        lv_obj_set_style_bg_color(block, lv_color_hex(0xdcb82a), LV_STATE_DEFAULT); break;
    case 11:
        lv_obj_set_style_bg_color(block, lv_color_hex(0xd4b02a), LV_STATE_DEFAULT); break;
    }

部分代码实例。


2.移动方块与方块合并

采用lvgl提供的手势函数判定滑动

lv_obj_add_event_cb(lv_scr_act(), event_cb, LV_EVENT_GESTURE, bg);

添加事件

if (code == LV_EVENT_GESTURE) {
        lv_dir_t dir = lv_indev_get_gesture_dir(lv_indev_get_act());    //手势判定
        if (dir == LV_DIR_TOP) {
            bg_hd(UP, bg);    //向上滑
        }
        else if (dir == LV_DIR_BOTTOM) {
            bg_hd(DOWN, bg);    //向下滑
        }
        else if (dir == LV_DIR_LEFT) {
            bg_hd(LEFT, bg);    //向左滑
        }
        else if (dir == LV_DIR_RIGHT) {
            bg_hd(RIGHT, bg);    //向右滑
        }
    }

在事件函数中判定手势,再调用不同方向的方块移动函数

 方块移动的方式:

        不同方向我们采用类似的扫描方法,这里我们以向上移动为例。

我们从第二行第一列开始扫描,将其元素往第一行移动,如果第一行有元素,则判断是否相同,相同的话就将第一行元素×2,并将第二行元素赋值为0,不相同则位置不动;如果没元素则将第二行赋值给第一行,第二行赋值为0。 (空格默认为0)

 

嵌套循环一遍遍向下循环,下面为部分代码。

uint8_t flag, flag_t = 0;     //不连续合并
    if (toward == UP) {
        for (short j = 0; j < WIDTH; j++) {
            flag = 1;
            for (short i = 1; i < WIDTH; i++) {
                for (short z = i; z > 0; z--) {
                    if (sz[z - 1][j] == sz[z][j] && sz[z][j] != 0 && flag) {
                        sz[z - 1][j] += 1;
                        cnt--;
                        score += (uint16_t)pow(2, sz[z - 1][j]);
                        sz[z][j] = 0;
                        flag = 0;
                        flag_t = 1;
                        if (flag_t != 1)flag_t = 1;
                    }
                    else if (sz[z - 1][j] == 0 && sz[z][j] != 0) {
                        sz[z - 1][j] = sz[z][j];
                        sz[z][j] = 0;
                        if (flag_t != 1)flag_t = 1;
                    }
                }
            }
        }
    }

 3.产生新方块

        这里要用到随机数函数,stm32产生随机数方法有ADC采集法(即ADC采集随机电压), 定时器计数生成法(即利用定时器进行计数,调用函数采集计数)等。这里我们采用定时器计数法。

 

TIM5溢出中断中添加ms计数,让其在1~2^32之间震荡。

  

随机生成方块。 


4.判断游戏结束

         当所有的格子都被填满,且相邻的方块不能合并时,游戏结束。

        首先第一层判定是格子全满,格子全满会调用相邻方块调用函数。这里我们用cnt计数,方块增加时:cnt+1,方块减少是cnt-=1。最后当cnt==WIDTH*WIDTH时,则格子全满。

        然后这里使用相邻方块是否相同判断函数:

bool detect_end() {
    const short a[4][2] = { 0,1,0,-1,1,0,-1,0 };
    for (short i = 0; i < WIDTH; i++) {
        for (short j = 0; j < WIDTH; j++) {
            for (short z = 0; z < 4; z++) {
                if (i + a[z][0] < WIDTH && j + a[z][1] < WIDTH && i + a[z][0] >= 0 && j + a[z][1] >= 0) {
                    if (sz[i][j] == sz[i + a[z][0]][j + a[z][1]]) {
                        return 0;
                    }
                }
            }
        }
    }
    return 1;
}

 若都满足则调用结算函数(部分)

void end_ami(lv_obj_t* bg) {

    lv_obj_add_flag(lv_layer_top(), LV_OBJ_FLAG_CLICKABLE);
    end_main = lv_obj_create(lv_layer_top());
    lv_obj_set_style_bg_opa(lv_layer_top(), LV_OPA_50, 0);                          // 设置对象透明度
    lv_obj_set_style_bg_color(lv_layer_top(), lv_palette_main(LV_PALETTE_GREY), 0); //灰色背景
    lv_obj_set_style_bg_color(end_main, lv_color_hex(0xb2a296), LV_STATE_DEFAULT);
    lv_obj_set_size(end_main, 140, 150);
	lv_obj_clear_flag(end_main, LV_OBJ_FLAG_SCROLLABLE);
    lv_obj_set_align(end_main, LV_ALIGN_CENTER);
	lv_obj_t* score_l = lv_label_create(end_main);
	lv_label_set_recolor(score_l, true);
	lv_label_set_text(score_l, "#000000 SCORE#");
	lv_obj_align(score_l, LV_ALIGN_TOP_MID, 0, 10);
	lv_obj_t* score_s = lv_label_create(end_main);
	static lv_style_t font_style1;
	lv_style_init(&font_style1);
	lv_label_set_recolor(score_s, true);
	lv_label_set_text_fmt(score_s, "#000000 %d#",score);
	lv_obj_add_style(score_s, &font_style1, LV_STATE_DEFAULT);
	lv_obj_align(score_s, LV_ALIGN_BOTTOM_MID, 0, -70);
	lv_obj_t* regame = lv_obj_create(end_main);
	lv_obj_set_size(regame, 100, 40);
	lv_obj_set_style_bg_color(regame,lv_color_hex(0x1E90FF), LV_STATE_DEFAULT);
	lv_obj_align(regame,LV_ALIGN_BOTTOM_MID, 0, -10);
	lv_obj_t* retry = lv_label_create(regame);
	lv_label_set_recolor(retry, true);
	lv_label_set_text(retry, "#000000 RETRY#");
	lv_obj_clear_flag(regame, LV_OBJ_FLAG_SCROLLABLE);
	lv_obj_align(retry, LV_ALIGN_CENTER,0,0);
	lv_obj_add_event_cb(regame, event_cb, LV_EVENT_CLICKED, bg);
}

5.得分计算

        这里使用全局变量score记录得分,当方块合并时score+=方块数字。实时得分会在左上角显示,部分代码:

    lv_obj_t* score_block = lv_obj_create(lv_scr_act());
    lv_obj_set_size(score_block, 50, 50);
    lv_obj_align(score_block,LV_ALIGN_TOP_LEFT,90,10);
    lv_obj_set_style_bg_color(score_block,lv_color_hex(0xb8afa0), LV_STATE_DEFAULT);


    lv_obj_t* score_l = lv_label_create(score_block);
    static lv_style_t font_style;
    lv_style_init(&font_style);
    lv_style_set_text_font(&font_style, &lv_font_montserrat_12);
    lv_obj_add_style(score_l, &font_style, LV_STATE_DEFAULT);
    p_score = score_block;
    lv_label_set_recolor(score_l, true);
    lv_label_set_text(score_l, "#f0e4d8 SCORE#");
    lv_obj_clear_flag(score_block, LV_OBJ_FLAG_SCROLLABLE);
    lv_obj_align(score_l, LV_ALIGN_TOP_MID, 0, -10);

    lv_obj_t* score_s = lv_label_create(score_block);
    static lv_style_t font_style1;
    lv_style_init(&font_style1);
    lv_style_set_text_font(&font_style1, &lv_font_montserrat_16);
    lv_label_set_recolor(score_s, true);
    lv_label_set_text_fmt(score_s, "#ffffff %d#",score);
    lv_obj_add_style(score_s, &font_style1, LV_STATE_DEFAULT);
    lv_obj_align(score_s, LV_ALIGN_BOTTOM_MID, 0, 5);


总结:

        本章为核心思路以及部分代码实现,后续全部代码会发出,下篇文章会给出一些细节注意,以及避坑指南。

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐