C++实现2048小游戏(使用ncurses库)

开发要点

  1. 一次只能合并相邻的两个数字,例如 [2 2 2 2] ,向右合并以后是 [空 空 4 4] ,不是 [空 空 空 8]
  2. 每次合并的时候,合并方向优先级高,例如 [空 2 2 2],向右合并以后是 [空 空 2 4],不是 [空 空 4 2]
  3. 判断游戏胜利或者失败
  4. 每次合并以后随机新出4的概率10%

开发步骤

  1. 引入curses库
  2. 绘制游戏界面
  3. 游戏状态切换
  4. 重启初始化游戏
  5. 向左移动
  6. 向其他方向移动
  7. 游戏胜负判定

扩展

记录最高分

代码

#include <string>
#include <vector>
#include <cstdlib>	// srand() rand()
#include <cstdio>	// ?
#include <ctime>
#include <curses.h>
using namespace std;

// 格子数
#define N 4
// 每个格子的字符长度
#define WIDTH 5

// 游戏状态
#define S_FAIL	0
#define S_WIN	1
#define S_NORMAL 2
#define S_QUIT	3

// 胜利条件
#define TARGET 2048

class Game2048
{
public:
	Game2048() : status(S_NORMAL)
	{
		setTestData();
	}

	int getStatus()
	{
		return status;
	}

	// 处理按键输入
	void processInput()
	{
		char ch = getch();
		// 小写转换成大写
		if (ch >= 'a' && ch <= 'z')
			ch -= 32;

		if (status == S_NORMAL)
		{
			bool updated = false;
			if (ch == 'A')
				updated = moveLeft();
			// 向下移动等同于把下方旋转成左方,向左移动后再旋转回下方
			else if (ch == 'S')
			{
				rotate();	// 逆时针旋转270度
				rotate();
				rotate();
				updated = moveLeft();
				rotate();
			}
			else if (ch == 'D')
			{
				rotate();
				rotate();
				updated = moveLeft();
				rotate();
				rotate();
			}
			else if (ch == 'W')
			{
				rotate();
				updated = moveLeft();
				rotate();
				rotate();
				rotate();
			}

			if (updated)
			{
				randNew();	// 如果发生改变则产生新随机数
				if (isOver())	// 判断游戏是否结束
					status = S_FAIL;
			}
		}

		if (ch == 'Q')
			status = S_QUIT;
		else if (ch == 'R')
			restart();
		//else
		//	status = (status + 1) % 3;	// 遍历其他三种状态
	}

	// 绘制游戏界面
	void draw()
	{
		// 清理屏幕
		clear();
		// 居中偏移
		const int offset = 12;

		for (int i = 0; i <= N; ++i)
		{
			for (int j = 0; j <= N; ++j)
			{
				// 相交点
				drawItem(i * 2, j * WIDTH + 1 + offset, '+');
				// 竖线
				if (i != N)
					drawItem(i * 2 + 1, j * WIDTH + 1 + offset, '|');
				// 横线
				for (int k = 1; j != N && k < WIDTH; ++k)
					drawItem(i * 2, j * WIDTH + 1 + k + offset, '-');
				// 格子里的数
				if (i != N && j != N)
					drawNum(i * 2 + 1, (j + 1) * WIDTH + offset, data[i][j]);
			}
		}

		// 提示文字
		mvprintw(2 * N + 2, (5 * (N - 4) - 1) / 2, "W(UP),S(DOWN),A(LEFT),D(RIGHT),R(RESTART),Q(QUIT)");
		mvprintw(2 * N + 3, 12 + 5 * (N - 4) / 2, "https://www.nowcoder.com");

		// 输赢显示
		if (status == S_FAIL)
			mvprintw(N, 5 * N / 2 - 1, "YOU FAIL,PRESS R TO RESTART");
		if (status == S_WIN)
			mvprintw(N, 5 * N / 2 - 1, "YOU WIN,PRESS R TO RESTART");
	}

	// 设置测试数据
	void setTestData()
	{
		for (int i = 0; i < N; ++i)
		{
			for (int j = 0; j < N; ++j)
			{
				data[i][j] = 16 << i << j;	// 16*2^i*2^j
				// data[i][j] = 2 << (1 + rand() % 7);
			}
		}
	}

private:
	// 判断游戏是否已经结束
	bool isOver()
	{
		for (int i = 0; i < N; ++i)
		{
			for (int j = 0; j < N; ++j)
			{
				// 相邻数字有空位或者相等则未结束
				if ((j + 1 < N) && (data[i][j] * data[i][j + 1] == 0 || data[i][j] == data[i][j + 1]))
					return false;
				if ((i + 1 < N) && (data[i][j] * data[i+1][j] == 0 || data[i][j] == data[i+1][j]))
					return false;
			}
		}

		return true;
	}

	// 增加判断每次移动后是否发生变化
	bool moveLeft()
	{
		int tmp[N][N];
		for (int i = 0; i < N; ++i)
		{
			// 对每一行的数字分别进行处理
			// 相同的两个数字融合后应该存入的位置
			int currentWriteIndex = 0;
			// 保存两个数字组成对组的第一个数字
			int lastValue = 0;
			for (int j = 0; j < N; ++j)	// 考虑3种情况
			{
				tmp[i][j] = data[i][j];

				if (data[i][j] == 0)	// 1
					continue;

				if (lastValue == 0)		// 2
					lastValue = data[i][j];
				else // 3
				{
					if (lastValue == data[i][j])
					{
						data[i][currentWriteIndex] = lastValue * 2;
						lastValue = 0;

						if (data[i][currentWriteIndex] == TARGET)	//出现2048则胜利
							status = S_WIN;
					}
					else
					{
						data[i][currentWriteIndex] = lastValue;
						lastValue = data[i][j];
					}
					++currentWriteIndex;
				}

				data[i][j] = 0;	// 把查过的值赋0
			}

			if (lastValue != 0)
				data[i][currentWriteIndex] = lastValue;
		}

		for (int i = 0; i < N; ++i)
		{
			for (int j = 0; j < N; ++j)
			{
				if (data[i][j] != tmp[i][j])
					return true;
			}
		}

		return false;
	}

	// 逆时针旋转90度
	void rotate()
	{
		int tmp[N][N];
		for (int i = 0; i < N; ++i)
		{
			for (int j = 0; j < N; ++j)
			{
				tmp[i][j] = data[j][N - 1 - i];	// 画个具体图示理解
			}
		}

		for (int i = 0; i < N; ++i)
		{
			for (int j = 0; j < N; ++j)
			{
				data[i][j] = tmp[i][j];
			}
		}
	}

	// 重新开始
	void restart()
	{
		for (int i = 0; i < N; ++i)
		{
			for (int j = 0; j < N; ++j)
			{
				data[i][j] = 0;
			}
		}
		randNew();
		randNew();
		status = S_NORMAL;
	}

	// 产生一个随机数
	bool randNew()
	{
		// 保存空的位置
		vector<int> emptyPos;
		for (int i = 0; i < N; ++i)
		{
			for (int j = 0; j < N; ++j)
			{
				if (data[i][j] == 0)
					emptyPos.push_back(i * N + j);	// 一维数组表示二位数组
			}
		}

		if (emptyPos.empty())
			return false;

		// 随机取一个空的位置
		int value = emptyPos[rand() % emptyPos.size()];
		// 新产生2或4,4的概率为10%
		data[value / N][value % N] = rand() % 10 == 1 ? 4 : 2;
		return true;
	}

	// 左上角为(0,0),在指定位置画一个字符
	void drawItem(int row, int col, char c)
	{
		move(row, col);
		addch(c);
	}

	// 游戏里的数字是右对齐,row col是数字最后一位所在的位置
	void drawNum(int row, int col, int num)
	{
		while (num > 0)
		{
			drawItem(row, col, num % 10 + '0');
			num /= 10;
			--col;
		}
	}

private:
	int data[N][N];	// 映射数组
	int status;
};

void initialize()
{
	// 初始化屏幕
	initscr();
	// 按键不需要输入回车直接交互
	cbreak();
	// 按键不显示
	noecho();
	// 隐藏光标
	curs_set(0);
	// 随机数
	srand(time(NULL));
}

void shutdown()
{
	// ncursrs清理
	endwin();
}

int main()
{
	initialize();

	Game2048 game;
	do
	{
		game.draw();
		game.processInput();
	} while (game.getStatus() != S_QUIT);

	shutdown();
	return 0;
}

总结

  • 游戏开发要分清每一个步骤要实现的小功能(简单的画字符函数=》界面绘制=》游戏状态=》具体的实现逻辑),同时设置合理的测试函数去检验功能的实现完善与否。
  • 注意宏定义的使用以方便后续程序的修改与扩展。
  • 使用一个类来囊括所有应该包括的功能。

参考

用C++开发2048小游戏

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
C++中编写2048游戏,你需要了解基本的游戏规则和游戏循环结构。以下是简要的步骤: 1. **游戏基础**: - 创建一个二维数组(如二维整型数组)来表示游戏网格每个格子存储数字(初始为0,之后根据操作随机填充1或2)。 - 定义一些游戏状态,如:移动(up, down, left, right)、合并、游戏结束等。 2. **用户输入处理**: - 使用`cin`或`getch()`函数获取玩家的方向选择,将其转换为相应的坐标(例如,上方向为-1, 0)。 3. **游戏逻辑**: - 根据玩家输入计算新的位置,并更新网格。检查是否有合并发生,如果发现合并,将新值写回网格并更新得分。 - 如果没有合并且无法继续移动(如到达边界或者无法找到合并),游戏结束。 4. **显示游戏状态**: - 使用控制台输出或图形化(如ncurses或SFML)来显示当前的游戏网格和得分。 5. **循环和错误处理**: - 在一个无限循环中进行游戏,直到玩家选择退出或者游戏结束。记得处理可能出现的边界错误和其他异常情况。 6. **游戏结束处理**: - 游戏结束后,展示最终分数和是否达到最大值(例如2048)的信息。 以下是一个简化版的代码框架示例(仅提供基本概念,未包含完整的错误检查和优化): ```cpp #include <iostream> #include <vector> using namespace std; // 定义游戏网格 const int SIZE = 4; vector<vector<int>> grid(SIZE, vector<int>(SIZE)); // 方向枚举 enum class Direction { Up, Down, Left, Right }; void move(Direction dir); bool isGameOver(); void display(); int main() { // 初始化游戏网格,添加起始值 grid = grid = 2; // 主游戏循环 while (!isGameOver()) { cout << "Choose a direction: "; char input; cin >> input; Direction direction = convertInputToDirection(input); move(direction); display(); } // 游戏结束,显示最终结果 return 0; } // ...其他游戏逻辑函数... ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值