我的第一篇blog,在我的 拖延 精益求精之下,终于诞生出来了。由于是大二上学期期末做的大作业,代码水平还不够高,所以CV了许多大佬的2048,经过我的一番 魔改 整合修改,所呈现出来了现在的2048。下面先展示一下最终的效果图(●’◡’●)
最开始的2048是没有加入图形库的,只是简简单单的在小黑框做了一个简易到不行的2048
先开始第一版的介绍,但是这些算法的思想和大致的代码,在后面两版都是基本不变的。
1. 随机数(2,4,8…如何去达到随机出现的概率)
根据生成的随机数,对一定的值进行取模,达到生成一定概率的数。
例如:我们设定4出现的概率为1/9,于是可以利用系统提供的随机数函数生成一个数,然后对9取余。得到的数若大于0则在游戏面板空格处生成一个2,若余数等于0,则生成4。
void add_rand_num() {
srand((unsigned int)time(0));
int n = rand() % get_null_count(); /* 确定在何处空位置生成随机数 */
for (int i = 0; i < 4; ++i) {
for (int j = 0; j < 4; ++j) {
/* 定位待生成的位置 */
if (board[i][j] == 0 && n-- == 0) {
//如果n=3,则在第三个空格生成随机数
board[i][j] = (rand() % 9 ? 2 : 4); /* 生成数字2或4,生成概率为8:1 */
return;
}
}
}
}
2. 方块的移动
在一行中,用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的数字。
/*
* 如下四个函数,实现上下左右移动时数字面板的变化算法
* 左和右移动的本质一样,区别仅仅是列项的遍历方向相反
* 上和下移动的本质一样,区别仅仅是行项的遍历方向相反
* 左和上移动的本质也一样,区别仅仅是遍历时行和列互换
*/
/* 左移 函数定义 */
void move_left() {
/* 变量i用来遍历行项的下标,并且在移动时所有行相互独立,互不影响 */
for (int i = 0; i < 4; ++i) {
/* 变量j为列下标,变量k为待比较(合并)项的下标,循环进入时k
for (int j = 1, k = 0; j < 4; ++j) {
if (board[i][j] > 0) /* 找出k后面第一个不为空的项,下标为j,之后分三种情况 */
{
if (board[i][k] == board[i][j]) {
/* 情况1:k项和j项相等,此时合并方块并计分 */
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都反向遍历 */
for (int i = 0; i < 4; ++i) {
for (int j = 2, k = 3; j >= 0; --j) {
if (board[i][j] > 0) {
if (board[i][k] == board[i][j]) {
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() {
/* 仿照左移操作,区别仅仅是行列互换后遍历*/
for (int i = 0; i < 4; ++i) {
for (int j = 1, k = 0; j < 4; ++j) {
if (board[j][i] > 0) {
if (board[k][i] == board[j][i]) {
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都反向遍历 */
for (int i = 0; i < 4; ++i) {
for (int j = 2, k = 3; j >= 0; --j) {
if (board[j][i] > 0) {
if (board[k][i] == board[j][i]) {
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;
}
}
}
}
}
}
3. 游戏页面的展示refresh_show();
根据数字长度、汉字长度、空格长度…一个个尝试出来的
/* 刷新界面 函数定义 */
void refresh_show() {
clear_screen();
printf("\n\n\n\n");
printf(" GAME: 2048 \n");
printf(" --------------------------------------------------");
/* 绘制方格和数字 */
printf("\n\n ┌────┬────┬────┬────┐\n");
int i;
for (i = 0; i < 4; ++i) {
printf(" │");
int j;
for (j = 0; j < 4; ++j) {
if (board[i][j] != 0) {
if (board[i][j] < 10) {
//当元素小于10时,打印
printf(" %d │", board[i][j]);
}
else if (board[i][j] < 100) {
//当元素大于10小于100时打印
printf(" %d │", board[i][j]);
}
else if (board[i][j] < 1000) {
//当元素大于100小于1000时打印
printf(" %d│", board[i][j]);
}
else if (board[i][j] < 10000) {
//当元素大于1000小于10000时打印
printf("%4d│", board[i][j]);
}
else {
int n = board[i][j];
int k;
for (k = 1; k < 20; ++k) {
n = n >> 1;
if (n == 1) {
printf("2^%02d│", k); //超过四位的数字用2的幂形式表示,如2^13形式
break;
}
}
}
}
else printf(" │");
}
if (i < 3) {
printf("\n ├────┼────┼────┼────┤\n");
}
else {
printf("\n └────┴────┴────┴────┘\n");
}
}
printf("\n");
printf(" --------------------------------------------------\n");
printf(" [W]:UP [S]:DOWN [A]:LEFT [D]:RIGHT [Q]:EXIT");
}
4. 记录最高分
Write_max(int); Read_max(); max_score;
static const char* score_max = "2048游戏-最高记录.txt";
用文件来记录最高分并在游戏开始调用出最高分的记录
5.获取空位置
根据随机函数生成一个数,然后对空格数取余,然后在第余数个空格出生成数字。
int get_null_count() {
int n = 0;
for (int i = 0; i < 4; ++i) {
for (int j = 0; j < 4; ++j) {
board[i][j] == 0 ? ++n : 1; //n为空格计数器
}
}
return n;
}
6.判断游戏是否结束
/* 检查游戏是否结束 函数定义 */
void check_game_over() {
int i;
for (i = 0; i < 4; ++i) {
int j;
for (j = 0; j < 3; ++j) {
/* 横向和纵向比较挨着的两个元素是否相等,若有相等则游戏不结束 */
if (board[i][j] == board[i][j + 1] || board[j][i] == board[j + 1][i]) {
if_game_over = 0;
return;
}
}
}
if_game_over = 1;
}
还有零零碎碎的一些刷新函数、重置游戏的函数、介绍规则的、鼠标的按键的……之后我把源码上传到GitHub上。到时候放链接。
第二版的伪代码展示