C++游戏项目二:井字棋

教程来源:【从零开始的C++游戏开发】最简游戏框架 | EasyX制作井字棋小游戏(上)_哔哩哔哩_bilibili

【从零开始的C++游戏开发】动态延时优化性能 | EasyX制作井字棋小游戏(下)_哔哩哔哩_bilibili

1.项目框架

一般的循环内容是: 读取鼠标点击信息——计算落子位置——绘制棋盘、棋子、提示信息——判断是否获胜

2.编写代码框架

实际项目要首先确定框架,明确每个部分的内容,具体代码实现后续再编写。

根据循环内容写出如下框架:

#include <graphics.h>
// 判断是否胜利
bool CheckWin() {

}
// 判断是否平局
bool CheckDraw() {

}
// 绘制棋盘
void DrawBoard() {

};
// 绘制棋子
void DrawPiece() {

};
// 绘制提示信息
void DrawTipText() {

};

int main() {

	initgraph(600, 600);

	bool running = true;

	ExMessage msg;

	BeginBatchDraw();

	while (running) {

		while (peekmessage(&msg)) {

		}

		if (CheckWin('X')) {
			MessageBox(GetHWnd(), _T("X 玩家获胜"), _T("游戏结束"), MB_OK);
			running = false;
		}

		else if (CheckWin('O')) {
			MessageBox(GetHWnd(), _T("X 玩家获胜"), _T("游戏结束"), MB_OK);
			running = false;
		}
	
		else if (CheckDraw()) {
			MessageBox(GetHWnd(), _T("平局"), _T("游戏结束"), MB_OK);
			running = false;
		}

		cleardevice();

		DrawBoard();
		DrawPiece();
		DrawTipText();

		FlushBatchDraw();
	}

	EndBatchDraw();


}

此框架添加了之前小球项目相同的双缓冲代码,使画面平滑。

3.棋子数据结构

棋子一共九个,不难想到使用二维数组存储。可以使用字符数组存储,字符"X"和"O"分别代表两个棋子,空位则用字符"-"表示。

char board_data[3][3] = {
	{'-', '-', '-'},
	{'-', '-', '-'},
	{'-', '-', '-'}
};

此外每回合落子类型需要定义,用于绘制对应棋子和生成提示信息。

4.判断胜利/平局

井字棋的胜利条件是三个同类型棋子相邻,其获胜方式的棋子布局类型是有限,可以穷举出来,因此可以设置多个if条件判断。而两种棋子都设置相同条件判断代码太过冗余,可以将棋子类型设置为参数,需要时调用对应棋子的函数即可。

bool CheckWin(char c) {
	if (board_data[0][0] == c && board_data[0][1] == c && board_data[0][2] == c)
		return true;
	if (board_data[1][0] == c && board_data[1][1] == c && board_data[1][2] == c)
		return true;
	if (board_data[2][0] == c && board_data[2][1] == c && board_data[2][2] == c)
		return true;
	if (board_data[0][0] == c && board_data[1][0] == c && board_data[2][0] == c)
		return true;
	if (board_data[0][1] == c && board_data[1][1] == c && board_data[2][1] == c)
		return true;
	if (board_data[0][2] == c && board_data[1][2] == c && board_data[2][2] == c)
		return true;
	if (board_data[0][0] == c && board_data[1][1] == c && board_data[2][2] == c)
		return true;
	if (board_data[0][2] == c && board_data[1][1] == c && board_data[2][0] == c)
		return true;

	return false;
}

平局的判定方式很简单,只需要二维数组全部被棋子填满,但没有棋子胜利(将判断胜利函数在平局函数之前调用)即可。

bool CheckDraw() {
	for (int i = 0; i < 3; i++) {
		for (int j = 0; j < 3; j++)
		{
			if (board_data[i][j] == '-')
			{
				return false;
			}
		}
	}

	return true;
}

5.绘制棋盘与棋子

棋盘是九宫格,使用四条直线绘制即可(边框可以使用窗口边框)。

void DrawBoard() {
	line(0, 200, 600, 200);
	line(0, 400, 600, 400);
	line(200, 0, 200, 600);
	line(400, 0, 400, 600);
};

O棋子在对应格子中绘制以中心坐标为圆心,格子边长的一半为半径的圆即可(可以略小一点)。

X棋子绘制格子对应的两条对角线即可。

void DrawPiece() {
	for (int i = 0; i < 3; i++) {
		for (int j = 0; j < 3; j++)
		{
			switch (board_data[i][j]) {
			case 'O':
				circle(200 * j + 100, 200 * i + 100, 99);
				break;
			case 'X':
				line(200 * j, 200 * i, 200 * (j + 1), 200 * (i + 1));
				line(200 * (j + 1), 200 * i, 200 * j, 200 * (i + 1));
				break;
			case '-':
				break;
			}
		}
	}
};

另外判定鼠标坐标位置落在哪个格子,可以用鼠标坐标除以格子边长得到。

int x = msg.x;
int y = msg.y;

int index_x = x / 200;
int index_y = y / 200;

6.绘制提示信息

void DrawTipText() {
	static TCHAR str[64];
	_stprintf_s(str, _T("当前棋子类型:%c"), current_piece);

	settextcolor(RGB(225, 175, 45));
	outtextxy(0, 0, str);
};

TCHAR代表待定类型,可以是char或wchar_t,使用_T宏来确保字符串字面量与TCHAR兼容。

settextcolor()设置提示信息字体颜色,outtextxy()设置提示信息位置。

该函数将当前回合的棋子类型提示信息显示在窗口左上角。

另外获胜或平局时需要弹出提示窗口并显示信息。

if (CheckWin('X')) {
	MessageBox(GetHWnd(), _T("X 玩家获胜"), _T("游戏结束"), MB_OK);
	running = false;
}

else if (CheckWin('O')) {
	MessageBox(GetHWnd(), _T("O 玩家获胜"), _T("游戏结束"), MB_OK);
	running = false;
}
	
else if (CheckDraw()) {
	MessageBox(GetHWnd(), _T("平局"), _T("游戏结束"), MB_OK);
	running = false;
}

MessageBox()函数是 Windows API 提供的一个用于显示消息框的函数。它显示一个带有指定文本的消息框,可以选择带有图标和按钮,并等待用户响应。

GetHWnd()这个函数用于获取绘图窗口句柄。在 Windows 下,句柄是一个窗口的标识,得到句柄后,可以使用 Windows API 中的函数实现对窗口的控制。

MB_OK是一个在 MessageBox()函数中使用的宏,它指定消息框将显示一个确定按钮(OK button)。当用户点击这个按钮时,MessageBox()函数会返回IDOK值。

7.释放资源

如果不做任何处理,该程序运行时会占用cpu大量资源,因为每次循环所需的时间小于1000/60ms(60Hz刷新率屏幕每一帧的时间),多余时间占用cpu空等待,因此可以释放来优化程序。

首先记录每次循环的运行时间,如果时间小于1000/60ms,就让程序sleep剩余时间。

int start_time = GetTickCount();
int end_time = GetTickCount();
int delta_time = end_time - start_time;
if (delta_time < 1000 / 60)
{
	Sleep(1000 / 60 - delta_time);
}

完整代码如下:

#include <graphics.h>

char board_data[3][3] = {
	{'-', '-', '-'},
	{'-', '-', '-'},
	{'-', '-', '-'}
};

char current_piece = 'O';

// 判断是否胜利
bool CheckWin(char c) {
	if (board_data[0][0] == c && board_data[0][1] == c && board_data[0][2] == c)
		return true;
	if (board_data[1][0] == c && board_data[1][1] == c && board_data[1][2] == c)
		return true;
	if (board_data[2][0] == c && board_data[2][1] == c && board_data[2][2] == c)
		return true;
	if (board_data[0][0] == c && board_data[1][0] == c && board_data[2][0] == c)
		return true;
	if (board_data[0][1] == c && board_data[1][1] == c && board_data[2][1] == c)
		return true;
	if (board_data[0][2] == c && board_data[1][2] == c && board_data[2][2] == c)
		return true;
	if (board_data[0][0] == c && board_data[1][1] == c && board_data[2][2] == c)
		return true;
	if (board_data[0][2] == c && board_data[1][1] == c && board_data[2][0] == c)
		return true;

	return false;
}
 
// 判断是否平局
bool CheckDraw() {
	for (int i = 0; i < 3; i++) {
		for (int j = 0; j < 3; j++)
		{
			if (board_data[i][j] == '-')
			{
				return false;
			}
		}
	}

	return true;
}

// 绘制棋盘
void DrawBoard() {
	line(0, 200, 600, 200);
	line(0, 400, 600, 400);
	line(200, 0, 200, 600);
	line(400, 0, 400, 600);
};

// 绘制棋子
void DrawPiece() {
	for (int i = 0; i < 3; i++) {
		for (int j = 0; j < 3; j++)
		{
			switch (board_data[i][j]) {
			case 'O':
				circle(200 * j + 100, 200 * i + 100, 99);
				break;
			case 'X':
				line(200 * j, 200 * i, 200 * (j + 1), 200 * (i + 1));
				line(200 * (j + 1), 200 * i, 200 * j, 200 * (i + 1));
				break;
			case '-':
				break;
			}
		}
	}
};

// 绘制提示信息
void DrawTipText() {
	static TCHAR str[64];
	_stprintf_s(str, _T("当前棋子类型:%c"), current_piece);

	settextcolor(RGB(225, 175, 45));
	outtextxy(0, 0, str);
};

int main() {

	initgraph(600, 600);

	bool running = true;

	ExMessage msg;

	BeginBatchDraw();

	while (running) {

		int start_time = GetTickCount();

		while (peekmessage(&msg)) {

			// 检查鼠标左键按下消息
			if (msg.message == WM_LBUTTONDOWN) {

				// 计算点击位置
				int x = msg.x;
				int y = msg.y;

				int index_x = x / 200;
				int index_y = y / 200;

				// 尝试落子
				if (board_data[index_y][index_x] == '-') {
					board_data[index_y][index_x] = current_piece;

					// 切换棋子类型
					if (current_piece == 'O')
						current_piece = 'X';
					else
						current_piece = 'O';
				}
			}
		}

		cleardevice();

		DrawBoard();
		DrawPiece();
		DrawTipText();

		FlushBatchDraw();

		if (CheckWin('X')) {
			MessageBox(GetHWnd(), _T("X 玩家获胜"), _T("游戏结束"), MB_OK);
			running = false;
		}

		else if (CheckWin('O')) {
			MessageBox(GetHWnd(), _T("O 玩家获胜"), _T("游戏结束"), MB_OK);
			running = false;
		}
	
		else if (CheckDraw()) {
			MessageBox(GetHWnd(), _T("平局"), _T("游戏结束"), MB_OK);
			running = false;
		}

		int end_time = GetTickCount();
		int delta_time = end_time - start_time;
		if (delta_time < 1000 / 60)
		{
			Sleep(1000 / 60 - delta_time);
		}

	}

	EndBatchDraw();


}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值