C++ 2048 是一个基于命令行操作的2048小游戏

游戏设计

相信 2048 大家都听说过,这篇文章会介绍如何使用 C++ 控制台应用程序实现 2048 的游戏。

首先,作为一个 2048 游戏,我们需要有如下的功能

  • 接受用户上、左、下、右键、ESC键的操作
  • 将操作反馈到命令行界面中

但是,跟所有的控制台程序一样,C++ 控制台也是无法只修改特定部分的内容的。换句话说,每一次检测到用户按键并且游戏执行完内部的操作之后,我们都需要清空命令行,并且重新输出所有的数据

代码架构

为了实现如上的设计,我们可以把整个游戏拆成两部分:

  • main.cpp
  • Grid.h
  • Grid.cpp

其中 main.cpp 起到一个调用游戏关键文件的作用,并且会调用函数检测用户输入,从而执行游戏内的特定操作。

Grid.h 为 Grid.cpp 的头文件。我们会在这里定义整个游戏的关键的函数,然后这些函数会在 Grid.cpp 里面进行实现。

下面,我会仔细介绍一下这些函数。

Grid.cpp 函数

首先,Grid.cpp 的内容如下:

#pragma once
#include <iostream>
#include <vector>

using namespace std;

struct GridLocation {
	int x;
	int y;
};

class Grid {
public:
	Grid();
	void left();
	void right();
	void up();
	void down();
	void clear();
	string structure();

private:
	vector<vector<int>> grid;
	vector<GridLocation> gl;
	void buildVector();
	void generateAPlace();
	void deleteGridLocation(int i, int k);
};

可以看到,我们定义了一个类Grid,这个Grid存放的就是我们2048游戏的主要方法,以及数据。

首先我们看我们定义的结构体&数据:

  • vector<vector<int>> grid;
  • struct GridLocation
  • vector<GridLocation> gl;

其中 grid 是一个 2D vector,它会被用来存储一个 4*4 的棋盘,其中每个位置存放这个位置的数字大小;
GridLocation 顾名思义,存储了一个x,y坐标,通过这个x,y坐标就可以帮我们找到一个 grid 中的任意一个点;
gl 则是一个1维的 vector,它的作用主要是存储一些在 grid 中还未被使用的点。这些点就是我们在进行按键之后,会生成新数字的可能位置。

接下来我们看一下 Grid 类的方法:

① 首先看Grid();

顾名思义,这是整个类的构造函数,其作用呢就是初始化 grid 以及 gl。它会从 grid 中的 16 个格子随机选取两个,将他们的值改为 2. 其余部分的值则默认为 0.

void left() & void right() & void up() & void down()

顾名思义,这四个函数为用户的左、右、上、下按键提供了具体地操作方法。他们会在执行的时候修改 grid 中的数据,从而实现特定的效果

void clear();

这个函数,主要是对程序接收到用户按下ESC键后所进行的操作,他会初始化整个棋盘,并且重置游戏进度

string structure();

因为命令行的特性,我们每次都需要重新输出棋盘。这个函数就是提供了把棋盘按格式化输出的方法

buildVector();

这个 private 函数提供了构建 grid 的方法,在Grid();以及void clear();函数中,它们都会调用buildVector();来具体构建好 grid

void generateAPlace();

根据他的名字我们就可以知道,它是用来随机选取一个 grid 中的空位置,并且向这个位置中存放一个数字 2. 具体来说,他会从 gl 向量中随机选取一个元素,并且将这个元素所对应的 grid 的位置填入一个数字 2.

void deleteGridLocation(int i, int k);

因为每次将 gl 中随机选区的一个元素的位置填入2之后,这个元素就不再是空的了,所以我们需要使用这个函数将 gl 中已经被使用的元素删掉。其中 i,k 对应 GridLocation 的x,y。这个函数会比对x,y是否与i,k相等来判断是否要删除这个元素。

代码构建

下面是所有部分的代码:

main.cpp:

#include <iostream>
#include <string>
#include "Grid.h"
#include <conio.h>

using namespace std;

void getInput(Grid& g) {
	int ch = _getch();
	while (ch != 27 && ch != 87 && ch != 119 && ch != 65 && ch != 97 && ch != 83 && ch != 115 && ch != 68 && ch != 100) {
		ch = _getch();
	}

	if (ch == 27) { g.clear(); }
	else if (ch == 87 || ch == 119) {
		g.up();
	}
	else if (ch == 65 || ch == 97) {
		g.left();
	}
	else if (ch == 83 || ch == 115) {
		g.down();
	}
	else if (ch == 68 || ch == 100) {
		g.right();
	}
}

int main()
{
	Grid g;

	string welcome = "Welcome to 2048, a game based on cpp console. Hope you can have an enjoyable experience here.\nYou can press WASD to control and ESC to restart the game.\n\n\n";

	while (true) {
		
		cout << welcome;
		cout << g.structure();
		
		getInput(g);
		system("cls");
	}
}

Grid.h:

#pragma once
#include <iostream>
#include <vector>

using namespace std;

struct GridLocation {
	int x;
	int y;
};

class Grid {
public:
	/**
	 * Creates a new 2048 Grid.
	 */
	Grid();

	/**
	 * This function moves all squares to left when user press 'A' or 'a'
	 */
	void left();

	/**
	 * This function moves all squares to right when user press 'D' or 'd'
	 */
	void right();

	/**
	 * This function moves all squares upward when user press 'W' or 'w'
	 */
	void up();

	/**
	 * This function moves all squares downward when user press 'S' or 's'
	 */
	void down();

	/**
	 * This function is used to restart a new 2048 game
	 */
	void clear();

	/*
	 * This function is used to generate a string about the content of the vector grid.
	 * The resulting string will be returned and outputed.
	 */
	string structure();

private:

	/**
	 * 2D vector that stores the grid
	 */
	vector<vector<int>> grid;

	/**
	 * A vector that stores all the available place
	 */
	vector<GridLocation> gl;

	/**
	 * A helper function to build an 2048 grid
	 */
	void buildVector();

	/**
	 * A helper function to generate a place to insert the number 2
	 */
	void generateAPlace();

	/**
	 * A helper function to remove used GridLocation point from vector gl.
	 */
	void deleteGridLocation(int i, int k);
};

Grid.cpp

#include "2048 grid.h"
#include <random>
#include <string>
#include <time.h>
#include <conio.h>

using namespace std;

/**
* This function is used to calculate how many digits a number have.
* Then, it will use the count to correct the output data.
 */
int countN(int num) {
	int count = 0;
	while (num != 0) {
		count++;
		num /= 10;
	}

	return count == 0 ? 1 : count;
}

void Grid::deleteGridLocation(int i, int k) {
	for (vector<GridLocation>::iterator iter = gl.begin(); iter != gl.end(); )
	{
		GridLocation tmp = *iter;
		if (tmp.x == i && tmp.y == k) {
			iter = gl.erase(iter);
			break;
		}	
		else
			iter++;
	}
}

void Grid::buildVector() {
	grid = { {0,0,0,0},{0,0,0,0},{0,0,0,0},{0,0,0,0}};
	gl = {};

	for (int i = 0; i < 4; ++i) {
		for (int j = 0; j < 4; ++j) {
			gl.push_back({ i, j });
		}
	}

	int built = 0;
	srand((unsigned)time(NULL));

	while (built < 2) {
		int x = rand() % 4;
		int y = rand() % 4;
		if (grid[x][y] == 0) {
			grid[x][y] = 2;
			built += 1;
			deleteGridLocation(x, y);
		}
	}
}

void Grid::generateAPlace() {
	srand((unsigned)time(NULL));
	int index = -1;

	if(gl.size() != 0) index = rand() % gl.size();
	else {
		cout << "\n\nYou can press any key to restart.";
		while (true) {
			if (_kbhit())
			{
				_getch();
				clear();
				return;
			}
		}
	}
	grid[gl[index].x][gl[index].y] = 2;
	deleteGridLocation(gl[index].x, gl[index].y);
}

Grid::Grid() {
	buildVector();
}

string Grid::structure() {
	string s = "";

	for (int i = 0; i < 4; ++i) {
		for (int j = 0; j < 4; ++j) {
			int count = countN(grid[i][j]);
			for (int m = 0; m < 5 - count + 1; ++m) {
				s += " ";
			}
			s += to_string(grid[i][j]);
			s += " ";
		}
		s += "\n";
	}

	return s;
}

void Grid::clear() {
	buildVector();
}

void Grid::left() {
	bool effectiveMove = false;

	for (int i = 0; i < 4; ++i) {
		for (int k = 1; k < 4; k++) {
			if (grid[i][k] != 0) {
				if (grid[i][k - 1] == 0) {
					effectiveMove = true;
					grid[i][k - 1] = grid[i][k];
					grid[i][k] = 0;
					
					deleteGridLocation(i, k-1);
					gl.push_back({ i,k });

					if (k != 1 && (grid[i][k - 2] == 0 || grid[i][k-2] == grid[i][k-1])) {
						k -= 2;
					}
				}
				else if (grid[i][k - 1] == grid[i][k]) {
					effectiveMove = true;
					grid[i][k - 1] += grid[i][k];
					grid[i][k] = 0;
					gl.push_back({ i,k });
				}
			}
		}
	}
	if(effectiveMove)
		generateAPlace();
}

void Grid::up() {
	bool effectiveMove = false;

	for (int k = 0; k < 4; ++k) {
		for (int i = 1; i < 4; i++) {
			if (grid[i][k] != 0) {
				if (grid[i - 1][k] == 0) {
					effectiveMove = true;
					grid[i - 1][k] = grid[i][k];
					grid[i][k] = 0;

					deleteGridLocation(i - 1, k);
					gl.push_back({ i,k });

					if (i != 1 && (grid[i - 2][k] == 0 || grid[i - 2][k] == grid[i - 1][k])) {
						i -= 2;
					}
				}
				else if (grid[i - 1][k] == grid[i][k]) {
					effectiveMove = true;
					grid[i - 1][k] += grid[i][k];
					grid[i][k] = 0;
					gl.push_back({ i,k });
				}
			}
		}
	}
	if(effectiveMove)
		generateAPlace();
}

void Grid::right() {
	bool effectiveMove = false;

	for (int i = 0; i < 4; ++i) {
		for (int k = 2; k >= 0; k--) {
			if (grid[i][k] != 0) {
				if (grid[i][k + 1] == 0) {
					effectiveMove = true;
					grid[i][k + 1] = grid[i][k];
					grid[i][k] = 0;

					deleteGridLocation(i, k+1);
					gl.push_back({ i,k });

					if (k != 2 && (grid[i][k + 2] == 0 || grid[i][k + 2] == grid[i][k + 1])) {
						k += 2;
					}
				}
				else if (grid[i][k + 1] == grid[i][k]) {
					effectiveMove = true;
					grid[i][k + 1] += grid[i][k];
					grid[i][k] = 0;
					gl.push_back({ i,k });
				}
			}
		}
	}
	if(effectiveMove)
		generateAPlace();
}

void Grid::down() {
	bool effectiveMove = false;

	for (int k = 0; k < 4; ++k) {
		for (int i = 2; i >= 0; i--) {
			if (grid[i][k] != 0) {
				if (grid[i + 1][k] == 0) {
					effectiveMove = true;
					grid[i + 1][k] = grid[i][k];
					grid[i][k] = 0;

					deleteGridLocation(i + 1, k);
					gl.push_back({ i,k });

					if (i != 2 && (grid[i + 2][k] == 0 || grid[i + 2][k] == grid[i + 1][k])) {
						i += 2;
					}
				}
				else if (grid[i + 1][k] == grid[i][k]) {
					effectiveMove = true;
					grid[i + 1][k] += grid[i][k];
					grid[i][k] = 0;
					gl.push_back({ i,k });
				}
			}
		}
	}
	if(effectiveMove)
		generateAPlace();
}

运行效果图

最后运行效果如图所示:
C++ 2048 运行效果

如果想要下载编译好的文件,链接在此:
https://pan.baidu.com/s/1cS20wEvpgIXWaS49hEGKRA
提取码:6666