十分钟开发出神经网络五子棋(三)

前面介绍了“控制台版本的神经网络五子棋”和“MFC对话框版本的神经网络五子棋”的开发过程,本文继续介绍一个可用于自主创业的实用案例——神经网络五子棋(家庭版)。因为实用需要考虑到诸多细节,所以本篇文章的篇幅较之前两篇长些。

1.神经网络五子棋(家庭版)

该案例虽然命名为家庭版,但不限于家庭使用,其他机构和个人亦均可使用。该版本同样是在MFC对话框上进行开发的,但是增加了人工落子(即用鼠标点击落子)部分,完成了人机对战功能。

(1)创建项目

运行Visual Studio 2015,选择菜单“文件-->新建-->项目”,在对话框的左侧选择“已安装-->模板-->Visual C++-->MFC”,在右侧选择“MFC应用程序”,并选定项目保存的位置和项目名称(此处项目名为test,保存在桌面),如下图所示:

点击“确定”按钮,在弹出的对话框的左侧选择“应用程序类型”,右侧选择“基于对话框”,并取消“安全开发生命周期(SDL)检查”的选择,若想开发的是绿色软件以免去分发时的动态库缺失问题,可将“MFC的使用”切换选为“在静态库中使用MFC”,详细如下图所示: 

 在对话框的左侧选择“用户界面功能”,右侧取消勾选“系统菜单”,如此可避免“关于”、最大化、最小化等带来的界面刷新问题,若需要支持这些功能,可自行在后续增加界面刷新机制,详细如下图所示:

 点击“完成”按钮,至此完成了项目的创建,显示如下:

(2)界面(GUI)设计 

本例需要一个显示棋盘的控件、一个按钮用来开始新游戏、一个按钮用来保存模型数据、以及一个退出按钮。故而,删除中间对话框上已有的“TODO:在此防止对话框控件”控件和确定、取消按钮;从上图右侧“工具箱”中将“Picture Control” 拖拽到中间的对话框上,调整大小,右击该控件,在右键菜单中选择“属性”,在弹出的属性框中找到ID,并将其修改为IDC_BOARD,如下图:

同理,从工具箱中将Button拖拽到对话框上,修改其ID为IDC_START,同时在属性框中修改其"Caption"为“开始新游戏”,如下图所示: 

重复以上操作,再添加一个按钮,修改ID为IDC_SAVE,修改其Caption为“保存模型”,用来保存模型数据,结果如下图所示:

继续重复以上操作,再添加一个按钮用于退出程序,修改其ID为IDC_QUIT,修改其Caption为“退出”,结果如下图所示:

(3)导入SDK

如前文所述一样,打开源代码所在的目录,将SDK文件(AIWZQDll.dll、AIWZQDll.lib、Inter.h)拷贝到该目录,如下图: 

然后,在解决方案资源管理器中右击test项目,在右键菜单中选择“添加-->现有项” ,在弹出的对话框中选中Inter.h并点击“添加”按钮即可。

(4)初始化神经网络

在解决方案资源管理器中双击testDlg.cpp文件,在显示的代码头部添加以下代码来引入SDK:

#include "Inter.h"
#pragma comment(lib, "AIWZQDll.lib")

结果显示如下:

在该文件中找到CtestDlg::OnInitDialog()函数,并调用Login()函数和InitFromModelFile()函数来初始化网络模型,登录失败时弹出提示对话框并退出程序,若没有找到模型文件,则调用InitWithoutModelFile()函数来初始化空模型:

	if (!Login("user", "password"))
	{
		AfxMessageBox(_T("登录失败,请确保网络畅通且用户名与密码均正确"));
		PostQuitMessage(0);
	}

	if (!InitFromModelFile("model.mod"))	//使用模型文件初始化
		InitWithoutModelFile(15, 15, 5);	//初始化棋盘大小为15x15,五连子获胜(即五子棋)

结果如下所示:

(5)实现“开始新游戏”

打开testDlg.h文件,添加如下代码来定义棋盘变量:

CStatic m_objBoard;

结果如下图所示:

在testDlg.cpp文件中找到CtestDlg::DoDataExchange(CDataExchange* pDX)并添加如下代码来将变量m_objBoard的值绑定到ID为IDC_BOARD的控件上显示:

DDX_Control(pDX, IDC_BOARD, m_objBoard);

结果如下图所示:

在前面的界面上找到“开始新游戏”按钮,双击该按钮,会自动添加该按钮的单击事件,即CtestDlg::OnBnClickedStart()函数,在该函数内添加以下代码来初始化游戏数据:

StartNewGame();

结果如下图所示:

(6)实现“保存模型”

同上,找到“保存模型”按钮,双击它来自动添加该按钮的单击事件,即CtestDlg::OnBnClickedSave()函数,在该函数内添加以下代码来保存神经网络模型:

	if (SaveModel("model.mod"))		//保存当前已经学习出的模型,供后续初始化使用,以便不断学习进化
		AfxMessageBox(_T("模型已成功保存"));

结果如下图所示:

模型保存成功后会弹出对话框提示保存成功,失败时不会提示。

(7)实现“退出”

重复上述操作,找到“退出”按钮,双击它自动添加该按钮的单击事件,即CtestDlg::OnBnClickedQuit()函数,在该函数内添加以下代码来退出应用,退出前会弹出确认提示对话框进行询问:

	if (MessageBox(_T("确定要退出程序吗?"), _T("温馨提示"), MB_OKCANCEL) == IDOK)
		PostQuitMessage(0);

结果如下图所示:

(8)增加一个刷新显示函数Show()

在testDlg.h文件的类定义中添加函数声明,如下图所示:

在testDlg.cpp文件中添加该函数的实现,代码如下:

void CtestDlg::Show()
{
	CRect rect;
	CRect rc;
	GetClientRect(&rect);
	CWnd* pBtn = GetDlgItem(IDC_START);
	if (pBtn != NULL)
		pBtn->GetClientRect(&rc);

	rect.right -= rc.right*1.5;
	CWnd* pBoard = GetDlgItem(IDC_BOARD);
	if (pBoard != NULL)
		pBoard->MoveWindow(&rect);

	DrawBoard(&m_objBoard);
	DrawPieces(&m_objBoard);
}

该函数用于显示,当界面刷新、调整大小等等操作之后需要调用该函数来刷新界面。目前,该函数内主要包含控件的定位、调整大小、绘制棋盘和棋子等操作,若需要调整按钮的位置和大小等,亦可在该函数内增加相应的代码,但是,禁用按钮、启用按钮等非显示相关的操作不应放于该函数内。结果如下图所示:

 定义好Show()函数之后,在CtestDlg::OnPaint()函数内调用它,如下图所示:

 至此,界面部分的代码基本已完成,可以运行程序,看到如下的效果:

(9) 实现操作逻辑

在点击“开始新游戏”按钮之前,“保存模型”按钮应该是不可用的,而且鼠标也是不能点击棋盘区域的,为此,在CNNWZQDlg::OnInitDialog()函数中添加如下代码来禁用“保存模型”按钮:

GetDlgItem(IDC_SAVE)->EnableWindow(false);

并在CtestDlg::OnBnClickedStart()函数中添加如下代码来启用“保存模型”按钮:

GetDlgItem(IDC_SAVE)->EnableWindow(true);

结果如下图所示:

接下来重写光标设置函数,以便从光标上区别是否可以落子。在testDlg.h文件的类实现中添加以下代码来重写光标设置函数:

	afx_msg BOOL OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message);

结果如下图所示:

然后在testDlg.cpp文件中增加该函数的实现代码:

BOOL CtestDlg::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message)
{
	CPoint pos;
	GetCursorPos(&pos);		//在整个屏幕上的坐标
	CRect rc;
	GetDlgItem(IDC_BOARD)->GetWindowRect(&rc);	//在整个屏幕上的坐标
	if (rc.PtInRect(pos))
	{
		if (m_bStarted && m_bHumanPlay)
		{
			SetCursor(LoadCursor(NULL, IDC_HAND));	//设置成手状,也可在OnMouseMove()中调用
		}
		else
		{
			SetCursor(LoadCursor(NULL, IDC_NO));	//设置成禁止光标
		}
		return TRUE;
	}

	return CDialogEx::OnSetCursor(pWnd, nHitTest, message);
}

此处代码表示,当开始了一局人机对弈且该人(而非机器)落子时,鼠标在棋盘区域将变为手状光标,否则鼠标在棋盘区域将变为禁止光标,另外,鼠标在棋盘区域之外不受此影响。这里的m_bStarted表示对弈是否已开始,m_bHumanPlay表示是否该人落子,将在后续详细介绍其逻辑。最后,还要在BEGIN_MESSAGE_MAP(CtestDlg, CDialogEx)部分增加ON_WM_SETCURSOR()来表示重载光标设置函数,如下图所示:

至此,显示上的逻辑已经完成,接下来要实现控制上的逻辑,即如何通过变量达成显示逻辑,亦即上述的m_bStarted和m_bHumanPlay等变量的操作。

先在testDlg.h文件中定义这些控制变量,如下图所示:

 然后在testDlg.cpp文件的CtestDlg::OnInitDialog()函数中初始化这些变量为false:

然后,在CtestDlg::OnBnClickedStart()函数中将m_bStarted设置为true,表示对弈已经开始:

 最后,重写鼠标单击事件。像重写设置光标的函数一样,在testDlg.h文件中增加以下代码:

afx_msg void OnLButtonDown(UINT nFlags, CPoint point);

同样的,在testDlg.cpp文件中实现该函数:

void CtestDlg::OnLButtonDown(UINT nFlags, CPoint point)
{
	CRect rect;
	m_objBoard.GetClientRect(&rect);
	if (rect.PtInRect(point))
	{
		if (!m_bStarted)
		{
			MessageBox(_T("请先点击开始新游戏"), _T("温馨提示"), MB_OK | MB_ICONHAND);
		}
		else if (m_bHumanPlay)
		{
			if (SetPieceWithGUI(&m_objBoard, point.x, point.y))
			{
				//PlaySound(MAKEINTRESOURCE(IDR_WAVE1), AfxGetResourceHandle(), SND_RESOURCE | SND_ASYNC);
				m_bHumanPlay = false;
				if (IsGameOver())
				{
					m_bStarted = false;
					switch (GetWinner())
					{
					case -1:
						MessageBox(_T("很遗憾,您输了"), _T("温馨提示"), MB_OK | MB_ICONINFORMATION);
						break;
					case 1:
						MessageBox(_T("恭喜,您赢了"), _T("温馨提示"), MB_OK | MB_ICONINFORMATION);
						break;
					default:	//0
						AfxMessageBox(_T("平局"));
						break;
					}
				}
				else
				{
					Sleep(500);
					if (!SetPieceByAIAndShow(&m_objBoard))
					{
						AfxMessageBox(_T("积分不足或网络问题,请确保网络畅通且积分充足(若是积分不足,充值后可继续本次对局)"));
					}
					else
					{
						//PlaySound(MAKEINTRESOURCE(IDR_WAVE1), AfxGetResourceHandle(), SND_RESOURCE | SND_ASYNC);
						m_bHumanPlay = true;
						if (IsGameOver())
						{
							m_bStarted = false;
							switch (GetWinner())
							{
							case -1:
								MessageBox(_T("很遗憾,您输了"), _T("温馨提示"), MB_OK | MB_ICONINFORMATION);
								break;
							case 1:
								MessageBox(_T("恭喜,您赢了"), _T("温馨提示"), MB_OK | MB_ICONINFORMATION);
								break;
							default:	//0
								AfxMessageBox(_T("平局"));
								break;
							}
						}
					}
				}
			}
		}
	}
	CDialogEx::OnLButtonDown(nFlags, point);
}

此处的代码表示,当鼠标在棋盘区域内单击时,若尚未开始对弈,则提示先点击“开始新游戏”按钮;若已开始对弈,且当前该人落子,则调用SetPieceWithGUI()函数并传入当前鼠标光标位置来完成落子。落子后判断游戏是否已经结束,结束的话就置m_bStarted为false并显示胜利者(详细解释请参考前文),若未结束,则调用SetPieceByAIAndShow()函数来让机器预测并落子。另外,Sleep()函数和PlaySound()函数为辅助函数,分别表示等待和播放声音。

之后,同OnSetCursor()函数一样,在BEGIN_MESSAGE_MAP(CtestDlg, CDialogEx)部分增加ON_WM_LBUTTONDOWN()来表示重载鼠标左键点击事件。

到此处,整个程序的大部分已经完成,但默认是机器先落子的,所以现在运行程序的话,会发现没法真正开始对弈。此时,需在“开始新游戏”按钮的单击响应函数CtestDlg::OnBnClickedStart()中增加第一步落子的操作:

    if (!SetPieceByAIAndShow(&m_objBoard))
	{
		AfxMessageBox(_T("积分不足或网络问题,请确保网络畅通且积分充足(若是积分不足,充值后可继续本次对局)"));
	}
	else 
	{
		m_bHumanPlay = true;
	}

此处代码表示,先调用SetPieceByAIAndShow()函数来让机器优先落子,然后将m_bHumanPlay置为true来等待人落子;若想让人先落子(而非机器优先),则简单地将m_bHumanPlay置为true即可。

至此,整个程序已经可以成功运行,如下图所示:

(10)游戏过程中新开局

至此,整个程序已可运行并进行人机对战,但是在游戏过程中或者一局结束时,点击“开始新游戏”按钮来新开一局,会发现界面上没能清除上一局的棋盘,这是因为已经重置的数据未能刷新到界面上,因此,在CtestDlg::OnBnClickedStart()函数中重置游戏数据之后,调用Invalidate(TRUE)函数来将重置后的数据刷新到界面上,结果如下所示:

(11) 附加操作

在初始化神经网络模型时的Login()函数调用是写死在代码中的,实际项目中可能需要提供界面供用户自己输入,该功能请参考我们提供的开源包,由于内容量较多,未在此处详述。

(12)完整代码如下


// testDlg.h : 头文件
//

#pragma once


// CtestDlg 对话框
class CtestDlg : public CDialogEx
{
// 构造
public:
	CtestDlg(CWnd* pParent = NULL);	// 标准构造函数

// 对话框数据
#ifdef AFX_DESIGN_TIME
	enum { IDD = IDD_TEST_DIALOG };
#endif

	protected:
	virtual void DoDataExchange(CDataExchange* pDX);	// DDX/DDV 支持


// 实现
protected:
	HICON m_hIcon;
	CStatic m_objBoard;

	bool m_bStarted;	//游戏是否已经开始
	bool m_bHumanPlay;	//是否该人/挑战者落子

	// 生成的消息映射函数
	virtual BOOL OnInitDialog();
	afx_msg void OnPaint();
	afx_msg HCURSOR OnQueryDragIcon();
	DECLARE_MESSAGE_MAP()
public:
	afx_msg void OnBnClickedStart();
	afx_msg void OnBnClickedSave();
	afx_msg void OnBnClickedQuit();
	afx_msg void OnLButtonDown(UINT nFlags, CPoint point);
	afx_msg BOOL OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message);

public:
	void Show();
};

// testDlg.cpp : 实现文件
//

#include "stdafx.h"
#include "test.h"
#include "testDlg.h"
#include "afxdialogex.h"

#include "Inter.h"
#pragma comment(lib, "AIWZQDll.lib")

#ifdef _DEBUG
#define new DEBUG_NEW
#endif


// CtestDlg 对话框



CtestDlg::CtestDlg(CWnd* pParent /*=NULL*/)
	: CDialogEx(IDD_TEST_DIALOG, pParent)
{
	m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
}

void CtestDlg::DoDataExchange(CDataExchange* pDX)
{
	CDialogEx::DoDataExchange(pDX);
	DDX_Control(pDX, IDC_BOARD, m_objBoard);
}

BEGIN_MESSAGE_MAP(CtestDlg, CDialogEx)
	ON_WM_PAINT()
	ON_WM_QUERYDRAGICON()
	ON_BN_CLICKED(IDC_START, &CtestDlg::OnBnClickedStart)
	ON_BN_CLICKED(IDC_SAVE, &CtestDlg::OnBnClickedSave)
	ON_BN_CLICKED(IDC_QUIT, &CtestDlg::OnBnClickedQuit)
	ON_WM_SETCURSOR()
	ON_WM_LBUTTONDOWN()
END_MESSAGE_MAP()


// CtestDlg 消息处理程序

BOOL CtestDlg::OnInitDialog()
{
	CDialogEx::OnInitDialog();

	// 设置此对话框的图标。  当应用程序主窗口不是对话框时,框架将自动
	//  执行此操作
	SetIcon(m_hIcon, TRUE);			// 设置大图标
	SetIcon(m_hIcon, FALSE);		// 设置小图标

	// TODO: 在此添加额外的初始化代码
	if (!Login("user", "password"))
	{
		AfxMessageBox(_T("登录失败,请确保网络畅通且用户名与密码均正确"));
		PostQuitMessage(0);
	}

	if (!InitFromModelFile("model.mod"))	//使用模型文件初始化
		InitWithoutModelFile(15, 15, 5);	//初始化棋盘大小为15x15,五连子获胜(即五子棋)

	GetDlgItem(IDC_SAVE)->EnableWindow(false);

	m_bStarted = false;
	m_bHumanPlay = false;

	return TRUE;  // 除非将焦点设置到控件,否则返回 TRUE
}

// 如果向对话框添加最小化按钮,则需要下面的代码
//  来绘制该图标。  对于使用文档/视图模型的 MFC 应用程序,
//  这将由框架自动完成。

void CtestDlg::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
	{
		CDialogEx::OnPaint();

		Show();
	}
}

//当用户拖动最小化窗口时系统调用此函数取得光标
//显示。
HCURSOR CtestDlg::OnQueryDragIcon()
{
	return static_cast<HCURSOR>(m_hIcon);
}

void CtestDlg::OnBnClickedStart()
{
	// TODO: 在此添加控件通知处理程序代码
	StartNewGame();
	GetDlgItem(IDC_SAVE)->EnableWindow(true);

	Invalidate(TRUE);

	m_bStarted = true;
	if (!SetPieceByAIAndShow(&m_objBoard))
	{
		AfxMessageBox(_T("积分不足或网络问题,请确保网络畅通且积分充足(若是积分不足,充值后可继续本次对局)"));
	}
	else
	{
		m_bHumanPlay = true;
	}
}

void CtestDlg::OnBnClickedSave()
{
	// TODO: 在此添加控件通知处理程序代码
	if (SaveModel("model.mod"))		//保存当前已经学习出的模型,供后续初始化使用,以便不断学习进化
		AfxMessageBox(_T("模型已成功保存"));
}

void CtestDlg::OnBnClickedQuit()
{
	// TODO: 在此添加控件通知处理程序代码
	if (MessageBox(_T("确定要退出程序吗?"), _T("温馨提示"), MB_OKCANCEL) == IDOK)
		PostQuitMessage(0);
}

void CtestDlg::OnLButtonDown(UINT nFlags, CPoint point)
{
	CRect rect;
	m_objBoard.GetClientRect(&rect);
	if (rect.PtInRect(point))
	{
		if (!m_bStarted)
		{
			MessageBox(_T("请先点击开始新游戏"), _T("温馨提示"), MB_OK | MB_ICONHAND);
		}
		else if (m_bHumanPlay)
		{
			if (SetPieceWithGUI(&m_objBoard, point.x, point.y))
			{
				//PlaySound(MAKEINTRESOURCE(IDR_WAVE1), AfxGetResourceHandle(), SND_RESOURCE | SND_ASYNC);
				m_bHumanPlay = false;
				if (IsGameOver())
				{
					m_bStarted = false;
					switch (GetWinner())
					{
					case -1:
						MessageBox(_T("很遗憾,您输了"), _T("温馨提示"), MB_OK | MB_ICONINFORMATION);
						break;
					case 1:
						MessageBox(_T("恭喜,您赢了"), _T("温馨提示"), MB_OK | MB_ICONINFORMATION);
						break;
					default:	//0
						AfxMessageBox(_T("平局"));
						break;
					}
				}
				else
				{
					Sleep(500);
					if (!SetPieceByAIAndShow(&m_objBoard))
					{
						AfxMessageBox(_T("积分不足或网络问题,请确保网络畅通且积分充足(若是积分不足,充值后可继续本次对局)"));
					}
					else
					{
						//PlaySound(MAKEINTRESOURCE(IDR_WAVE1), AfxGetResourceHandle(), SND_RESOURCE | SND_ASYNC);
						m_bHumanPlay = true;
						if (IsGameOver())
						{
							m_bStarted = false;
							switch (GetWinner())
							{
							case -1:
								MessageBox(_T("很遗憾,您输了"), _T("温馨提示"), MB_OK | MB_ICONINFORMATION);
								break;
							case 1:
								MessageBox(_T("恭喜,您赢了"), _T("温馨提示"), MB_OK | MB_ICONINFORMATION);
								break;
							default:	//0
								AfxMessageBox(_T("平局"));
								break;
							}
						}
					}
				}
			}
		}
	}
	CDialogEx::OnLButtonDown(nFlags, point);
}

BOOL CtestDlg::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message)
{
	CPoint pos;
	GetCursorPos(&pos);		//在整个屏幕上的坐标
	CRect rc;
	GetDlgItem(IDC_BOARD)->GetWindowRect(&rc);	//在整个屏幕上的坐标
	if (rc.PtInRect(pos))
	{
		if (m_bStarted && m_bHumanPlay)
		{
			SetCursor(LoadCursor(NULL, IDC_HAND));	//设置成手状,也可在OnMouseMove()中调用
		}
		else
		{
			SetCursor(LoadCursor(NULL, IDC_NO));	//设置成禁止光标
		}
		return TRUE;
	}

	return CDialogEx::OnSetCursor(pWnd, nHitTest, message);
}

void CtestDlg::Show()
{
	CRect rect;
	CRect rc;
	GetClientRect(&rect);
	CWnd* pBtn = GetDlgItem(IDC_START);
	if (pBtn != NULL)
		pBtn->GetClientRect(&rc);

	rect.right -= rc.right*1.5;
	CWnd* pBoard = GetDlgItem(IDC_BOARD);
	if (pBoard != NULL)
		pBoard->MoveWindow(&rect);

	DrawBoard(&m_objBoard);
	DrawPieces(&m_objBoard);
}

(13)完整的Inter.h文件如下


typedef signed char TBOARD;

//以下函数在程序运行开始的时候调用,仅调用一次
extern "C" __declspec(dllexport) bool Login(char* strLoginName, char* strPassword);		//登录

extern "C" __declspec(dllexport) bool InitFromModelFile(char* strModelFileName);	//使用模型文件初始化
extern "C" __declspec(dllexport) bool InitWithoutModelFile(int nBoardWidth, int nBoardHeight, int nWinLen);		//无模型文件时初始化

//以下函数在每局游戏开始的时候调用,每局游戏调用一次
extern "C" __declspec(dllexport) bool StartNewGame();			//开始游戏,并重置相关数据

//以下函数在每步落子的时候调用,每步落子调用一次
extern "C" __declspec(dllexport) bool SetPieceWithCoord(int nX, int nY);	//根据坐标落子
extern "C" __declspec(dllexport) bool SetPieceWithGUI(CStatic* pCtrlBoard, int nCursorXInCtrl, int nCursorYInCtrl);		//根据界面组件及屏幕鼠标位置落子

extern "C" __declspec(dllexport) bool SetPieceByAI(void);					//AI落子,该函数返回失败表示所登录的用户积分不足
extern "C" __declspec(dllexport) bool SetPieceByAIAndShow(CStatic* pCtrlBoard);		//AI落子并在界面上显示

extern "C" __declspec(dllexport) bool IsGameOver();		//游戏是否已经结束
extern "C" __declspec(dllexport) int GetWinner();		//获胜者

extern "C" __declspec(dllexport) bool DrawBoard(CStatic* pCtrlBoard);		//绘制棋盘
extern "C" __declspec(dllexport) bool DrawPieces(CStatic* pCtrlBoard);		//绘制所有棋子

//以下函数在需要的时候调用,非必须调用
extern "C" __declspec(dllexport) TBOARD* GetBoardData(int* pnBoardWidth, int* pnBoardHeight);	//获得当前棋局数组的内存区首地址(及矩阵的宽和高,如果需要的话)
extern "C" __declspec(dllexport) int GetPoint();

extern "C" __declspec(dllexport) bool SaveSteps(char* strDataFileName);		//保存棋局数据
extern "C" __declspec(dllexport) bool SaveModel(char* strModelFileName);	//保存模型数据

2. 文中所提的SDK(及案例的完整代码)可在如下地址下载:

官网下载:www.gnxxkj.com

github下载:https://github.com/wangdechang119

gitlab下载:https://gitlab.com/wangdechang119

gitee下载:https://gitee.com/wangdechang119

  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

De-Chang Wang

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值