昨天写了一个2048小程序,玩起来没有什么明显的bug。今天及时的做一个分析。
首先2048的小游戏逻辑十分清晰。可以把其中要实现的要点归为以下几点:
1.需要一个4x4的棋盘。并且需要展示它的函数。
2.需要一个随机坐标的产生,该坐标在棋盘上的空闲位置产生。
3.需要四个可以操作数据的函数:up(),down(),left(),right(),操作内容为数据合并。
4.需要一个可以检查棋盘的函数,该函数检查两个内容,一个是操作是否成功移动数字,成功就增加新的随机数字,不成功将继续游戏;另一个内容是检查游戏是否结束。
游戏的流程也代表main()函数的内容。
1,首先介绍信息,比如玩法
2,产生一个带有初始数字的棋盘并且打印出来。
3,等待用户输入字符,根据字符判断应该执行什么操作。(上下左右?)
4,执行操作之后进行检查。
5,游戏结束。
流程实现思路:
第一点介绍信息,选择用I/O对象cout打印出来。
第二点产生一个棋盘并且展示它,我选择使用一个game类来实现,该类包含一个私有成员——二维数组chessboard,其中的值反应该位置的值。因为要展示该数组,所以需要提供一个访问权限为public的showchessboard()函数来按照一定格式打印该函数。
第三点等待用户输入字符,可以用while(cin>>char),来实现反复输入,对于输入的字符进行判断,不是w/a/s/d就全部抛弃字符重新输入,如果是w/a/s/d的话就有两种情况,一种是大写的,一种就是小写的,需要都转化为大写的,然后用switch语句完成不同字符对应的不同操作。该操作为game类的四种public函数,实现向上移动,向下移动,向左移动,向右移动。
第四点
操作完成后,有可能出现两种情况,一种是操作生效了,存在数字被移动了,另一种是操作失效了,没有数字被移动,如果操作失效了,没有数字被移动,那么就不能产生随机位置的新数字,如果生效了,就可以产生随机位置的新数字,产生随机位置的新数字因为多次用到,所以封装为函数add(),该函数被该类的public函数调用,而不能被用户调用,所以访问权限是private。
操作有效性判断完毕之后,应该检查游戏是否需要继续。该游戏不能继续的条件是 棋盘已满&&不能合并。依次判断即可,有任何一个不满足都可以继续。
第五点打印游戏结束信息。
缺点:
未考虑积分系统。
未考虑如何退出。
未考虑如何重开游戏。
未将add()函数访问权限设置为private,对游戏产生威胁。
计划:完成C++的基础学习之后,重新写一遍2048,完善上述缺点。
头文件:
//game_2048.h
#pragma once
#define Size 4
class game_2048
{
private:
int chessboard[Size][Size];//私有成员棋盘
int chessboardB[Size][Size];//上次操作棋盘的记录。用于判断操作是否生效
long score;//积分
bool is_eliminated;
int elimination_level;//连续消除等级,影响新生成的数字和分数
void product_number();//产生随机坐标,被内部函数所调用
public:
game_2048();//构造函数
void show_chessboard();//展示棋盘
//四个移动合并操作
void up();
void down();
void left();
void right();
bool is_change();//判断棋盘的变化,产生变化返回true,否则返回false
bool gameover_check();//游戏结束检查
void restart();//重新开始
//返回游戏结果
long get_score() { return score; }
int get_maxnumber();
};
void delay_time(double t);
源文件:
//main.cpp
#include"game_2048.h"
#include <iostream>
int main()
{
using std::cout;
using std::cin;
using std::endl;
cout << "=====================" << endl;
cout << " **** **** * * **** " << endl;
cout << " * * * * * * * " << endl;
cout << " **** * * **** **** " << endl;
cout << " * * * * * * " << endl;
cout << " **** **** * **** " << endl;
cout << " by Mr.W " << endl;
cout << "=====================" << endl;
delay_time(6.0);
system("cls");
game_2048 game;//创建对象
char choice;
bool is_WASDR = NULL;
while (cin.get(choice))
{
is_WASDR = true;
choice = toupper(choice);//输入转化,否则就得考虑大小写都作为case
switch (choice)
{
case 'W':game.up();
break;
case 'A':game.left();
break;
case 'S':game.down();
break;
case 'D':game.right();
break;
default:is_WASDR = false;
break;
}
if (choice == 'R')
{
while (cin.get() != '\n');//多余输入擦除
if (!game.gameover_check()) //检查
break;
cout << "If restart?[Y/N]";
if (cin.get() == 'Y')
{
cin.get();
system("cls");
cout << "=====================" << endl;
cout << " **** **** * * **** " << endl;
cout << " * * * * * * * " << endl;
cout << " **** * * **** **** " << endl;
cout << " * * * * * * " << endl;
cout << " **** **** * **** " << endl;
cout << " by Mr.W " << endl;
cout << "=====================" << endl;
delay_time(6.0);
system("cls");
game.restart();
continue;
}
}
if (choice == 'E')
exit(0);
else if (choice == 'H')
{
system("cls");
game.show_chessboard();
cout << "Press W/A/S/D to play, R to restart~" << endl;
delay_time(6.0);
}
else if (!is_WASDR)
{
while (cin.get() != '\n')//多余输入擦除
;
//刷新屏幕;
system("cls");
game.show_chessboard();
continue;
}
while (cin.get() != '\n');//多余输入擦除
if (!game.gameover_check()) //检查
break;
//刷新屏幕
system("cls");
game.show_chessboard();
if(choice == 'H')
cout << "输入W/A/S/D合并数字,输入E退出游戏,输入R重新开始~" << endl;
}
cout << "GAME OVER!\nThe max number is " << game.get_maxnumber() << "!" << endl;
cout << "Your score is " << game.get_score() << "!" << endl;
delay_time(10);
system("cls");
if (game.get_maxnumber() > 2048)
{
cout << " ****** ****** ******" << endl;
cout << " * * * " << endl;
cout << " * * * " << endl;
cout << " ****** ****** ******" << endl;
cout << " * * * * * *" << endl;
cout << " * * * * * *" << endl;
cout << " ****** ****** ******" << endl;
cout << "" << endl;
}
delay_time(10);
system("cls");
return 0;
}
//game_2048.cpp
#include"game_2048.h"
#include<cstdlib>
#include<ctime>
#include<iostream>
#include<iomanip>
void game_2048::product_number()
{
int x, y;
srand((unsigned)time(NULL));
do
{
x = rand() % Size;
y = rand() % Size;
} while (chessboard[x][y] != 0);//随机选出棋盘上的非零位置
//根据当前连续消除等级分配新的数字,超过两次就分配4,否则就分配2
if (elimination_level == 0 || elimination_level == 1)
chessboard[x][y] = 2;
else
chessboard[x][y] = 4;
}
bool game_2048::is_change()
{
for (int i = 0; i < Size; i++)
for (int j = 0; j < Size; j++)
if (chessboard[i][j] != chessboardB[i][j])//存在不同就说明发生移动,就返回true
return true;
return false;
}
game_2048::game_2048()
{
elimination_level = 0;//连消等级初始化为0
score = 0;//分数 初始化为零
for (int i = 0; i < Size; i++)
for (int j = 0; j < Size; j++)
chessboard[i][j] = 0; //利用双循环将其盘全部置空
//产生初始的两个数字
product_number();
product_number();
show_chessboard();//展示棋盘
}
void game_2048::show_chessboard()
{
using std::cout;
using std::endl;
using std::setw;
cout << "=====================" << endl;
for (int i = 0; i < Size; i++)
{
for (int j = 0; j < Size; j++)
{
cout << "|" << setw(4) << chessboard[i][j];
}
cout << "|" << endl;
}
cout << "=====================" << endl;
cout << "| |" << endl;
cout << "|" << "连续消除:" << setw(4) << elimination_level << "次 |" << endl;
cout << "=====================" << endl;
}
void game_2048::up()
{
is_eliminated = false;//初始化消除记录,发生消除则为true,根据该量判断消除等级是否增加
for (int i = 0; i < Size; i++)
for (int j = 0; j < Size; j++)
chessboardB[i][j] = chessboard[i][j];
//向上移动的操作选择逐列检查是否存在数字合并
for (int j = 0; j < Size; j++)
for (int i = 0; i < Size - 1; i++)
{
if (chessboard[i][j] != 0)
{
int k;
for (k = i + 1; k < Size && chessboard[k][j] == 0; k++)//锁定该列的下一个非零数
continue;
if (k == Size)//如果没有就跳出该列到下一列
break;
else if (k != Size && chessboard[i][j] == chessboard[k][j])//如果存在且二者相等就合并,从下一行开始检查
{
score += chessboard[i][j];
chessboard[i][j] *= 2;
chessboard[k][j] = 0;
i = (k + 1) - 1;
is_eliminated = true;
}
else if (k != Size && chessboard[i][j] != chessboard[k][j])//如果存在且二者不等,则从后者开始重新检查
i = k - 1;
}
}
//合并数字之后,对棋盘上的数字进行整理
//比如向上合并,就应该每一列的数字都向上移动堆叠成一摞
for (int j = 0; j < Size; j++)
{
int k = 0;//用来自上而下的指示可以堆叠的位置,找到非零数之后,移动到该位置,然后k指向下一行
for (int i = 0; i < Size; i++)
{
if (chessboard[i][j] != 0)
{
int temp;
temp = chessboard[i][j];
chessboard[i][j] = 0;
chessboard[k][j] = temp;
k++;
}
else
continue;
}
}
}
void game_2048::down()
{
is_eliminated = false;//初始化消除记录,发生消除则为true,根据该量判断消除等级是否增加
for (int i = 0; i < Size; i++)
for (int j = 0; j < Size; j++)
chessboardB[i][j] = chessboard[i][j];
//向上移动的操作选择逐列检查是否存在数字合并
for (int j = 0; j < Size; j++)
for (int i = Size-1; i > 0; i--)
{
if (chessboard[i][j] != 0)
{
int k;
for (k = i - 1; k >= 0 && chessboard[k][j] == 0; k--)//锁定该列的下一个非零数
continue;
if (k == -1)//如果没有就跳出该列到下一列
break;
else if (k != -1 && chessboard[i][j] == chessboard[k][j])//如果存在且二者相等就合并,从下一行开始检查
{
score += chessboard[i][j];
chessboard[i][j] *= 2;
chessboard[k][j] = 0;
i = (k - 1) + 1;//本来应该跳到k-1,但是循环体结束i--,所以不能直接跳,要跳到(k-1)加1位置
is_eliminated = true;
}
else if (k != -1 && chessboard[i][j] != chessboard[k][j])//如果存在且二者不等,则从后者开始重新检查
i = k + 1;
}
}
//合并数字之后,对棋盘上的数字进行整理
//比如向上合并,就应该每一列的数字都向上移动堆叠成一摞
for (int j = 0; j < Size; j++)
{
int k = Size - 1;//用来自下而上的指示可以堆叠的位置,找到非零数之后,移动到该位置,然后k指向下一行
for (int i = Size - 1; i >= 0; i--)
{
if (chessboard[i][j] != 0)
{
int temp;
temp = chessboard[i][j];
chessboard[i][j] = 0;
chessboard[k][j] = temp;
k--;
}
else
continue;
}
}
}
void game_2048::left()//将向上移动的函数代码中i、j交换可得
{
is_eliminated = false;//初始化消除记录,发生消除则为true,根据该量判断消除等级是否增加
for (int i = 0; i < Size; i++)
for (int j = 0; j < Size; j++)
chessboardB[i][j] = chessboard[i][j];
//向左移动的操作选择逐列检查是否存在数字合并
for (int j = 0; j < Size; j++)
for (int i = 0; i < Size - 1; i++)
{
if (chessboard[j][i] != 0)
{
int k;
for (k = i + 1; k < Size && chessboard[j][k] == 0; k++)//锁定该行的下一个非零数
continue;
if (k == Size)//如果没有就跳出该行到下一行
break;
else if (k != Size && chessboard[j][i] == chessboard[j][k])//如果存在且二者相等就合并,从下一列开始检查
{
score += chessboard[j][i];
chessboard[j][i] *= 2;
chessboard[j][k] = 0;
i = (k + 1) - 1;
is_eliminated = true;
}
else if (k != Size && chessboard[j][i] != chessboard[j][k])//如果存在且二者不等,则从后者开始重新检查
i = k - 1;
}
}
//合并数字之后,对棋盘上的数字进行整理
//比如向左合并,就应该每一列的数字都向左移动堆叠成一摞
for (int j = 0; j < Size; j++)
{
int k = 0;//用来自左而右的指示可以堆叠的位置,找到非零数之后,移动到该位置,然后k指向下一行
for (int i = 0; i < Size; i++)
{
if (chessboard[j][i] != 0)
{
int temp;
temp = chessboard[j][i];
chessboard[j][i] = 0;
chessboard[j][k] = temp;
k++;
}
else
continue;
}
}
}
void game_2048::right()//将向下移动的函数代码中i、j交换可得
{
is_eliminated = false;//初始化消除记录,发生消除则为true,根据该量判断消除等级是否增加
for (int i = 0; i < Size; i++)
for (int j = 0; j < Size; j++)
chessboardB[i][j] = chessboard[i][j];
//向右移动的操作选择逐列检查是否存在数字合并
for (int j = 0; j < Size; j++)
for (int i = Size-1; i > 0;i--)
{
if (chessboard[j][i] != 0)
{
int k;
for (k = i - 1; k >= 0 && chessboard[j][k] == 0; k--)//锁定该行的下一个非零数
continue;
if (k == -1)//如果没有就跳出该行到下一行
break;
else if (k != -1 && chessboard[j][i] == chessboard[j][k])//如果存在且二者相等就合并,从下一列开始检查
{
score += chessboard[j][k];
chessboard[j][i] *= 2;
chessboard[j][k] = 0;
i = (k-1) + 1;
is_eliminated = true;
}
else if (k != -1 && chessboard[j][i] != chessboard[j][k])//如果存在且二者不等,则从后者开始重新检查
i = k+1;
}
}
//合并数字之后,对棋盘上的数字进行整理
//比如向右合并,就应该每一列的数字都向右移动堆叠成一摞
for (int j = 0; j < Size; j++)
{
int k = Size - 1;//用来自右而左的指示可以堆叠的位置,找到非零数之后,移动到该位置,然后k指向下一行
for (int i = Size - 1; i >= 0; i--)
{
if (chessboard[j][i] != 0)
{
int temp;
temp = chessboard[j][i];
chessboard[j][i] = 0;
chessboard[j][k] = temp;
k--;
}
else
continue;
}
}
}
bool game_2048::gameover_check()
{
//刷新连消等级
if (is_eliminated)
elimination_level++;
else
elimination_level = 0;
if (!is_change())
return true;
product_number();//产生新数字
//判断两步:是否满,能否能继续合并?
//第一步,棋盘存在空位,就返回true,满了就继续判断下一步
for (int i = 0; i < Size; i++)
for (int j = 0; j < Size; j++)
if (chessboard[i][j] == 0)
return true;
//第二步,是否能继续合并
//该工作分为两部分,一部分是检查边上的元素的合并性,一部分是检查中心四个元素和周围十二个元素的合并性
//part1:第一行
for (int i = 0, j = 0; j < Size - 1; j++)
if (chessboard[i][j] == chessboard[i][j+1])
return true;
//part2:最后一行
for (int i = Size-1, j = 0; j < Size - 1; j++)
if (chessboard[i][j] == chessboard[i][j+1])
return true;
//part3:第一列(可将第一行中的i、j交换)
for (int i = 0, j = 0; j < Size - 1; j++)
if (chessboard[j][i] == chessboard[j+1][i])
return true;
//part4:最后一列(可将最后一行的i、j交换)
for (int i = Size - 1, j = 0; j < Size - 1; j++)
if (chessboard[j][i] == chessboard[j+1][i])
return true;
//part5:中心四个元素和四周元素的合并性
for (int i = 1; i < Size - 1; i++)
for (int j = 1; j < Size - 1; j++)
if (chessboard[i][j] == chessboard[i][j + 1] || chessboard[i][j] == chessboard[i][j - 1] || chessboard[i][j] == chessboard[i + 1][j] || chessboard[i][j] == chessboard[i - 1][j])
return true;
return false;//panduan
}
void game_2048::restart()
{
elimination_level = 0;//连消等级初始化为0
score = 0;//分数 初始化为零
for (int i = 0; i < Size; i++)
for (int j = 0; j < Size; j++)
chessboard[i][j] = 0; //利用双循环将其盘全部置空
//产生初始的两个数字
product_number();
product_number();
show_chessboard();//展示棋盘
}
int game_2048::get_maxnumber()
{
int max = 0;
for (int i = 0; i < Size; i++)
for (int j = 0; j < Size; j++)
if (max < chessboard[i][j])
max = chessboard[i][j];
return max;
}
void delay_time(double t)
{
clock_t start = clock();
clock_t delay = (clock_t)t * CLOCKS_PER_SEC;
while (clock() - start < delay)
continue;
}