C语言实现2048游戏(Windows版)

一、游戏介绍

二、游戏规则

三、核心算法

1、方块的移动和合并

代码语言:JavaScript代码运行次数:0运行复制

 主要思想:把游戏数字面板抽象成4行4列的二维数组a[4][4],值为0的位置表示空方块,其他表示对应数字方块。把每一行同等对待,只研究一行的移动和合并算法,然后可以通过遍历行来实现所有行的移动合并算法。在一行中,用b[4]表示一行的一维数组,使用两个下标变量来遍历列项,这里使用j和k,其中j总在k的后面,用来寻找k项后面第一个不为0的数字,而k项用于表示当前待比较的项,总是和j项之间隔着若干个数字0,或者干脆紧挨着。不失一般性,考虑往左滑动时,初始情况下j等于1,而k等于0,接着判断j项数字是否大于0,若是,则判断j项和k项数字的关系,分成3种情况处理,分别是(合并)P1: b[k]==b[j],(移动)P2: b[k]==0和(碰撞)P3: b[k]!=0且b[k]!=b[j];若否,则j自加1,然后继续寻找k项后面第一个不为0的数字。 其中P1,P2和P3分别对应如下: (合并)P1:b[k]==b[j],则b[k] = 2 * b[k](说明两数合并了),且b[j] = 0(合并之后要将残留的j项值清零),接着k自加1,然后进行下一次循环。 (移动)P2:b[k]==0,则表示b[j]之前全是空格子,此时直接移动b[j]到k的位置,也就是b[k] = b[j],然后b[j] = 0(移动后将残留的j项值清零),接着k值不变,然后进行下一次循环。 (碰撞)P3:b[k]!=0且b[k]!=b[j],则表示两数不相等且都不为0,此时将两数靠在一起,也就是b[k+1] = b[j]。接着分两种小情况,若j!=k+1,则b[j] = 0(移动后将残留的j项值清零);若否,则表示两数原先就靠在一起,则不进行特殊处理(相当于未移动)。接着k自加1,然后进行下一次循环。 举一个P1的例子,流程表示如下: 一行内移动合并算法描述如下(此例为左移情况,其他方向与之类似,区别仅仅是遍历二维数组的行项和列项的方式):

代码语言:javascript代码运行次数:0运行复制

int j, k;for (j = 1, k = 0; j  0) { /* 找出k后面第一个不为空的项,下标为j,之后分三种情况 */        if (b[k] == b[j]) { /* P1情况,合并 */            b[k] = 2 * b[k];            b[j] = 0;            k = k + 1;        } else if (b[k] == 0) { /* P2情况,移动 */            b[k] = b[j];            b[j] = 0;        } else { /* P3情况,碰撞 */            b[k + 1] = b[j];            if (j != k + 1) { /* 原先两数不挨着 */                b[j] = 0;            }            k = k + 1;        }    }}

2、判断游戏是否结束

立即学习C语言免费学习笔记(深入)”;

代码语言:javascript代码运行次数:0运行复制

 核心思想:遍历二维数组,看是否存在横向和纵向两个相邻的元素相等,若存在,则游戏不结束,若不存在,则游戏结束。 算法代码描述如下(board表示真正的游戏源码中使用的二维数组):

代码语言:javascript代码运行次数:0运行复制

/* 检查游戏是否结束 函数定义 */void check_game_over() {    int i;    for (i = 0; i <p>3、生成随机数</p>代码语言:javascript<i class="icon-code"></i>代码运行次数:<!-- -->0<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewbox="0 0 16 16" fill="none"><path d="M6.66666 10.9999L10.6667 7.99992L6.66666 4.99992V10.9999ZM7.99999 1.33325C4.31999 1.33325 1.33333 4.31992 1.33333 7.99992C1.33333 11.6799 4.31999 14.6666 7.99999 14.6666C11.68 14.6666 14.6667 11.6799 14.6667 7.99992C14.6667 4.31992 11.68 1.33325 7.99999 1.33325ZM7.99999 13.3333C5.05999 13.3333 2.66666 10.9399 2.66666 7.99992C2.66666 5.05992 5.05999 2.66659 7.99999 2.66659C10.94 2.66659 13.3333 5.05992 13.3333 7.99992C13.3333 10.9399 10.94 13.3333 7.99999 13.3333Z" fill="currentcolor"></path></svg>运行<svg width="16" height="16" viewbox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M4.5 15.5V3.5H14.5V15.5H4.5ZM12.5 5.5H6.5V13.5H12.5V5.5ZM9.5 2.5H3.5V12.5H1.5V0.5H11.5V2.5H9.5Z" fill="currentcolor"></path></svg>复制<pre class="prism-token token line-numbers javascript"> 核心思想:根据生成的随机数,对一定的值进行取模,达到生成一定概率的数。在本游戏中,设定4出现的概率为1/10,于是可以利用系统提供的随机数函数生成一个数,然后对10取余,得到的数若大于0则在游戏面板空格处生成一个2,若余数等于0,则生成4。在选择将在哪一个空格出生成数的时候,也是根据系统提供的随机函数生成一个数,然后对空格数取余,然后在第余数个空格出生成数字。 算法代码描述如下(board表示真正的游戏源码中使用的二维数组):

代码语言:javascript代码运行次数:0运行复制

/* 生成随机数 函数定义 */void add_rand_num() {    srand((unsigned int) time(0));    int n = rand() % get_null_count(); /* 确定在何处空位置生成随机数 */    int i;    for (i = 0; i <p>4、绘制界面</p>代码语言:javascript<i class="icon-code"></i>代码运行次数:<!-- -->0<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewbox="0 0 16 16" fill="none"><path d="M6.66666 10.9999L10.6667 7.99992L6.66666 4.99992V10.9999ZM7.99999 1.33325C4.31999 1.33325 1.33333 4.31992 1.33333 7.99992C1.33333 11.6799 4.31999 14.6666 7.99999 14.6666C11.68 14.6666 14.6667 11.6799 14.6667 7.99992C14.6667 4.31992 11.68 1.33325 7.99999 1.33325ZM7.99999 13.3333C5.05999 13.3333 2.66666 10.9399 2.66666 7.99992C2.66666 5.05992 5.05999 2.66659 7.99999 2.66659C10.94 2.66659 13.3333 5.05992 13.3333 7.99992C13.3333 10.9399 10.94 13.3333 7.99999 13.3333Z" fill="currentcolor"></path></svg>运行<svg width="16" height="16" viewbox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M4.5 15.5V3.5H14.5V15.5H4.5ZM12.5 5.5H6.5V13.5H12.5V5.5ZM9.5 2.5H3.5V12.5H1.5V0.5H11.5V2.5H9.5Z" fill="currentcolor"></path></svg>复制<pre class="prism-token token line-numbers javascript"> 核心思想:利用系统提供的控制台界面清屏功能,达到刷新界面的效果,利用控制制表符位置,达到绘制游戏数字面板的效果。 由于绘制界面不算是本游戏的本质,且代码段相对较长,所以算法描述在这里省略,读者可以参考完整源代码。

5、计算得分

代码语言:javascript代码运行次数:0运行复制

 核心思想:两块带数字的方格合并后的数字为合并的得分,一次上下左右移动后游戏面板上所有合并的得分总和为一次移动的得分,多次移动的得分进行累加作为当前总得分。 如果当前总得分(SCORE)超过最高分(BEST),则最高分被改写为当前总得分,并存储下来,下次启动游戏时会自动载入本机存储的最高分。

四、完整源代码如下,敬请读者批评指正:

代码语言:javascript代码运行次数:0运行复制

#include <time.h>   /* 包含设定随机数种子所需要的time()函数 */#include <stdio.h>  /* 包含C的IO读写功能 */#include <stdlib.h> /* 包含C标准库的功能 */#ifdef _WIN32/* 包含windows平台相关函数,包括控制台界面清屏及光标设定等功能 */#include <conio.h>#include <windows.h>#include <io.h>#include <direct.h>#include <shlobj.h>#else/* 包含linux平台相关函数,包括控制台界面清屏及光标设定等功能 */#include <termio.h>#include <unistd.h>#include <bits>#include <signal.h>#define KEY_CODE_UP    0x41#define KEY_CODE_DOWN  0x42#define KEY_CODE_LEFT  0x44#define KEY_CODE_RIGHT 0x43#define KEY_CODE_QUIT  0x71struct termios old_config; /* linux下终端属性配置备份 */#endifstatic char config_path[4096] = {0}; /* 配置文件路径 */static void init_game();    /* 初始化游戏 */static void loop_game();    /* 游戏循环 */static void reset_game();   /* 重置游戏 */static void release_game(int signal); /* 释放游戏 */static int read_keyboard();static void move_left();  /* 左移 */static void move_right(); /* 右移 */static void move_up();    /* 上移 */static void move_down();  /* 下移 */static void add_rand_num();    /* 生成随机数,本程序中仅生成2或4,概率之比设为9:1 */static void check_game_over(); /* 检测是否输掉游戏,设定游戏结束标志 */static int get_null_count();   /* 获取游戏面板上空位置数量 */static void clear_screen();    /* 清屏 */static void refresh_show();    /* 刷新界面显示 */static int board[4][4];     /* 游戏数字面板,抽象为二维数组 */static int score;           /* 游戏得分 */static int best;            /* 游戏最高分 */static int if_need_add_num; /* 是否需要生成随机数标志,1表示需要,0表示不需要 */static int if_game_over;    /* 是否游戏结束标志,1表示游戏结束,0表示正常 */static int if_prepare_exit; /* 是否准备退出游戏,1表示是,0表示否 *//* main函数 函数定义 */int main(int argc, char *argv[]) {    init_game();    loop_game();    release_game(0);    return 0;}/* 读取键盘 函数定义 */int read_keyboard() {#ifdef _WIN32    return _getch();#else    int key_code;    if (read(0, &amp;key_code, 1)  best) {            best = score;            FILE *fp = fopen(config_path, "w");            if (fp) {                fwrite(&amp;best, sizeof(best), 1, fp);                fclose(fp);            }        }        /* 默认为需要生成随机数时也同时需要刷新显示,反之亦然 */        if (if_need_add_num) {            add_rand_num();            refresh_show();        } else if (if_prepare_exit) {            refresh_show();        }    }}/* 重置游戏 函数定义 */void reset_game() {    score = 0;    if_need_add_num = 1;    if_game_over = 0;    if_prepare_exit = 0;    /* 了解到游戏初始化时出现的两个数一定会有个2,所以先随机生成一个2,其他均为0 */    int n = rand() % 16;    int i;    for (i = 0; i  0) /* 找出k后面第一个不为空的项,下标为j,之后分三种情况 */            {                if (board[i][k] == board[i][j]) {                    /* 情况1:k项和j项相等,此时合并方块并计分 */                    score += board[i][k++] *= 2;                    board[i][j] = 0;                    if_need_add_num = 1; /* 需要生成随机数和刷新界面 */                } else if (board[i][k] == 0) {                    /* 情况2:k项为空,则把j项赋值给k项,相当于j方块移动到k方块 */                    board[i][k] = board[i][j];                    board[i][j] = 0;                    if_need_add_num = 1;                } else {                    /* 情况3:k项不为空,且和j项不相等,此时把j项赋值给k+1项,相当于移动到k+1的位置 */                    board[i][++k] = board[i][j];                    if (j != k) {                        /* 判断j项和k项是否原先就挨在一起,若不是则把j项赋值为空(值为0) */                        board[i][j] = 0;                        if_need_add_num = 1;                    }                }            }        }    }}/* 右移 函数定义 */void move_right() {    /* 仿照左移操作,区别仅仅是j和k都反向遍历 */    int i;    for (i = 0; i = 0; --j) {            if (board[i][j] &gt; 0) {                if (board[i][k] == board[i][j]) {                    score += board[i][k--] *= 2;                    board[i][j] = 0;                    if_need_add_num = 1;                } else if (board[i][k] == 0) {                    board[i][k] = board[i][j];                    board[i][j] = 0;                    if_need_add_num = 1;                } else {                    board[i][--k] = board[i][j];                    if (j != k) {                        board[i][j] = 0;                        if_need_add_num = 1;                    }                }            }        }    }}/* 上移 函数定义 */void move_up() {    /* 仿照左移操作,区别仅仅是行列互换后遍历 */    int i;    for (i = 0; i  0) {                if (board[k][i] == board[j][i]) {                    score += board[k++][i] *= 2;                    board[j][i] = 0;                    if_need_add_num = 1;                } else if (board[k][i] == 0) {                    board[k][i] = board[j][i];                    board[j][i] = 0;                    if_need_add_num = 1;                } else {                    board[++k][i] = board[j][i];                    if (j != k) {                        board[j][i] = 0;                        if_need_add_num = 1;                    }                }            }        }    }}/* 下移 函数定义 */void move_down() {    /* 仿照左移操作,区别仅仅是行列互换后遍历,且j和k都反向遍历 */    int i;    for (i = 0; i = 0; --j) {            if (board[j][i] &gt; 0) {                if (board[k][i] == board[j][i]) {                    score += board[k--][i] *= 2;                    board[j][i] = 0;                    if_need_add_num = 1;                } else if (board[k][i] == 0) {                    board[k][i] = board[j][i];                    board[j][i] = 0;                    if_need_add_num = 1;                } else {                    board[--k][i] = board[j][i];                    if (j != k) {                        board[j][i] = 0;                        if_need_add_num = 1;                    }                }            }        }    }}/* 清屏 */void clear_screen() {#ifdef _WIN32    /* 重设光标输出位置清屏可以减少闪烁,system("cls")为备用清屏命令,均为Windows平台相关*/    COORD pos = {0, 0};    SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), pos);    CONSOLE_CURSOR_INFO info = {1, 0};    SetConsoleCursorInfo(GetStdHandle(STD_OUTPUT_HANDLE), &amp;info);#else    printf("33c");     /* linux下的清屏命令 */    printf("33[?25l"); /* linux下的隐藏输入光标 */#endif}/* 刷新界面 函数定义 */void refresh_show() {    clear_screen();    printf("nnnn");    printf("                  GAME: 2048     SCORE: %05d     BEST: %06dn", score, best);    printf("               --------------------------------------------------");    /* 绘制方格和数字 */    printf("nn                             ┌────┬────┬────┬────┐n");    int i;    for (i = 0; i &gt; 1;                        if (n == 1) {                            printf("2^%02d│", k); /* 超过四位的数字用2的幂形式表示,如2^13形式 */                            break;                        }                    }                }            } else printf("    │");        }        if (i <p>五、运行界面如下,仅供读者参考玩乐:</p>代码语言:javascript<i class="icon-code"></i>代码运行次数:<!-- -->0<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewbox="0 0 16 16" fill="none"><path d="M6.66666 10.9999L10.6667 7.99992L6.66666 4.99992V10.9999ZM7.99999 1.33325C4.31999 1.33325 1.33333 4.31992 1.33333 7.99992C1.33333 11.6799 4.31999 14.6666 7.99999 14.6666C11.68 14.6666 14.6667 11.6799 14.6667 7.99992C14.6667 4.31992 11.68 1.33325 7.99999 1.33325ZM7.99999 13.3333C5.05999 13.3333 2.66666 10.9399 2.66666 7.99992C2.66666 5.05992 5.05999 2.66659 7.99999 2.66659C10.94 2.66659 13.3333 5.05992 13.3333 7.99992C13.3333 10.9399 10.94 13.3333 7.99999 13.3333Z" fill="currentcolor"></path></svg>运行<svg width="16" height="16" viewbox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M4.5 15.5V3.5H14.5V15.5H4.5ZM12.5 5.5H6.5V13.5H12.5V5.5ZM9.5 2.5H3.5V12.5H1.5V0.5H11.5V2.5H9.5Z" fill="currentcolor"></path></svg>复制<pre class="prism-token token line-numbers javascript"> 其中,按方向键,或者w、s、a、d键为上、下、左、右移动,按q键为退出游戏。
C语言实现2048游戏(Windows版)

© 版权声明
THE END
喜欢就支持一下吧
点赞14 分享