昨天写了一个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;
}