MFC双人黑白棋

一、简介

黑白棋 游戏规则:
黑白棋的棋盘是一个有8*8方格的棋盘。下棋时将棋下在空格中间,而不是像围棋一样下在交叉点上。开始时在棋盘正中有两白两黑四个棋子交叉放置,黑棋总是先下子 。
把自己颜色的棋子放在棋盘的空格上,而当自己放下的棋子在横、竖、斜八个方向内有一个自己的棋子,则被夹在中间的全部翻转会成为自己的棋子。并且,只有在可以翻转棋子的地方才可以下子。如果一方不能翻转另一方的棋子,就由另一方连续下子,直到这一方可以下。
如果双方都不能翻转对方的棋子或棋盘下满了,一盘棋就结束了。哪一方的棋子多哪一方就获胜。

我设计的双人黑白棋实现了如下功能:
1.使用MFC绘制棋盘、棋子并可以使用鼠标操作;
2.判断哪些地方可以落子;
3.终局时判断胜负。
完整代码点击此处下载
此外,我还发表了一篇MFC双人五子棋的文章,黑白棋很多代码都是参考的五子棋,这篇博客也有很多地方是参考的五子棋那篇博客,有兴趣的人可以点击此处看看

二、程序设计

1.创建资源文件

调整对话框尺寸为400*400(Windows坐标是800*900),在下方添加三个Button控件,分别是“开始游戏”(IDC_START)“结束本局”(IDC_ENDGAME)“退出”(IDC_QUIT),把“开始游戏”设为默认按钮,“结束本局”设为初始禁用,再添加一个Static控件(IDC_CHESSCOUNT),初始文本设为“双人黑白”,游戏进行的时候我们用它显示棋子个数。把对话框标题改为“双人黑白棋”。棋盘和棋子后面我们会用代码绘制。
然后,添加两个cursor光标资源,图案分别是一个黑棋和一个白棋。黑棋的ID是IDC_BLACK,白棋的ID是IDC_WHITE。

2.下子算法设计

黑白棋的判断胜负很简单,就是看看哪一方棋子个数多就行了,但判断哪里能下子比较复杂。要知道,黑白棋不是任意地方都能下子的,而是能翻转对方的棋子才可以下。
为了方便修改棋盘尺寸,我们定义了一个宏SIZE,它代表棋盘的行数和列数。

#define SIZE 8

下面我们开始创建变量。接下来的变量都放在对话框类中作为对话框类的成员。首先,我们创建一个SIZE*SIZE的int(char也行)二维数组(名为ChessBoard),这个数组就代表棋盘,每个元素的不同值代表棋盘交叉点的不同状态:-1为空,0为白,1为黑。再创建一个bool变量NowColor用来记录下一步棋的颜色,false(0)为白,true(1)为黑。接着创建一个bool变量IsPlaying记录是否正在游戏。代码如下:

bool IsPlaying;
bool NowColor;
int ChessBoard[SIZE][SIZE];//棋盘,-1为空,0为白,1为黑

由于计算机中数组下标是先行后列(先y轴后x轴),与我们平常的习惯(先列后行)不符,所以我们先创建一个函数转换这个差异。当然,这个函数也可以不写,但后面绘制棋子和判断鼠标位置的程序需要修改一下。

int COthelloDlg::GetChessBoardColor(int nx, int ny)
{
    
    
	return ChessBoard[ny][nx];
}

接下来,我们开始创建算法的核心部分。我创建了一个函数,名为GetNextSameColorChessPos,这个函数的功能是获取指定坐标上的棋子在指定方向上的最近的、中间没有空格的同色棋子的坐标,如果没有则返回(SIZE,SIZE),即(8,8),表示没有找到。举个例子,假如下面的棋盘右上角棋子坐标为(3,3),GetNextSameColorChessPos(3,3,7)的值就为(0,0)(即左下角棋子的坐标)。其中,参数表中第三个参数是方向,7代表左下。
在这里插入图片描述
该函数代码如下:

CPoint COthelloDlg::GetNextSameColorChessPos(int nx, int ny, int direction, int TestColor = 2)
{
    
    
	int x = nx, y = ny;
	int color = TestColor;
	if (color == 2)
		color = GetChessBoardColor(x, y);
	switch (direction)//注意:Windows系统和数组坐标以左上角为原点,所以上下要相反
	{
    
    
	case 0://while (1)
		{
    
    
			x--;
			if (x < 0)//未找到
				return CPoint(SIZE, SIZE);
			if (GetChessBoardColor(x, y) == -1)//中间有空格
				return CPoint(SIZE, SIZE);
			if (GetChessBoardColor(x, y) == color)
				return CPoint(x, y);
		}
		//函数肯定会返回,无需break
	case 1://左上
		while (1)
		{
    
    
			x--;
			y--;
			if (x < 0 || y < 0)//未找到
				return CPoint(SIZE, SIZE);
			if (GetChessBoardColor(x, y) == -1)//中间有空格
				return CPoint(SIZE, SIZE);
			if (GetChessBoardColor(x, y) == color)
				return CPoint(x, y);
		}
	case 2://while (1)
		{
    
    
			y--;
			if (y < 0)//未找到
				return CPoint(SIZE, SIZE);
			if (GetChessBoardColor(x, y) == -1)//中间有空格
				return CPoint(SIZE, SIZE);
			if (GetChessBoardColor(x, y) == color)
				return CPoint(x, y);
		}
	case 3://右上
		while (1)
		{
    
    
			x++;
			y--;
			if (x >= SIZE || y < 0)//未找到
				return CPoint(SIZE, SIZE);
			if (GetChessBoardColor(x, y) == -1)//中间有空格
				return CPoint(SIZE, SIZE);
			if (GetChessBoardColor(x, y) == color)
				return CPoint(x, y);
		}
	case 4://while (1)
		{
    
    
			x++;
			if (x >= SIZE)//未找到
				return CPoint(SIZE, SIZE);
			if (GetChessBoardColor(x, y) == -1)//中间有空格
				return CPoint(SIZE, SIZE);
			if (GetChessBoardColor(x, y) == color)
				return CPoint(x, y);
		}
	case 5://右下
		while (1)
		{
    
    
			x++;
			y++;
			if (x >= SIZE || y >=SIZE)//未找到
				return CPoint(SIZE, SIZE);
			if (GetChessBoardColor(x, y) == -1)//中间有空格
				return CPoint(SIZE, SIZE);
			if (GetChessBoardColor(x, y) == color)
				return CPoint(x, y);
		}
	case 6://while (1)
		{
    
    
			y++;
			if (y >=SIZE)//未找到
				return CPoint(SIZE, SIZE);
			if (GetChessBoardColor(x, y) == -1)//中间有空格
				return CPoint(SIZE, SIZE);
			if (GetChessBoardColor(x, y) == color)
				return CPoint(x, y);
		}
	case 7://左下
		while (1)
		{
    
    
			x--;
			y++;
			if (x < 0 || y >=SIZE)//未找到
				return CPoint(SIZE, SIZE);
			if (GetChessBoardColor(x, y) == -1)//中间有空格
				return CPoint(SIZE, SIZE);
			if (GetChessBoardColor(x, y) == color)
				return CPoint(x, y);
		}
	default:
		return CPoint(SIZE, SIZE);
	}
}

这个函数的代码虽然长,但不复杂,写完第一种情况之后几乎全是复制,修改一下符号就行了。这里需要注意的就是符号,有++ – >= <,很容易重复了。还要注意,这里上并不y坐标是+,而是-,这是因为Windows系统和数组坐标以左上角为原点。注意这个函数的第四个参数TestColor,用于测试,也就是说那个坐标还没有棋子,给它假设一种颜色,看看下在这里行不行,用于鼠标点击后判断能不能落子。如果不填这个参数,默认是获取指定坐标的棋子颜色进行判断。
接下来,我们就可以创建一个判断某个坐标能不能落子的函数了。该函数代码如下:

bool COthelloDlg::CanItPlaceChessPieces(int x, int y, int color)
{
    
    
	int sum = 0;
	for (int direction = 0; direction < 8; direction++)
	{
    
    
		int count;
		switch (direction)
		{
    
    
		case 2://case 6://{
    
    
			int pos = GetNextSameColorChessPos(x, y, direction, color).y;
			count = abs(pos - y) - 1;
			//计算y轴坐标的差
			if (pos == SIZE)//没找到
				count = 0;
		}
		break;
		default://其它方向,计算x轴坐标的差
		{
    
    
			int pos = GetNextSameColorChessPos(x, y, direction, color).x;
			count = abs(pos - x) - 1;
			//计算y轴坐标的差
			if (pos == SIZE)//没找到
				count = 0;
		}
		}
		sum += count;
	}
	return sum;//return (sum > 0);
}

这个函数的实现原理是遍历8个方向,把每个方向中间间隔的棋子(可翻转的棋子)个数加起来,最后判断它们的和是否为0,就可以判断能否落子了。这个函数要注意的是switch语句,当direction等于2或6,也就是上方或下方的时候我们需要计算两个坐标y轴的差,因为每一行上肯定只有一个棋子。其它方向我们都可以计算x轴坐标的差,因为每一列上肯定只有一个棋子。
到现在,下子部分的算法设计完毕,我们开始设计响应控件事件和绘制棋盘的有关函数。

2.窗口消息、控件事件和绘图函数

让我们先从简单的入手吧。我们先处理窗口的WM_CLOSE消息,当窗口收到WM_CLOSE消息时,判断游戏是否在进行,如果在进行则弹出对话框询问是否要退出否则直接退出。代码如下:

void COthelloDlg::OnClose()
{
    
    
	if (!IsPlaying || MessageBoxW(L"正在游戏中,确定要退出吗?", L"双人黑白棋", MB_YESNO | MB_ICONQUESTION) == IDYES)
		CDialogEx::OnClose();
}

这里用到了一个C++逻辑或运算符基本规则,那就是如果第一个表达式成立,就不会计算第二个表达式。所以如果!IsPlaying(游戏不在进行),就不会弹出对话框,而是直接关闭。
“退出”按钮的BN_CLICKED消息和WM_CLOSE消息的处理程序基本相同,这里就不再赘述了。代码如下:

void COthelloDlg::OnBnClickedQuit()
{
    
    
	if (!IsPlaying || MessageBoxW(L"正在游戏中,确定要退出吗?", L"双人黑白棋", MB_YESNO | MB_ICONQUESTION) == IDYES)
		EndDialog(0);
}

接着,我们修改一下对话框的DoDataExchange函数,进行数据初始化。代码很简单,就不再解释。代码如下:

void COthelloDlg::DoDataExchange(CDataExchange* pDX)
{
    
    
	IsPlaying = false;
	for (int i = 0; i < SIZE; i++)
	{
    
    
		for (int j = 0; j < SIZE; j++)
		{
    
    
			ChessBoard[i][j] = -1;
		}
	}
	//初始化棋盘
	CDialogEx::DoDataExchange(pDX);
}

然后,我们要修改WM_PAINT消息绘制棋盘和棋子。代码如下:

void COthelloDlg::OnPaint()
{
    
    
	if (IsIconic())
	{
    
    
		CPaintDC dc(this); // 用于绘制的设备上下文

		SendMessage(WM_ICONERASEBKGND, reinterpret_cast<WPARAM>(dc.GetSafeHdc()), 0);

		// 使图标在工作区矩形中居中
		int cxIcon = GetSystemMetrics(SM_CXICON);
		int cyIcon = GetSystemMetrics(SM_CYICON);
		CRect rect;
		GetClientRect(&rect);
		int x = (rect.Width() - cxIcon + 1) / 2;
		int y = (rect.Height() - cyIcon + 1) / 2;

		// 绘制图标
		dc.DrawIcon(x, y, m_hIcon);
	}
	else
	{
    
    
		CPaintDC dc(this);
		CPen pen(PS_SOLID, 2, RGB(0, 0, 0));
		dc.SelectObject(pen);
		for (int i = 0; i < (SIZE+1)/*黑白棋下在格子里,要多画一条线*/; i++)
		{
    
    
			dc.MoveTo(40, 50 + i * 90);
			dc.LineTo(760, 50 + i * 90);
		}//绘制棋盘横线
		for (int i = 0; i < (SIZE + 1); i++)
		{
    
    
			dc.MoveTo(40 + i * 90, 50);
			dc.LineTo(40 + i * 90, 770);
		}//绘制棋盘竖线
		for (int nx = 0; nx < SIZE; nx++)
		{
    
    
			for (int ny = 0; ny < SIZE; ny++)
			{
    
    

				int color = GetChessBoardColor(nx, ny);
				if (color == 0)//白棋
				{
    
    
					CBrush brush_w(RGB(255, 255, 255));
					const CPoint o(90 * nx + 85, 90 * ny + 95);//圆心
					dc.SelectObject(brush_w);
					dc.Ellipse(o.x - 30, o.y - 30, o.x + 30, o.y + 30);
				}
				else if (color == 1)//黑棋
				{
    
    
					CBrush brush_b(RGB(0, 0, 0));
					const CPoint o(90 * nx + 85, 90 * ny + 95);//圆心
					dc.SelectObject(brush_b);
					dc.Ellipse(o.x - 30, o.y - 30, o.x + 30, o.y + 30);
				}
			}
		}
	}
}

代码应该不难理解,用的是MFC的绘图工具,和GDI绘图类似,非常方便,不需要担心内存泄漏等问题,构造函数和析构函数会自动处理绘图对象的创建和删除。但有些MFC或Windows API开发经验的人可能会注意到一个问题:我用的是CPaintDC,相当于Windows API中的BeginPaint函数,这样只会绘制一次,绘制结束后不会再收到WM_PAINT消息,下棋后无法正常显示棋子。这个问题有道理。但是,因为黑白棋不需要一直重绘已经存在的棋子,如果一直重绘已经存在的棋子会大幅度降低程序性能,所以我在SetChessBoardColor函数中会绘制新下的棋子,已经绘制的棋子和棋盘不会改变,这个函数下面会讲到。这里之所以还添加绘制棋子的程序,是因为游戏过程中,窗口被移出屏幕边缘或被最小化,恢复正常时窗口会收到WM_PAINT消息,如果不绘制棋子,则无法正常显示棋盘上已经存在的棋子。
既然提到了SetChessBoardColor函数,那我们先来看看它的代码。这个函数不仅能修改ChessBoard,还能在屏幕上绘制出棋子,这样就不用重绘整个棋盘了。

void COthelloDlg::SetChessBoardColor(int nx, int ny, int color)
{
    
    
	ChessBoard[ny][nx] = color;
	CDC* dc = this->GetDC();
	CPen pen(PS_SOLID, 2, RGB(0, 0, 0));
	dc->SelectObject(pen);
	if (color == 0)//白棋
	{
    
    
		CBrush brush_w(RGB(255, 255, 255));
		const CPoint o(90 * nx + 85, 90 * ny + 95);//圆心
		dc->SelectObject(brush_w);
		dc->Ellipse(o.x - 30, o.y - 30, o.x + 30, o.y + 30);
	}
	else if (color == 1)//黑棋
	{
    
    
		CBrush brush_b(RGB(0, 0, 0));
		const CPoint o(90 * nx + 85, 90 * ny + 95);//圆心
		dc->SelectObject(brush_b);
		dc->Ellipse(o.x - 30, o.y - 30, o.x + 30, o.y + 30);
	}
}

然后是CleanChessBoard函数,该函数清空ChessBoard数组和屏幕上的棋子。代码如下:

void COthelloDlg::CleanChessBoard()
{
    
    
	for (int i = 0; i < SIZE; i++)
	{
    
    
		for (int j = 0; j < SIZE; j++)
		{
    
    
			ChessBoard[i][j] = -1;
		}
	}
	Invalidate();
}

然后我们来创建EndGame函数,它的功能是结束游戏。代码如下:

void COthelloDlg::EndGame()
{
    
    
	CleanChessBoard();
	IsPlaying = false;
	GetDlgItem(IDC_START)->SetWindowTextW(L"开始游戏");
	GetDlgItem(IDC_ENDGAME)->EnableWindow(FALSE);
	GetDlgItem(IDC_CHESSCOUNT)->SetWindowTextW(L"双人黑白棋");
}

接下来,我们创建“开始游戏”按钮的BN_CLICKED消息。由于开始游戏与重玩的代码完全相同,所以我们就不再创建重玩按钮,而是通过修改“开始游戏”按钮的窗口标题实现。代码如下:

void COthelloDlg::OnBnClickedStart()
{
    
    
	if (IsPlaying && MessageBoxW(L"确定要重玩吗?", L"双人黑白棋", MB_YESNO | MB_ICONQUESTION) == IDNO)
		return;
	GetDlgItem(IDC_START)->SetWindowTextW(L"重玩");
	IsPlaying = true;
	NowColor = 1;//黑先
	GetDlgItem(IDC_ENDGAME)->EnableWindow(TRUE);
	CleanChessBoard();
	SetChessBoardColor(SIZE / 2 - 1, SIZE / 2 - 1, 1);
	SetChessBoardColor(SIZE / 2, SIZE / 2, 1);
	SetChessBoardColor(SIZE / 2, SIZE / 2 - 1, 0);
	SetChessBoardColor(SIZE / 2 - 1, SIZE / 2, 0);
	GetDlgItem(IDC_CHESSCOUNT)->SetWindowTextW(L"黑棋:2个\t白棋:2个");
	//黑白棋初始有四个棋子
}

这里用到了一个C++逻辑与运算符基本规则,那就是如果第一个表达式不成立,就不会计算第二个表达式。所以如果IsPlaying(游戏在进行)不成立,就不会 弹出对话框,而是直接进行下面的代码。
接着,我们创建“结束本局”的BN_CLICKED消息处理程序。因为我们以前已经写了EndGame函数,这里直接调用就行了。代码如下:

void COthelloDlg::OnBnClickedEndgame()
{
    
    
	if (MessageBoxW(L"确定要结束本局吗?", L"双人黑白棋", MB_YESNO | MB_ICONQUESTION) == IDYES)
		EndGame();
}

然后是对话框的WM_SETCURSOR消息处理程序。这个函数设置鼠标光标的状态,决定鼠标是黑子、白子还是普通。代码如下:

BOOL COthelloDlg::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message)
{
    
    
	POINT point;
	GetCursorPos(&point);
	ScreenToClient(&point);
	if (!IsPlaying || point.x < 40 || point.x>760 || point.y < 50 || point.y>770)
		return CDialogEx::OnSetCursor(pWnd, nHitTest, message);
	if (NowColor == 1)//黑棋
		SetCursor(LoadCursorW(AfxGetApp()->m_hInstance, MAKEINTRESOURCE(IDC_BLACK)));
	else
		SetCursor(LoadCursorW(AfxGetApp()->m_hInstance, MAKEINTRESOURCE(IDC_WHITE)));
	return TRUE;
}

现在,我们要创建一个极其重要的消息处理函数——响应鼠标左键松开的消息处理函数,其中point是鼠标的坐标(以客户区为参照系)。这个函数的功能是在鼠标单击处放置棋子、翻转棋子、显示棋子个数并判断胜负。代码如下:

void COthelloDlg::OnLButtonUp(UINT nFlags, CPoint point)
{
    
    
	if (!IsPlaying || point.x < 40 || point.x>760 || point.y < 50 || point.y>770)
		return;
	int x = int(round((point.x - 45 - 40) / 90.0));
	int y = int(round((point.y - 45 - 50) / 90.0));
	//将鼠标坐标转为数组下标
	if (GetChessBoardColor(x, y) != -1)//如果已有棋子
		return;
	if (!CanItPlaceChessPieces(x, y, NowColor))
		return;
	SetChessBoardColor(x, y, NowColor);
	for (int direction = 0; direction < 8; direction++)
	{
    
    
		CPoint pt = GetNextSameColorChessPos(x, y, direction);
		if (pt.x == SIZE)
			continue;
		switch (direction)//注意:Windows系统和数组坐标以左上角为原点,所以上下要相反
		{
    
    
		case 0://for (int nx = pt.x + 1; nx < x; nx++)//注意符号
				SetChessBoardColor(nx, y, NowColor);
			break;
		case 1://左上
			for (int nx = pt.x + 1, ny = pt.y + 1; nx < x; nx++, ny++)//注意符号
				SetChessBoardColor(nx, ny, NowColor);
			break;
		case 2://for (int ny = pt.y + 1; ny < y; ny++)//注意符号
				SetChessBoardColor(x, ny, NowColor);
			break;
		case 3://右上
			for (int nx = pt.x - 1, ny = pt.y + 1; nx > x; nx--, ny++)//注意符号
				SetChessBoardColor(nx, ny, NowColor);
			break;
		case 4://for (int nx = pt.x - 1; nx > x; nx--)//注意符号
				SetChessBoardColor(nx, y, NowColor);
			break;
		case 5://右下
			for (int nx = pt.x - 1, ny = pt.y - 1; nx > x; nx--, ny--)//注意符号
				SetChessBoardColor(nx, ny, NowColor);
			break;
		case 6://for (int ny = pt.y - 1; ny > y; ny--)//注意符号
				SetChessBoardColor(x, ny, NowColor);
			break;
		case 7://左下
			for (int nx = pt.x + 1, ny = pt.y - 1; nx < x; nx++, ny--)//注意符号
				SetChessBoardColor(nx, ny, NowColor);
			break;
		}
		
	}
	bool b1 = false, b2 = false;
	for (int i = 0; i < SIZE; i++)
	{
    
    
		for (int j = 0; j < SIZE; j++)
		{
    
    
			if ((GetChessBoardColor(i, j) == -1) && (CanItPlaceChessPieces(i, j, !NowColor)))
			{
    
    
				NowColor = (!NowColor);
				b1 = true;
				break;
			}
		}
		if (b1)
			break;
	}
	if (!b1)
	{
    
    
		for (int i = 0; i < SIZE; i++)
		{
    
    
			for (int j = 0; j < SIZE; j++)
			{
    
    
				if ((GetChessBoardColor(i, j) == -1) && (CanItPlaceChessPieces(i, j, NowColor)))
				{
    
    
					b2 = true;
					break;
				}
			}
			if (b2)
				break;
		}
	}
	//黑白棋不一定是轮流下,如果一方无棋可下则另一方一直下,直到那一方可以下
	SendMessage(WM_SETCURSOR);
	//以上为放置棋子
	int white = 0, black = 0;
	for (int i = 0; i < SIZE; i++)
	{
    
    
		for (int j = 0; j < SIZE; j++)
		{
    
    
			int color = GetChessBoardColor(i, j);
			if (color == 0)
				white++;
			else if (color == 1)
				black++;
		}
	}
	CString str;
	str.Format(L"黑棋:%d个\t白棋:%d个", black, white); 
	CRect rcStatic;
	GetDlgItem(IDC_CHESSCOUNT)->GetWindowRect(&rcStatic);
	ScreenToClient(&rcStatic);
	InvalidateRect(&rcStatic);
	GetDlgItem(IDC_CHESSCOUNT)->SetWindowTextW(str);
	if ((!b1)&&(!b2))//双方都无棋可下
	{
    
    
		if (white > black)
			MessageBoxW(L"白棋胜利!\n"+str, L"双人黑白棋", MB_OK | MB_ICONINFORMATION);
		else if (black > white)
			MessageBoxW(L"黑棋胜利!\n"+str, L"双人黑白棋", MB_OK | MB_ICONINFORMATION);
		else
			MessageBoxW(L"平局!\n"+str, L"双人黑白棋", MB_OK | MB_ICONINFORMATION);
		EndGame();
	}
	//以上为判断胜负
}

代码最复杂的部分是翻转棋子。首先,翻转棋子不需要擦除,直接在原来的棋子上覆盖就行了。翻转棋子的代码一定要注意符号,一不小心就会重复。告诉你们一个小窍门:如果初始化nx或ny时是+1,后面就对应着<和++,否则对应>和–。它的原理倒是很简单,就是把两个相同颜色的棋子中间夹着的棋子全部变成一种颜色。还有一点,在显示棋子个数的时候没有简单地SetWindowText,这是因为如果不强制刷新,新的数据会在旧数据上面显示。毕竟Static控件一般是用来显示固定的文本的。当然,也可以用Edit控件代替,不过要设为只读的。
为了美观,我还为程序添加了一个背景图,添加背景图的过程很简单,先把图片添加到资源文件里,再在OnInitDialog函数中添加一句

SetBackgroundImage(IDB_BACKGROUNDIMAGE);

就可以了。

3.其它功能

由于时间问题,黑白棋还有很多功能没有实现,如悔棋、保存和打开棋局、显示哪些地方可以落子等。下面我给大家提供一些思路:
悔棋
因为黑白棋落子后还要翻转,所以不能逆推出上一步,只能创建一个3维数组,保存每一步棋盘的状态。这和双人五子棋的悔棋功能有些相似,有兴趣的话可以看看五子棋的悔棋功能。
保存、打开棋局
这个功能和五子棋的功能基本相同,只要按格式把内存中相关变量复制到磁盘里就行了。具体见五子棋的保存、打开棋局功能。
显示落子的地方
在一个函数中遍历棋盘的所有空交叉点,在可以落子的地方绘图。不过每走完一步不要忘记把上一步绘制的擦除哦!

三、程序截图

至此,双人黑白棋已经完成。这个程序我调试过很多次,目前没有发现bug,如果有人发现了漏洞或有更好的意见,欢迎大家在评论区提出。
下面我给出程序的一些截图。
初始状态:
初始状态
游戏中:
游戏中
判断胜负:
判断胜负
PS:如果觉得写得好,不要忘记点个赞哦!

猜你喜欢

转载自blog.csdn.net/qq_54121864/article/details/115793171
今日推荐