我想做三国志

努力学习做游戏的小白

网络联机中国象棋

程序截图

环境配置

visual studio 2019,EasyX_20211109,字符集配置为 Unicode 字符集。

网络联机说明

网络编程需要一个服务器,这个服务器可以是自己的电脑,但最好是一台在公网上有固定 ip 的电脑,云服务器就是让你可以远程的这类电脑,至于自己的电脑作为服务器,那就得自己在 cmd 里输入 ipconfig/all 来查看“服务器 ip 地址”也就是自己电脑的 IP 地址。

IP 是因特网互联协议(Internet Protocol)的缩写,而 IP 地址是一个具体的地址,IP 这种协议是发送数据时要解码的一段数据,而 IP 地址是 IP 这种协议里面要包含的内容。

这个程序用的 ip 地址是 ipv4 地址,也就是用 4 字节的数据表示一个 IP 地址,也就是一个 unsigned long 来表示 ip 地址。

127.0.0.1 表示本主机,也就是 localhost,你可以不知道你电脑的 ip 地址,但只要在这台电脑上连到 127.0.0.1 就会连到自己。

然后用到 TCP 协议,TCP 就是传输控制协议(Transmission Control Protocol)的缩写,TCP 协议跟 IP 协议一样是一段发送出去的数据,有特定的编码方式,没什么好讲的,调一下别人封装好的库就能用了。

然后是端口号(port),这个是告诉电脑这段数据是给哪个程序的,也就是找到目标应用程序,是一个 16 位无符号整数,也就是一个 unsigned short 数据类型,不过在能取的所有值即 0~65535 的整数中,0~1024 的整数是给操作系统用的,应用程序的端口号一般大于 1024。

需要事先知道的概念就这些,当然网络编程还有很多概念,但是都没用上。

具体到编程语言的实现就是套接字(Socket)编程,我先给出两个案例,先别点进去看。

1:网络编程 helloworld

2:select 模式介绍

用到的库是<WinSocket2.h>这个库函数,这个库需要预加载,要开始套接字编程时需要先调用 WSAStartup,结束套接字编程时需要调用 WSACleanup。

也就是说模板是:

#include<WinSock2.h>
#pragma comment(lib, "Ws2_32.lib")
int main()
{
	WSADATA wsData;	// WSADATA windows 异步套接字数据
	// MAKEWORD(a, b)是一个宏,等效于 (a|(b<<8)),MAKEWORD(2, 2) 是版本号 2.2 的意思,写成 short 类型就是 0x0202
	// 注意 WSAStartup 的第二个数据不能传空指针,不然会报错
	WSAStartup(MAKEWORD(2, 2), &wsData);	// 这个函数用于初始化 WSADATA,第一个参数为版本号,是 short 类型,第二个参数是 WSADATA 的首地址,用于赋值

	// ...这里就是编程的内容

	WSACleanup();	// 这个函数用于释放系统资源
	return 0;
}

如果完全没接触过网络编程,或者对 C++ 套接字编程不熟悉的,那现在就可以点开那两个链接看了,我觉得我是做到的没有多余代码的,只要看函数说明应该能意会那些函数要怎么应用。

最后所需要的拓展知识是多线程的知识,只学一种。因为接受消息是一定会不停等待消息发送过来的,如果你不开一个线程来等待对面发消息过来,那么光是接受消息的功能就能令程序干不了任何事情,所以需要多线程。

具体用三个函数 CreateThread;WaitForSingleObject;CloseHandle;

要用多线程时引用一下 Windows.h 头文件,同时注意一下 WinSocket.h 这个头文件要放在 Windows.h 头文件之前,不然会有一些报错。

最简单的案例:

#include<Windows.h>
#include<strsafe.h>

struct ThreadFuncParam
{
	bool* isexit;
	int* num;
};

DWORD WINAPI ThreadFunc(LPVOID lParam)// 线程具体执行的代码
{
	ThreadFuncParam* param = (ThreadFuncParam*)lParam;
	while (!*param->isexit)
	{
		Sleep(1000);
		(*param->num)++;
	}
	return 0;
}

int main()
{
	HANDLE thread;

	bool isexit = false;
	int num = 0;
	ThreadFuncParam threadfuncparam;
	threadfuncparam.isexit = &isexit;
	threadfuncparam.num = &num;
	thread = CreateThread(NULL, 0, ThreadFunc, &threadfuncparam, 0, NULL);	// 创建线程,0、NULL 照抄,ThreadFunc、&threadfuncparam 是必须得传的

	WriteConsole(GetStdHandle(STD_OUTPUT_HANDLE), L"回车使线程结束\n", 8, NULL, NULL);	// 8 是字符个数,\0 不计在内
	getchar();
	isexit = true;

	WaitForSingleObject(thread, INFINITE);	// 等待线程结束,INFINITE 是永远等待的意思
	CloseHandle(thread);	// 关闭线程

	wchar_t t_Buf[128];
	size_t len = 0;
	StringCchPrintf(t_Buf, 128, L"线程运行秒数:%d\n", num);	// 这个相当于 sprintf 函数
	StringCchLength(t_Buf, 128, &len);	// 这个相当于 strlen 函数
	WriteConsole(GetStdHandle(STD_OUTPUT_HANDLE), t_Buf, len, NULL, NULL);	// 这个相当于 printf 函数
	getchar();
	return 0;
}

如果你已经看到这里了,其实后面的已经可以不用看了,完全可以独自写出来一个联机的程序,我这个项目就是证明。

个人实现

接下来就是个人实现这个项目的方法。

首先我实现了字符集转换,具体看字符集转换。字符集转换方便接受消息和显示消息。

服务器端用的是 select 模式,这种轮询的方式有信息进来不能立刻处理,得问问对面准备好了没有,准备好了再发送信息过去,所以需要一个缓冲区来将信息的处理先存进里面去,能发送消息的时候一次性取出来发过去,同时 select 模式下的 fd_set 的规则很简单,在 vs 里看一下源代码就知道了,只是一个动态数组的逻辑,而且最大接入数是固定的 FD_SETSIZE,直接用数组下标来区分是哪个客户端的消息处理即可,所以这个缓冲区我的写法是

std::vector<TheSendMessage> allmessage[FD_SETSIZE];

TheSendMessage 是发送消息的某种编码规则,长这样:

struct TheSendMessage
{
	char code;
	size_t len;
	char message[SENDMESSAGEMAXLEN];
};

SENDMESSAGEMAXLEN 是一条消息最长能是多长。

code 是消息码,方便接受到消息后进行解码,比如客户端告诉服务器端我进入了某个房间,如果是先发"I'll go into the room"让服务器判断一下,再发过去一个整型的数据,效率肯定不如用一个字节的消息码直接告诉服务器我要进入房间。

所有状态码的取值都用宏定义来固定

#define ISERRORMESSAGE 0	// 错误信息
#define ISSENDMESSAGE 1		// 发送消息
#define ISCONNECTNUMCHANGE 2// 在线人数改变
#define ISENTERROOM 3		// 进入房间
#define ISEXITROOM 4		// 退出房间
#define ISCONNECTMAXNUM 5	// 最大连接人数
#define ISHALLMESSAGE 6		// 大厅中房间有无人的数据
#define ISCHESSMOVE 7		// 棋子移动的数据
#define ISUNDO 8			// 嗯。。。
#define ISGIVEUP 9			// 认输
#define ISGETREADY 10		// 准备

len 是接收到的数据的有效长度,接受到一个信息接收到什么时候结束就通过这个数据来控制,不然接受着接受着把下一条信息的内容都给接受了就不好了。

接收到信息,解完码,再处理接收到的信息,流程就是这样。

接受信息的操作是用多线程来操作的,使线程退出的方法用的是一个 bool* isexit; 和在主线程里连接到服务器端来控制。用指针可以不用定义全局变量也能让两个线程共享内存,这样我在主线程让 isexit 变成 true 线程里的 isexit 也会变成 true,况且我也只开一个线程,不用考虑脏读的问题。服务器端连接到服务器端是为了避免无数据接入时的 select 阻塞住导致线程无法退出。这些待会看服务器端主函数代码的时候就能看到。

客户端就要先实现象棋的逻辑,这些我写在了 Chess.h 头文件里,这个反而是整个项目里最简单的事情。

最后以客户端为第一视角介绍一下这个项目。

首先打开程序会进入这个初始界面,这时没有联机,服务器还不知道你的存在。

点击联机,服务器知道了你的存在,它给你发来最大在线人数,和在线人数,并且你开辟出一块内存作为房间表,服务器告诉你每个房间里有没有人,你一一记录下来,与此同时你终于看到了大厅的模样。

一种情况

另一种情况

有人进入房间时你看到的会是这样。这时候服务器端挨个找了每一个在线的人,告诉他们有人进入某个房间了,要你们修改一下房间表,当你进入房间时,你会告诉服务器你进入了某个房间,服务器同时继续挨个告诉。

进入房间后双方都准备好。

提子

落子

落子那一瞬间你告诉服务器我要从 6,9 的位置下到 4,7 的位置,服务器告诉你对面的人你的下法,你对面的人同时将你的下法转换了下坐标系,也就是 8-6,9-9、8-4,9-7,最后对面那位得到的值是 2,0 到 4,2 的位置。

客户端和服务器端的位数要相同,因为代码里用到了 size_t,size_t 在 x32 下是 unsigned int 在 x64 下是 unsigned long long,我这里用的是 x64,接受消息时解码 size_t 的内容用的是 ntohll,发送消息时编码用的是 htonll,如果改为 32 位的程序就把所有 ntohll htonll 改一下。vs 下设置为 x64 编译器的方法为:

这个项目上传到 gitee 上了,不想配置直接下载就行:中国象棋联机

服务器端 server:

#define _WINSOCK_DEPRECATED_NO_WARNINGS
#define FD_SETSIZE 64
#define HALLNUM (FD_SETSIZE>>1)
#include<WinSock2.h>
#include<Windows.h>
#include<vector>
#include<strsafe.h>
#include<conio.h>
#pragma comment(lib, "Ws2_32.lib")
#define SENDMESSAGEMAXLEN 300
#define ISERRORMESSAGE 0
#define ISSENDMESSAGE 1
#define ISCONNECTNUMCHANGE 2
#define ISENTERROOM 3
#define ISEXITROOM 4
#define ISCONNECTMAXNUM 5
#define ISHALLMESSAGE 6
#define ISCHESSMOVE 7
#define ISUNDO 8
#define ISGIVEUP 9
#define ISGETREADY 10

struct People
{
	char roomnumber;
	bool isOne;
};

struct PeopleList
{
	People arr[FD_SETSIZE];
	size_t len;
};

struct Room
{
	int people1, people2;
	bool ispeople1, ispeople2;
};

void InitPeopleList(PeopleList* peoplelist)
{
	for (int i = 0; i < FD_SETSIZE; i++)
	{
		peoplelist->arr[i].roomnumber = -1;
	}
	peoplelist->len = 1;
}

size_t TransformCharToWideChar(const char* target, wchar_t** output)
{
	size_t len = MultiByteToWideChar(CP_ACP, 0, target, -1, NULL, 0);
	TCHAR* arr = new TCHAR[len];
	MultiByteToWideChar(CP_ACP, 0, target, -1, arr, len);
	*output = arr;
	return len;
}

struct TheSendMessage
{
	char code;
	size_t len;
	char message[SENDMESSAGEMAXLEN];
};

bool AddTSM(std::vector<TheSendMessage>* array, char code, size_t len, const char* message)
{
	// if (array->len >= MAXMESSAGE)return false;
	TheSendMessage temp;
	temp.code = code;
	temp.len = len;
	for (int i = 0; i < len; i++)temp.message[i] = message[i];
	array->push_back(temp);
	return true;
}

struct DealMessageParam
{
	SOCKET serveSocket;
	fd_set* SocketArray;
	Room (*hall)[HALLNUM];
	PeopleList *peoplelist;
	std::vector<TheSendMessage> (*allmessage)[FD_SETSIZE];
	bool isExit;
};


void My_Strcpy(char* target, const char* source, size_t len)
{
	for (int i = 0; i < len; i++)
	{
		target[i] = source[i];
	}
}

bool My_Strcmp(const char* target, const char* source, size_t len)
{
	for (size_t i = 0; i < len; i++)
		if (target[i] != source[i])return false;
	return true;
}

void WINAPI My_Printf(const char* arr)
{
	wchar_t* tarr;
	size_t szlen = TransformCharToWideChar(arr, &tarr);
	WriteConsole(GetStdHandle(STD_OUTPUT_HANDLE), tarr, szlen - 1, NULL, NULL);	// \0 被计算进去
	delete[] tarr;
}

void WINAPI My_Printf(const char* arr, const char* another)
{
	wchar_t* tarr;
	wchar_t* tanother;
	size_t szlen = TransformCharToWideChar(arr, &tarr);
	szlen += TransformCharToWideChar(another, &tanother);
	wchar_t* OutMsg = new wchar_t[szlen];
	StringCchPrintf(OutMsg, szlen, tarr, tanother);
	WriteConsole(GetStdHandle(STD_OUTPUT_HANDLE), OutMsg, szlen - 4, NULL, NULL);	// % s \0 \0 这四个被计算进去
	delete[] tarr;
	delete[] tanother;
	delete[] OutMsg;
}

DWORD WINAPI DealMessage(LPVOID lparam)
{
	fd_set AllWrite;
	FD_ZERO(&AllWrite);

	DealMessageParam* param = (DealMessageParam*)lparam;

	while (!param->isExit)
	{
		fd_set fdRead = *param->SocketArray;
		fd_set fdWrite = AllWrite;
		int iRet = select(0, &fdRead, &fdWrite, NULL, NULL);
		if (iRet > 0)
		{
			for (size_t i = 0; i < param->SocketArray->fd_count; i++)
			{
				if (FD_ISSET(param->SocketArray->fd_array[i], &fdWrite)&&
					param->SocketArray->fd_array[i]!=param->serveSocket)// 如果在可写集合里不为主机
				{
					for (int j = 0; j < (*param->allmessage)[i].size(); j++)// 所有种类的信息看看那条能发送
					{
						if ((*param->allmessage)[i][j].code != -1)
						{
							send(param->SocketArray->fd_array[i], &(*param->allmessage)[i][j].code, sizeof(char), 0);
							size_t messagelen = htonll((*param->allmessage)[i][j].len);
							send(param->SocketArray->fd_array[i], (char*)&messagelen, sizeof(messagelen), 0);
							send(param->SocketArray->fd_array[i], (char*)(*param->allmessage)[i][j].message,
								sizeof(char)* (*param->allmessage)[i][j].len, 0);
							(*param->allmessage)[i][j].code = -1;
						}
					}
					(*param->allmessage)[i].clear();
					FD_CLR(param->SocketArray->fd_array[i], &AllWrite);
					// My_Printf("完事了");
				}
				if (FD_ISSET(param->SocketArray->fd_array[i], &fdRead))// 如果连到服务器的套接字在可读的区域里
				{
					if (param->SocketArray->fd_array[i] == param->serveSocket)// 如果可读的是自己
					{
						if (param->SocketArray->fd_count < FD_SETSIZE)
						{
							sockaddr_in clientAddress;
							int clientlen = sizeof(clientAddress);
							SOCKET clientSocket = accept(param->serveSocket, (sockaddr*)&clientAddress, &clientlen);	// 接受
							FD_SET(clientSocket, param->SocketArray);
							for (int index = 1; index < param->SocketArray->fd_count; index++)// 告诉所有现存的人新人来了
							{
								FD_SET(param->SocketArray->fd_array[index], &AllWrite);
								if (param->SocketArray->fd_array[index] == clientSocket)// 如果是新加入的那个人
								{
									unsigned int maxconnect = htonl(FD_SETSIZE);
									AddTSM(&(*param->allmessage)[index], ISCONNECTMAXNUM,
										sizeof(unsigned int), (char*)&maxconnect);
									char t_sendmessage[FD_SETSIZE];
									for (int t_index = 0; t_index < HALLNUM; t_index++)
									{
										t_sendmessage[2 * t_index] = (*param->hall)[t_index].ispeople1;
										t_sendmessage[2 * t_index + 1] = (*param->hall)[t_index].ispeople2;
									}
									AddTSM(&(*param->allmessage)[index], ISHALLMESSAGE,
										FD_SETSIZE, (char*)t_sendmessage);
								}
								unsigned int connectnum = htonl(param->SocketArray->fd_count - 1);
								AddTSM(&(*param->allmessage)[index], ISCONNECTNUMCHANGE,
									sizeof(unsigned int), (char*)&connectnum);
							}
							param->peoplelist->len++;
							My_Printf("接受到连接:%s\n", inet_ntoa(clientAddress.sin_addr));
						}
						else
						{
							My_Printf("连接太多\n");
						}
					}
					else
					{
						char szText[SENDMESSAGEMAXLEN + sizeof(char)+sizeof(size_t)];
						int iRecv = recv(param->SocketArray->fd_array[i], szText,
							SENDMESSAGEMAXLEN + sizeof(char) + sizeof(size_t), 0);
						// 接受到消息
						if (iRecv > 0)
						{
							switch (szText[0])
							{
							case ISSENDMESSAGE:
							{
								if (param->peoplelist->arr[i].roomnumber != -1)
								{
									unsigned int t_roomnumber = param->peoplelist->arr[i].roomnumber;
									if (param->peoplelist->arr[i].isOne && (*param->hall)[t_roomnumber].ispeople2)
									{
										int theIndex = (*param->hall)[t_roomnumber].people2;
										FD_SET(param->SocketArray->fd_array[theIndex], &AllWrite);
										size_t messagelen = *(size_t*)(szText + sizeof(char));
										messagelen = ntohll(messagelen);
										AddTSM(&(*param->allmessage)[theIndex], ISSENDMESSAGE, messagelen,
											(char*)&szText[sizeof(char) + sizeof(size_t)]);
									}
									else if (!param->peoplelist->arr[i].isOne && (*param->hall)[t_roomnumber].ispeople1)
									{
										int theIndex = (*param->hall)[t_roomnumber].people1;
										FD_SET(param->SocketArray->fd_array[theIndex], &AllWrite);
										size_t messagelen = *(size_t*)(szText + sizeof(char));
										messagelen = ntohll(messagelen);
										AddTSM(&(*param->allmessage)[theIndex], ISSENDMESSAGE, messagelen,
											(char*)&szText[sizeof(char) + sizeof(size_t)]);
									}
								}
							}
								break;
							case ISENTERROOM:
							{
								size_t messagelen = *(size_t*)(szText + sizeof(char));
								messagelen = ntohll(messagelen);
								unsigned int roomnumber = ntohl(*(unsigned int*)(szText + sizeof(char) + sizeof(size_t)));
								if (roomnumber >= HALLNUM)
								{
									FD_SET(param->SocketArray->fd_array[i], &AllWrite);
									AddTSM(&(*param->allmessage)[i], ISERRORMESSAGE, 12, "no this room");
								}
								else if (param->peoplelist->arr[i].roomnumber != -1)
								{
									FD_SET(param->SocketArray->fd_array[i], &AllWrite);
									AddTSM(&(*param->allmessage)[i], ISERRORMESSAGE, 15, "you are in room");
								}
								else
								{
									int theroomnumber = -1;
									if (!(*param->hall)[roomnumber].ispeople1)
									{
										(*param->hall)[roomnumber].ispeople1 = true;
										(*param->hall)[roomnumber].people1 = i;
										param->peoplelist->arr[i].isOne = true;
										param->peoplelist->arr[i].roomnumber = roomnumber;
										theroomnumber = 2 * roomnumber;
									}
									else if (!(*param->hall)[roomnumber].ispeople2)
									{
										(*param->hall)[roomnumber].ispeople2 = true;
										(*param->hall)[roomnumber].people2 = i;
										param->peoplelist->arr[i].isOne = false;
										param->peoplelist->arr[i].roomnumber = roomnumber;
										theroomnumber = 2 * roomnumber + 1;
									}
									else
									{
										FD_SET(param->SocketArray->fd_array[i], &AllWrite);
										AddTSM(&(*param->allmessage)[i], ISERRORMESSAGE, 16, "the room is full");
									}
									if (theroomnumber != -1)
									{
										for (int index = 1; index < param->SocketArray->fd_count; index++)
										{
											FD_SET(param->SocketArray->fd_array[index], &AllWrite);
											unsigned int temp = htonl(theroomnumber);
											AddTSM(&(*param->allmessage)[index], ISENTERROOM,
												sizeof(unsigned int), (char*)&temp);
										}
									}
								}
							}
								break;
							case ISEXITROOM:
							{
								if (param->peoplelist->arr[i].roomnumber == -1)
								{
									FD_SET(param->SocketArray->fd_array[i], &AllWrite);
									AddTSM(&(*param->allmessage)[i], ISERRORMESSAGE, 15, "you are in hall");
								}
								else
								{
									unsigned int theroomnumber = 0;
									if (param->peoplelist->arr[i].isOne)
									{
										(*param->hall)[param->peoplelist->arr[i].roomnumber].ispeople1 = false;
										(*param->hall)[param->peoplelist->arr[i].roomnumber].people1 = -1;
										theroomnumber = 2 * param->peoplelist->arr[i].roomnumber;
									}
									else
									{
										(*param->hall)[param->peoplelist->arr[i].roomnumber].ispeople2 = false;
										(*param->hall)[param->peoplelist->arr[i].roomnumber].people2 = -1;
										theroomnumber = 2 * param->peoplelist->arr[i].roomnumber + 1;
									}
									param->peoplelist->arr[i].roomnumber = -1;
									param->peoplelist->arr[i].isOne = false;
									for (int index = 1; index < param->SocketArray->fd_count; index++)
									{
										FD_SET(param->SocketArray->fd_array[index], &AllWrite);
										unsigned int temp = htonl(theroomnumber);
										AddTSM(&(*param->allmessage)[index], ISEXITROOM,
											sizeof(unsigned int), (char*)&temp);
									}
								}
							}
								break;
							case ISCHESSMOVE:
							{
								unsigned int t_roomnumber = param->peoplelist->arr[i].roomnumber;
								int t_index=-1;
								if (param->peoplelist->arr[i].isOne)
								{
									if(param->hall[t_roomnumber]->ispeople2)
										t_index = param->hall[t_roomnumber]->people2;
								}
								else
								{
									if (param->hall[t_roomnumber]->ispeople1)
										t_index = param->hall[t_roomnumber]->people1;
								}
								if (t_index != -1)
								{
									size_t messagelen = *(size_t*)(szText + sizeof(char));
									messagelen = ntohll(messagelen);
									FD_SET(param->SocketArray->fd_array[t_index], &AllWrite);
									AddTSM(&(*param->allmessage)[t_index], ISCHESSMOVE, messagelen,
										(char*)&(szText[sizeof(char) + sizeof(size_t)]));
								}
								else
								{
									FD_SET(param->SocketArray->fd_array[i], &AllWrite);
									AddTSM(&(*param->allmessage)[i], ISERRORMESSAGE, 21,
										"no people in opposite");
								}
							}
								break;
							case ISGETREADY:
							{
								unsigned int t_roomnumber = param->peoplelist->arr[i].roomnumber;
								int t_index = -1;
								if (param->peoplelist->arr[i].isOne)
								{
									if (param->hall[t_roomnumber]->ispeople2)
										t_index = param->hall[t_roomnumber]->people2;
								}
								else
								{
									if (param->hall[t_roomnumber]->ispeople1)
										t_index = param->hall[t_roomnumber]->people1;
								}
								if (t_index != -1)
								{
									char t_code = ISGETREADY;	// 填充的数据
									FD_SET(param->SocketArray->fd_array[t_index], &AllWrite);
									AddTSM(&(*param->allmessage)[t_index], ISGETREADY, sizeof(char), &t_code);
								}
								else
								{
									FD_SET(param->SocketArray->fd_array[i], &AllWrite);
									AddTSM(&(*param->allmessage)[i], ISERRORMESSAGE, 21,
										"no people in opposite");
								}
							}
								break;
							case ISGIVEUP:
							{
								unsigned int t_roomnumber = param->peoplelist->arr[i].roomnumber;
								int t_index = -1;
								if (param->peoplelist->arr[i].isOne)
								{
									if (param->hall[t_roomnumber]->ispeople2)
										t_index = param->hall[t_roomnumber]->people2;
								}
								else
								{
									if (param->hall[t_roomnumber]->ispeople1)
										t_index = param->hall[t_roomnumber]->people1;
								}
								if (t_index != -1)
								{
									char t_code = ISGIVEUP;	// 填充的数据
									FD_SET(param->SocketArray->fd_array[t_index], &AllWrite);
									AddTSM(&(*param->allmessage)[t_index], ISGIVEUP, sizeof(char), &t_code);
								}
								else
								{
									FD_SET(param->SocketArray->fd_array[i], &AllWrite);
									AddTSM(&(*param->allmessage)[i], ISERRORMESSAGE, 21,
										"no people in opposite");
								}
							}
								break;
							default:
								break;
							}
						}
						// 断开连接
						else
						{
							closesocket(param->SocketArray->fd_array[i]);
							int theroomnumber = -1;
							if (param->peoplelist->arr[i].roomnumber != -1)
							{
								if (param->peoplelist->arr[i].isOne)
								{
									(*param->hall)[param->peoplelist->arr[i].roomnumber].ispeople1 = false;
									(*param->hall)[param->peoplelist->arr[i].roomnumber].people1 = -1;
									theroomnumber = param->peoplelist->arr[i].roomnumber * 2;
								}
								else
								{
									(*param->hall)[param->peoplelist->arr[i].roomnumber].ispeople2 = false;
									(*param->hall)[param->peoplelist->arr[i].roomnumber].people2 = -1;
									theroomnumber = param->peoplelist->arr[i].roomnumber * 2 + 1;
								}
								param->peoplelist->arr[i].roomnumber = -1;
								param->peoplelist->arr[i].isOne = false;
							}
							for (int index = i + 1; index < param->peoplelist->len; index++)
							{
								if (param->peoplelist->arr[index].roomnumber != -1)
								{
									if (param->peoplelist->arr[index].isOne)(*param->hall)[param->peoplelist->arr[index].roomnumber].people1--;
									else (*param->hall)[param->peoplelist->arr[index].roomnumber].people2--;
								}
								param->peoplelist->arr[index - 1] = param->peoplelist->arr[index];
							}
							param->peoplelist->len--;
							FD_CLR(param->SocketArray->fd_array[i], param->SocketArray);
							for (int index = 1; index < param->SocketArray->fd_count; index++)// 告诉所有现存的人有人走了
							{
								FD_SET(param->SocketArray->fd_array[index], &AllWrite);
								unsigned int connectnum = htonl(param->SocketArray->fd_count - 1);
								AddTSM(&(*param->allmessage)[index], ISCONNECTNUMCHANGE,
									sizeof(unsigned int), (char*)&connectnum);
								if (theroomnumber != -1)
								{
									unsigned int temp = htonl(theroomnumber);
									AddTSM(&(*param->allmessage)[index], ISEXITROOM,
										sizeof(unsigned int), (char*)&temp);
								}
							}
							i--;
						}
					}
				}
			}
		}
		else
		{
			My_Printf("故障了\n");
			closesocket(param->serveSocket);
			FD_CLR(param->serveSocket, param->SocketArray);
			break;
		}
	}
	return 0;
}

int main()
{
	WSAData wsa;
	WSAStartup(MAKEWORD(2, 2), &wsa);

	SOCKET serveSocket = socket(AF_INET, SOCK_STREAM, 0);

	sockaddr_in serveAddress;
	serveAddress.sin_addr.S_un.S_addr = htonl(ADDR_ANY);	// ip 地址
	serveAddress.sin_family = AF_INET;	// ipv4
	serveAddress.sin_port = htons(6000);	// 端口号

	bind(serveSocket, (sockaddr*)&serveAddress, sizeof(serveAddress));	// 绑定

	listen(serveSocket, 5);	// 监听

	fd_set fdSocket;
	FD_ZERO(&fdSocket);
	FD_SET(serveSocket, &fdSocket);


	Room hall[HALLNUM];
	PeopleList peoplelist;
	std::vector<TheSendMessage> allmessage[FD_SETSIZE];
	InitPeopleList((PeopleList*)&peoplelist);
	for (int i = 0; i < HALLNUM; i++)
	{
		hall[i].ispeople1 = false;
		hall[i].ispeople2 = false;
		hall[i].people1 = -1;
		hall[i].people2 = -1;
	}
	HANDLE DealMes;
	DealMessageParam dealmessageparam;
	dealmessageparam.isExit = false;
	dealmessageparam.SocketArray = &fdSocket;
	dealmessageparam.serveSocket = serveSocket;
	dealmessageparam.allmessage = &allmessage;
	dealmessageparam.peoplelist = &peoplelist;
	dealmessageparam.hall = &hall;
	DealMes = CreateThread(NULL, 0, DealMessage, &dealmessageparam, 0, NULL);

	getch();
	dealmessageparam.isExit = true;

	SOCKET clientSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	serveAddress.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
	serveAddress.sin_port = htons(6000);
	serveAddress.sin_family = AF_INET;
	connect(clientSocket, (sockaddr*)&serveAddress, sizeof(serveAddress));	// 连过去避免 select 阻塞住,可控一些

	WaitForSingleObject(DealMes, INFINITE);
	CloseHandle(DealMes);

	shutdown(serveSocket, SD_RECEIVE);
	for (int i = 0; i < fdSocket.fd_count; i++)
	{
		closesocket(fdSocket.fd_array[i]);
	}
	WSACleanup();
	return 0;
}

My_Gui_Button.h:

#pragma once
#include<string>
#include<functional>
#include<graphics.h>

TCHAR* TransformCharToWideChar(const char* target)
{
	int len = MultiByteToWideChar(CP_ACP, 0, target, -1, NULL, 0);
	TCHAR* arr = new TCHAR[len];
	MultiByteToWideChar(CP_ACP, 0, target, -1, arr, len);
	return arr;
}


namespace MGB
{
	struct Vec2
	{
		double x, y;
	};

	struct Button
	{
		Vec2 pos;
		Vec2 anchor;
		Vec2 Size;
		std::function<bool()> CallbackFunc;
		std::string name;
		void operator=(Button num)
		{
			this->anchor = num.anchor;
			this->CallbackFunc = num.CallbackFunc;
			this->name = num.name;
			this->pos = num.pos;
			this->Size = num.Size;
		}
	};

	struct List_Node_Button
	{
		Button button;
		List_Node_Button* next;
	};

	struct List_Button
	{
		List_Node_Button* head;
		unsigned long long len;
		List_Node_Button* operatButton;
		bool ispress;
	};
}

bool isInButton(double x, double y, MGB::Button num)
{
	double l_x = num.pos.x - num.Size.x * num.anchor.x;
	double l_y = num.pos.y - num.Size.y * num.anchor.y;
	double r_x = num.pos.x + num.Size.x * (1 - num.anchor.x);
	double r_y = num.pos.y + num.Size.y * (1 - num.anchor.y);

	if (x > l_x && x < r_x && y > l_y && y < r_y)return true;
	return false;
}

void drawRoundedRectangle(double l_x, double l_y, double r_x, double r_y)
{
	double Size_x = r_x - l_x;
	double Size_y = r_y - l_y;
	roundrect(l_x, l_y, r_x, r_y, Size_x / 3.0, Size_y / 3.0);
}

void DrawButton(MGB::Button num, MGB::Vec2 offset = { 0, 0 })
{
	double l_x = num.pos.x - num.Size.x * num.anchor.x;
	double l_y = num.pos.y - num.Size.y * num.anchor.y;
	double r_x = num.pos.x + num.Size.x * (1 - num.anchor.x);
	double r_y = num.pos.y + num.Size.y * (1 - num.anchor.y);
	drawRoundedRectangle(l_x, l_y, r_x, r_y);
	l_x += num.Size.x / 6.0;
	l_y += num.Size.y / 6.0;
	r_x -= num.Size.x / 6.0;
	r_y -= num.Size.y / 6.0;
	double Size_x = r_x - l_x;
	double Size_y = r_y - l_y;
	if (num.name.size() != 0)
	{
		settextstyle(Size_y, Size_x / (double)num.name.size(), _T("Consolas"));
		TCHAR* arr = TransformCharToWideChar(num.name.c_str());
		outtextxy(l_x + offset.x * Size_x, l_y + offset.y * Size_y, arr);
		delete[] arr;
	}
}

void DrawButton_Restore(MGB::Button num)
{
	COLORREF col = getbkcolor();
	col = col ^ ((1 << 24) - 1);
	setlinecolor(col);
	setlinestyle(PS_SOLID, 1);
	settextcolor(col);
	DrawButton(num);
}

void DrawButton_GetTo(MGB::Button num)
{
	COLORREF col = getbkcolor();
	col = col ^ ((1 << 24) - 1);
	setlinecolor(col);
	setlinestyle(PS_SOLID, 5);
	settextcolor(col);
	DrawButton(num);
}

void DrawButton_Response(MGB::Button num)
{
	COLORREF col = getbkcolor();
	col = col ^ ((1 << 24) - 1);
	setlinecolor(col);
	setlinestyle(PS_SOLID, 5);
	settextcolor(col);
	DrawButton(num, { 0.05, 0.05 });
}

MGB::Button CreateButton(MGB::Vec2 pos, MGB::Vec2 anchor, MGB::Vec2 Size, std::string name, std::function<bool()> CallbackFunc)
{
	MGB::Button result;
	result.pos = pos;
	result.anchor = anchor;
	result.Size = Size;
	result.name = name;
	result.CallbackFunc = CallbackFunc;
	return result;
}

MGB::List_Node_Button* CreateList_Node_Button(MGB::Button button)
{
	MGB::List_Node_Button* result = new MGB::List_Node_Button;
	result->button = button;
	result->next = nullptr;
	return result;
}

void clearList_Node_Button(MGB::List_Node_Button* (&head))
{
	MGB::List_Node_Button* current = head;
	while (current != nullptr)
	{
		MGB::List_Node_Button* temp = current;
		current = current->next;
		delete temp;
	}
	head = nullptr;
}

void addChild_List_Node_Button(MGB::List_Node_Button* parent, MGB::List_Node_Button* child)
{
	if (parent->next != nullptr)clearList_Node_Button(parent->next);
	parent->next = child;
}

void addToChild_List_Node_Button(MGB::List_Node_Button* head, MGB::List_Node_Button* child)
{
	MGB::List_Node_Button* current = head;
	while (current->next != nullptr)current = current->next;
	current->next = child;
}

MGB::List_Button Create_List_Button()
{
	MGB::List_Button result;
	result.head = nullptr;
	result.len = 0;
	result.operatButton = nullptr;
	result.ispress = false;
	return result;
}

void addChild_List_Button(MGB::List_Button& num, MGB::List_Node_Button* child)
{
	if (num.head == nullptr)num.head = child;
	else addToChild_List_Node_Button(num.head, child);
	num.len++;
}

void Clear_List_Button(MGB::List_Button& num)
{
	num.len = 0;
	clearList_Node_Button(num.head);
	if (num.operatButton != nullptr)delete num.operatButton;
	num.operatButton = nullptr;
}

MGB::List_Node_Button* GetOpeartButton(MGB::List_Button& num, double x, double y)
{
	MGB::List_Node_Button* last = nullptr;
	MGB::List_Node_Button* current = num.head;
	while (current != nullptr && !isInButton(x, y, current->button))
	{
		last = current;
		current = current->next;
	}
	if (current != nullptr)
	{
		if (last != nullptr)
			last->next = current->next;
		else num.head = current->next;
		current->next = nullptr;
		num.len--;
	}
	return current;
}

void addToHead_List_Button(MGB::List_Button& num, MGB::List_Node_Button* (&head))
{
	head->next = num.head;
	num.head = head;
	head = nullptr;
	num.len++;
}

void DrawList_Button(MGB::List_Button num)
{
	MGB::List_Node_Button* current = num.head;
	while (current != nullptr)
	{
		DrawButton_Restore(current->button);
		current = current->next;
	}
}

void Trigger_Button(ExMessage* msg, MGB::List_Button& List_Button)
{
	if (!List_Button.ispress && msg->lbutton)
	{
		List_Button.ispress = true;
		if (List_Button.operatButton != nullptr)DrawButton_Response(List_Button.operatButton->button);
		DrawList_Button(List_Button);
	}
	else if (!List_Button.ispress && !msg->lbutton)
	{
		if (List_Button.operatButton == nullptr)
			List_Button.operatButton = GetOpeartButton(List_Button, msg->x, msg->y);
		else if (!isInButton(msg->x, msg->y, List_Button.operatButton->button))
		{
			addToHead_List_Button(List_Button, List_Button.operatButton);
			List_Button.operatButton = GetOpeartButton(List_Button, msg->x, msg->y);
		}
		if (List_Button.operatButton != nullptr)DrawButton_GetTo(List_Button.operatButton->button);
		DrawList_Button(List_Button);
	}
	else if (List_Button.ispress && msg->lbutton)
	{
		if (List_Button.operatButton != nullptr)DrawButton_Response(List_Button.operatButton->button);
		DrawList_Button(List_Button);
	}
	else if (List_Button.ispress && !msg->lbutton)
	{
		List_Button.ispress = false;
		DrawList_Button(List_Button);
		if (List_Button.operatButton != nullptr)
		{
			if (isInButton(msg->x, msg->y, List_Button.operatButton->button))
			{
				List_Button.operatButton->button.CallbackFunc();
				DrawButton_GetTo(List_Button.operatButton->button);
			}
			else DrawButton_Restore(List_Button.operatButton->button);
			addToHead_List_Button(List_Button, List_Button.operatButton);
		}
	}
}

Chess.h

#pragma once
#ifndef PI
#define PI 3.1415926536
#endif // !PI 3.1415926536


#include"My_Gui_Button.h"
#include<list>
#include<math.h>


COLORREF BOARDCOL = RGB(212, 165, 121);		// 棋盘颜色
COLORREF PIECECOL = RGB(254, 201, 139);		// 棋子颜色
COLORREF DIFFERENTCOL = RGB(128, 128, 128);	// 不同颜色,用于填充
// 二维向量,棋盘数组的坐标
struct Vec2
{
	char x, y;
};
// 动作类,实现动画效果
class Action
{
	double delay;		// 延迟时间
	double FPS;			// 帧率
	double progress;	// 进度
	std::function<bool(double progress)> Act; // 行动时的函数,根据 process 确定进行到什么程度
	std::function<bool()>End_Act;	// 行动结束时的函数
	MGB::Vec2 Start;	// 进行移动的行动时起始位置的标记
	MGB::Vec2 End;		// 进行移动的行动时终止位置的标记
	std::string Sign;	// 字符串标记,用于保存行动中需要的字符信息
public:
	Action() :delay(0), FPS(0), progress(0)
	{
		Start = { 0, 0 };
		End = { 0, 0 };
	}
	Action(double delay, double FPS, double progress) :delay(delay), FPS(FPS), progress(progress)
	{
		Start = { 0, 0 };
		End = { 0, 0 };
	}
	void SetAct(std::function<bool(double progress)> Act)
	{
		this->Act = Act;
	}
	void SetEnd_Act(std::function<bool()> End_Act) { this->End_Act = End_Act; }
	void SetBasicValue(double delay, double FPS, double progress)
	{
		this->delay = delay;
		this->FPS = FPS;
		this->progress = progress;
	}
	// 运行每一帧
	bool RunEveryFrameAction()
	{
		if (progress > 1.0)return false;
		Act(progress);
		Sleep(1000 * FPS);
		progress += FPS / delay;
		if (progress > 1.0)
		{
			End_Act();
			return false;
		}
		return true;
	}
	// 设置移动路径
	void SetMoveAct_Route(MGB::Vec2 Start, MGB::Vec2 End)
	{
		this->Start = Start;
		this->End = End;
	}
	// 得到移动到现在的位置,这里可以改动轨迹方程
	MGB::Vec2 GetMoveAct_Current()
	{
		return { Start.x + (End.x - Start.x) * progress, Start.y + (End.y - Start.y) * progress };
	}
	MGB::Vec2 GetEnd() { return End; }
	MGB::Vec2 GetStart() { return Start; }
	void SetSign(std::string Sign) { this->Sign = Sign; }
	std::string GetSign() { return this->Sign; }
};
// 棋子类,实现画棋子功能,同时也能保存棋子在棋盘中的坐标
class Piece
{
	Vec2 pos;				// 位置
	std::string piece_Name;	// 棋子名字
	COLORREF Camp;			// 阵营
public:
	Piece()
	{
		pos = { 0, 0 };
		piece_Name = "";
		Camp = BOARDCOL;
	}
	Piece(Vec2 pos, std::string piece_Name, COLORREF Camp) :piece_Name(piece_Name), Camp(Camp)
	{
		this->pos = pos;
	}
	~Piece() {}
	void setPos(const Vec2 pos) { this->pos = pos; }
	void setpiece_Name(const std::string piece_Name) { this->piece_Name = piece_Name; }
	void setCamp(const COLORREF Camp) { this->Camp = Camp; }
	Vec2 getPos() { return pos; }
	std::string getpiece_Name() { return piece_Name; }
	COLORREF getCamp() { return Camp; }
	void DrawPiece_Ori(MGB::Vec2 place, MGB::Vec2 size)
	{
		double zoomOut = 0.8;
		setlinecolor(DIFFERENTCOL);
		ellipse(place.x - size.x * zoomOut / 2.0, place.y - size.y * zoomOut / 2.0,
			place.x + size.x * zoomOut / 2.0, place.y + size.y * zoomOut / 2.0);
		setfillcolor(PIECECOL);
		floodfill(place.x, place.y, DIFFERENTCOL);
		setlinecolor(PIECECOL);
		ellipse(place.x - size.x * zoomOut / 2.0, place.y - size.y * zoomOut / 2.0,
			place.x + size.x * zoomOut / 2.0, place.y + size.y * zoomOut / 2.0);
		setlinecolor(Camp);
		double Ratio = 0.7;
		double skewing = zoomOut * (1 - Ratio) / 4.0;
		ellipse(place.x - size.x * zoomOut * Ratio / 2.0, place.y - size.y * zoomOut * Ratio / 2.0 - size.y * skewing,
			place.x + size.x * zoomOut * Ratio / 2.0, place.y + size.y * zoomOut * Ratio / 2.0 - size.y * skewing);
		double a = size.x * zoomOut * Ratio / 2.0;
		double b = size.y * zoomOut * Ratio / 2.0;
		double text_size = 2 * sqrt(a * a * b * b / (a * a + b * b));
		settextstyle((int)text_size, (int)(text_size / 2.0), L"Consolas");
		settextcolor(Camp);
		setbkmode(TRANSPARENT);
		TCHAR* arr = TransformCharToWideChar(piece_Name.c_str());
		outtextxy(place.x - text_size / 2.0, place.y - text_size / 2.0 - size.y * skewing, arr);
		delete[] arr;
	}
	void DrawPiece_Pick(MGB::Vec2 place, MGB::Vec2 size)
	{
		double zoomOut = 1;
		setlinecolor(DIFFERENTCOL);
		ellipse(place.x - size.x * zoomOut / 2.0, place.y - size.y * zoomOut / 2.0,
			place.x + size.x * zoomOut / 2.0, place.y + size.y * zoomOut / 2.0);
		setfillcolor(PIECECOL);
		floodfill(place.x, place.y, DIFFERENTCOL);
		setlinecolor(PIECECOL);
		ellipse(place.x - size.x * zoomOut / 2.0, place.y - size.y * zoomOut / 2.0,
			place.x + size.x * zoomOut / 2.0, place.y + size.y * zoomOut / 2.0);
		setlinecolor(Camp);
		double Ratio = 0.7;
		double skewing = zoomOut * (1 - Ratio) / 4.0;
		ellipse(place.x - size.x * zoomOut * Ratio / 2.0, place.y - size.y * zoomOut * Ratio / 2.0 - size.y * skewing,
			place.x + size.x * zoomOut * Ratio / 2.0, place.y + size.y * zoomOut * Ratio / 2.0 - size.y * skewing);
		double a = size.x * zoomOut * Ratio / 2.0;
		double b = size.y * zoomOut * Ratio / 2.0;
		double text_size = 2 * sqrt(a * a * b * b / (a * a + b * b));
		settextstyle(text_size, text_size / 2.0, L"Consolas");
		settextcolor(Camp);
		setbkmode(TRANSPARENT);
		TCHAR* arr = TransformCharToWideChar(piece_Name.c_str());
		outtextxy(place.x - text_size / 2.0, place.y - text_size / 2.0 - size.y * skewing, arr);
		delete[]arr;
	}
};
// 棋盘类,画棋盘和棋子,根据数组下标寻找该位置的棋子
class ChessBoard
{
	Piece* map[10][9] = { nullptr };	// 地图数组
	std::list<Piece*> alivePool;		// 生存池,可以理解为静态链表
	MGB::Vec2 pos;	// 位置
	MGB::Vec2 size;	// 尺寸
	COLORREF Camp;	// 第一人称阵营
public:
	ChessBoard()
	{
		pos = { 0, 0 };
		size = { 0, 0 };
		Camp = BLACK;
	}
	ChessBoard(MGB::Vec2 pos, MGB::Vec2 size, COLORREF Camp) :Camp(Camp)
	{
		this->pos = pos;
		this->size = size;
	}
	~ChessBoard()
	{
		ClearMap();
	}
	MGB::Vec2 getGrid() { return { size.x / 9.0, size.y / 10.0 }; }
	MGB::Vec2 getStart() { return { pos.x - size.x / 2.0 + getGrid().x / 2.0, pos.y - size.y / 2.0 + getGrid().y / 2.0 }; }
	void initPiece(Vec2 pos, std::string name, COLORREF Camp, bool as = true, bool isonly = false)
	{
		Piece* temp;
		temp = new Piece(pos, name, Camp);
		alivePool.push_back(temp);
		map[pos.y][pos.x] = temp;
		if (isonly)return;
		temp = new Piece({ 8 - pos.x, pos.y }, name, Camp);
		alivePool.push_back(temp);
		map[pos.y][8 - pos.x] = temp;
		if (!as)return;
		COLORREF anoCamp = ((Camp == RED) ? BLACK : RED);
		temp = new Piece({ pos.x, 9 - pos.y }, name, anoCamp);
		alivePool.push_back(temp);
		map[9 - pos.y][pos.x] = temp;
		temp = new Piece({ 8 - pos.x, 9 - pos.y }, name, anoCamp);
		alivePool.push_back(temp);
		map[9 - pos.y][8 - pos.x] = temp;
	}
	void InitMap()
	{
		initPiece({ 0, 9 }, "車", Camp);
		initPiece({ 1, 9 }, "马", Camp);
		if (Camp == BLACK)
		{
			initPiece({ 2, 0 }, "相", RED, false);
			initPiece({ 2, 9 }, "象", BLACK, false);
			initPiece({ 0, 3 }, "兵", RED, false);
			initPiece({ 0, 6 }, "卒", BLACK, false);
			initPiece({ 2, 3 }, "兵", RED, false);
			initPiece({ 2, 6 }, "卒", BLACK, false);
			initPiece({ 4, 0 }, "帅", RED, false, true);
			initPiece({ 4, 9 }, "将", BLACK, false, true);
			initPiece({ 4, 3 }, "兵", RED, false, true);
			initPiece({ 4, 6 }, "卒", BLACK, false, true);
		}
		else
		{
			initPiece({ 2, 0 }, "象", BLACK, false);
			initPiece({ 2, 9 }, "相", RED, false);
			initPiece({ 0, 6 }, "兵", RED, false);
			initPiece({ 0, 3 }, "卒", BLACK, false);
			initPiece({ 2, 6 }, "兵", RED, false);
			initPiece({ 2, 3 }, "卒", BLACK, false);
			initPiece({ 4, 9 }, "帅", RED, false, true);
			initPiece({ 4, 0 }, "将", BLACK, false, true);
			initPiece({ 4, 6 }, "兵", RED, false, true);
			initPiece({ 4, 3 }, "卒", BLACK, false, true);
		}
		initPiece({ 3, 9 }, "士", Camp);
		initPiece({ 1, 7 }, "炮", Camp);
	}
	void ClearMap()
	{
		for (Piece* i : alivePool)delete i;
		alivePool.clear();
		for (int i = 0; i < 10; i++)
			for (int j = 0; j < 9; j++)
				map[i][j] = nullptr;
	}
	void DrawArcPoint(MGB::Vec2 pos, bool isLeft = true, bool isRight = true)
	{
		MGB::Vec2 grid = { size.x / 9.0, size.y / 10.0 };
		if (isLeft)
		{
			arc(pos.x - grid.x / 2.0, pos.y - grid.y / 2.0, pos.x - grid.x / 6.0, pos.y - grid.y / 6.0, PI * 3 / 2.0, PI * 2);
			arc(pos.x - grid.x / 2.0, pos.y + grid.y / 6.0, pos.x - grid.x / 6.0, pos.y + grid.y / 2.0, 0, PI / 2.0);
		}
		if (isRight)
		{
			arc(pos.x + grid.x / 6.0, pos.y - grid.y / 2.0, pos.x + grid.x / 2.0, pos.y - grid.y / 6.0, PI, PI * 3 / 2.0);
			arc(pos.x + grid.x / 6.0, pos.y + grid.y / 6.0, pos.x + grid.x / 2.0, pos.y + grid.y / 2.0, PI / 2.0, PI);
		}
	}
	void Draw_Board()
	{
		setlinecolor(DIFFERENTCOL);
		rectangle(pos.x - size.x / 2.0, pos.y - size.y / 2.0, pos.x + size.x / 2.0, pos.y + size.y / 2.0);
		setfillcolor(BOARDCOL);
		floodfill(pos.x, pos.y, DIFFERENTCOL);
		setlinecolor(BOARDCOL);
		rectangle(pos.x - size.x / 2.0, pos.y - size.y / 2.0, pos.x + size.x / 2.0, pos.y + size.y / 2.0);
		MGB::Vec2 grid = getGrid();
		MGB::Vec2 start = getStart();
		setlinecolor(RGB(62, 23, 0));
		for (int i = 0; i < 4; i++)
		{
			for (int j = 0; j < 8; j++)
			{
				rectangle(grid.x * j + start.x, grid.y * i + start.y,
					grid.x * (j + 1) + start.x, grid.y * (i + 1) + start.y);
				rectangle(grid.x * j + start.x, grid.y * (i + 5) + start.y,
					grid.x * (j + 1) + start.x, grid.y * (i + 6) + start.y);
			}
		}
		line(grid.x * 3 + start.x, grid.y * 0 + start.y, grid.x * 5 + start.x, grid.y * 2 + start.y);
		line(grid.x * 3 + start.x, grid.y * 2 + start.y, grid.x * 5 + start.x, grid.y * 0 + start.y);
		line(grid.x * 3 + start.x, grid.y * 9 + start.y, grid.x * 5 + start.x, grid.y * 7 + start.y);
		line(grid.x * 3 + start.x, grid.y * 7 + start.y, grid.x * 5 + start.x, grid.y * 9 + start.y);
		// 画炮台,兵,卒,炮一开始放的地方
		for (int i = 1; i < 4; i++)
		{
			DrawArcPoint({ start.x + grid.x * i * 2, start.y + grid.y * 3 });
			DrawArcPoint({ start.x + grid.x * i * 2, start.y + grid.y * 6 });
		}
		DrawArcPoint({ start.x + grid.x, start.y + grid.y * 2 });
		DrawArcPoint({ start.x + grid.x, start.y + grid.y * 7 });
		DrawArcPoint({ start.x + grid.x * 7, start.y + grid.y * 2 });
		DrawArcPoint({ start.x + grid.x * 7, start.y + grid.y * 7 });

		DrawArcPoint({ start.x, start.y + grid.y * 3 }, false, true);
		DrawArcPoint({ start.x, start.y + grid.y * 6 }, false, true);
		DrawArcPoint({ start.x + grid.x * 8, start.y + grid.y * 3 }, true, false);
		DrawArcPoint({ start.x + grid.x * 8, start.y + grid.y * 6 }, true, false);

		double zoomOut = 0.8;
		MGB::Vec2 offset = { (1 - zoomOut) * grid.x / 2.0, (1 - zoomOut) * grid.y / 2.0 };
		settextstyle(grid.y * zoomOut, (grid.x / 2.0) * zoomOut, L"Consolas");
		settextcolor(BLACK);
		setbkmode(TRANSPARENT);
		outtextxy(start.x + grid.x * 1 + offset.x, start.y + grid.y * 4 + offset.y, L"楚");
		outtextxy(start.x + grid.x * 2 + offset.x, start.y + grid.y * 4 + offset.y, L"河");
		outtextxy(start.x + grid.x * 5 + offset.x, start.y + grid.y * 4 + offset.y, L"汉");
		outtextxy(start.x + grid.x * 6 + offset.x, start.y + grid.y * 4 + offset.y, L"界");
	}
	void PutPiece()
	{
		MGB::Vec2 grid = { size.x / 9.0, size.y / 10.0 };
		MGB::Vec2 start = { pos.x - size.x / 2.0 + grid.x / 2.0, pos.y - size.y / 2.0 + grid.y / 2.0 };
		for (Piece* i : alivePool)
			i->DrawPiece_Ori({ start.x + grid.x * i->getPos().x, start.y + grid.y * i->getPos().y }, grid);
	}
	MGB::Vec2 GetPlace() { return pos; }
	MGB::Vec2 GetSize() { return size; }
	COLORREF GetCamp() { return Camp; }
	void setPos(MGB::Vec2 pos) { this->pos = pos; }
	void setSize(MGB::Vec2 size) { this->size = size; }
	void CampOpposite()
	{
		if (Camp == RED)Camp = BLACK;
		else Camp = RED;
	}
	Vec2 GetMapVec2(double x, double y)
	{
		MGB::Vec2 grid = { size.x / 9.0, size.y / 10.0 };
		return { (char)(x / grid.x), (char)(y / grid.y) };
	}
	Piece* GetOperatePiece(double x, double y)
	{
		MGB::Vec2 grid = { size.x / 9.0, size.y / 10.0 };
		if (x > 9 * grid.x || x < 0 || y>10 * grid.y || y < 0)return nullptr;
		Piece* result = map[(int)(y / grid.y)][(int)(x / grid.x)];
		map[(int)(y / grid.y)][(int)(x / grid.x)] = nullptr;
		std::list<Piece*>::iterator ite = alivePool.begin();
		if (result != nullptr)
		{
			for (std::list<Piece*>::iterator ite = alivePool.begin(); ite != alivePool.end(); ite++)
			{
				if (*ite == result)
				{
					alivePool.erase(ite);
					break;
				}
			}
		}
		return result;
	}
	Piece* GetOperatePiece(Vec2 place)
	{
		Piece* result = map[place.y][place.x];
		map[place.y][place.x] = nullptr;
		std::list<Piece*>::iterator ite = alivePool.begin();
		if (result != nullptr)
		{
			for (std::list<Piece*>::iterator ite = alivePool.begin(); ite != alivePool.end(); ite++)
			{
				if (*ite == result)
				{
					alivePool.erase(ite);
					break;
				}
			}
		}
		return result;
	}
	void TakePiece(Piece& num)
	{
		MGB::Vec2 grid = { size.x / 9.0, size.y / 10.0 };
		MGB::Vec2 start = { pos.x - size.x / 2.0 + grid.x / 2.0, pos.y - size.y / 2.0 + grid.y / 2.0 };
		num.DrawPiece_Pick({ start.x + grid.x * num.getPos().x, start.y + grid.y * num.getPos().y }, grid);
	}
	bool isInMap(Vec2 place) { return !(place.x > 8 || place.x < 0 || place.y>9 || place.y < 0); }
	bool isCanThrough(Vec2 place, COLORREF Camp)
	{
		if (!isInMap(place))return false;
		if (map[place.y][place.x] != nullptr && map[place.y][place.x]->getCamp() == Camp)return false;
		return true;
	}
	Piece* GetMapPiece(Vec2 place)
	{
		return map[place.y][place.x];
	}
	void DrawOperationalArea(std::list<Vec2>& list)
	{
		MGB::Vec2 grid = { size.x / 9.0, size.y / 10.0 };
		MGB::Vec2 start = { pos.x - size.x / 2.0 + grid.x / 2.0, pos.y - size.y / 2.0 + grid.y / 2.0 };
		setfillcolor(GREEN);
		for (Vec2 i : list)
			solidcircle(i.x * grid.x + start.x, i.y * grid.y + start.y, min(grid.x, grid.y) / 5.0);
	}
	bool PutPieceInBoard(Piece* piece)
	{
		if (piece == nullptr)return false;
		Piece* ori = map[piece->getPos().y][piece->getPos().x];
		map[piece->getPos().y][piece->getPos().x] = piece;
		alivePool.push_back(piece);
		if (ori != nullptr)
		{
			std::list<Piece*>::iterator ite = alivePool.begin();
			for (std::list<Piece*>::iterator ite = alivePool.begin(); ite != alivePool.end(); ite++)
			{
				if (*ite == ori)
				{
					alivePool.erase(ite);
					break;
				}
			}
			delete ori;
			return false;
		}
		return true;
	}
	bool isFailure()
	{
		for (Piece* i : alivePool)
			if ((i->getpiece_Name() == "帅" && i->getCamp() == Camp) ||
				(i->getpiece_Name() == "将" && i->getCamp() == Camp))return false;
		return true;
	}
};
// 管理者,管理游戏中的所有规则及处理鼠标信息
class Manager_Chess
{
	ChessBoard operateBoard;		// 用于操作的棋盘
	Piece* operatePiece;			// 正在操作的棋子
	std::list<Vec2> OperationalArea;	// 正在操作棋子的可移动区域
	bool ispress;		// 是否按下
	bool isRedTurn;		// 是否是红色一方的回合
	bool isBlocking;	// 是否阻塞,不处理鼠标信息
	bool isConnectWeb;	// 是否联网
	Vec2 beginplace;
	Vec2 endplace;
	SOCKET clientSocket;
	Action Act;			// 正在进行的行动
private:
	// 画一个阴阳鱼
	void DrawTaiJi(MGB::Vec2 pericious, double radius, COLORREF Camp)
	{
		setlinecolor(Camp);
		arc(pericious.x - radius / 2.0, pericious.y - radius, pericious.x + radius / 2.0, pericious.y, PI / 2.0, PI * 3 / 2.0);
		arc(pericious.x - radius / 2.0, pericious.y, pericious.x + radius / 2.0, pericious.y + radius, -PI / 2.0, PI / 2.0);
		if (Camp == RED)arc(pericious.x - radius, pericious.y - radius, pericious.x + radius, pericious.y + radius, -PI / 2.0, PI / 2.0);
		else arc(pericious.x - radius, pericious.y - radius, pericious.x + radius, pericious.y + radius, PI / 2.0, PI * 3 / 2.0);
	}
	// 设置兵的可移动区域
	void SetSoldier(Vec2 place, COLORREF Soldier_Camp)
	{
		COLORREF Camp = operateBoard.GetCamp();
		if (Soldier_Camp == Camp)
		{
			if (operateBoard.isCanThrough({ place.x, place.y - 1 }, Soldier_Camp))
				OperationalArea.push_back({ place.x, place.y - 1 });
			if (place.y < 5)
			{
				if (operateBoard.isCanThrough({ place.x - 1, place.y }, Soldier_Camp))
					OperationalArea.push_back({ place.x - 1, place.y });
				if (operateBoard.isCanThrough({ place.x + 1, place.y }, Soldier_Camp))
					OperationalArea.push_back({ place.x + 1, place.y });
			}
		}
		else
		{
			if (operateBoard.isCanThrough({ place.x, place.y + 1 }, Soldier_Camp))
				OperationalArea.push_back({ place.x, place.y + 1 });
			if (place.y >= 5)
			{
				if (operateBoard.isCanThrough({ place.x - 1, place.y }, Soldier_Camp))
					OperationalArea.push_back({ place.x - 1, place.y });
				if (operateBoard.isCanThrough({ place.x + 1, place.y }, Soldier_Camp))
					OperationalArea.push_back({ place.x + 1, place.y });
			}
		}
	}
	// 设置马的可移动区域
	void SetHorse(Vec2 place, COLORREF Horse_Camp)
	{
		if (operateBoard.isInMap({ place.x, place.y + 1 }) &&
			operateBoard.GetMapPiece({ place.x, place.y + 1 }) == nullptr)
		{
			if (operateBoard.isCanThrough({ place.x - 1, place.y + 2 }, Horse_Camp))
				OperationalArea.push_back({ place.x - 1, place.y + 2 });
			if (operateBoard.isCanThrough({ place.x + 1, place.y + 2 }, Horse_Camp))
				OperationalArea.push_back({ place.x + 1, place.y + 2 });
		}
		if (operateBoard.isInMap({ place.x, place.y - 1 }) &&
			operateBoard.GetMapPiece({ place.x, place.y - 1 }) == nullptr)
		{
			if (operateBoard.isCanThrough({ place.x - 1, place.y - 2 }, Horse_Camp))
				OperationalArea.push_back({ place.x - 1, place.y - 2 });
			if (operateBoard.isCanThrough({ place.x + 1, place.y - 2 }, Horse_Camp))
				OperationalArea.push_back({ place.x + 1, place.y - 2 });
		}
		if (operateBoard.isInMap({ place.x + 1, place.y }) &&
			operateBoard.GetMapPiece({ place.x + 1, place.y }) == nullptr)
		{
			if (operateBoard.isCanThrough({ place.x + 2, place.y + 1 }, Horse_Camp))
				OperationalArea.push_back({ place.x + 2, place.y + 1 });
			if (operateBoard.isCanThrough({ place.x + 2, place.y - 1 }, Horse_Camp))
				OperationalArea.push_back({ place.x + 2, place.y - 1 });
		}
		if (operateBoard.isInMap({ place.x - 1, place.y }) &&
			operateBoard.GetMapPiece({ place.x - 1, place.y }) == nullptr)
		{
			if (operateBoard.isCanThrough({ place.x - 2, place.y + 1 }, Horse_Camp))
				OperationalArea.push_back({ place.x - 2, place.y + 1 });
			if (operateBoard.isCanThrough({ place.x - 2, place.y - 1 }, Horse_Camp))
				OperationalArea.push_back({ place.x - 2, place.y - 1 });
		}
	}
	// 设置炮或车的可移动区域
	void SetCarOrGun(Vec2 place, COLORREF Camp, bool isGun)
	{
		char i = 1;
		while (operateBoard.isInMap({ place.x, place.y + i }) &&
			operateBoard.GetMapPiece({ place.x, place.y + i }) == nullptr)
		{
			OperationalArea.push_back({ place.x, place.y + i });
			i++;
		}
		if (isGun)
		{
			i++;
			while (operateBoard.isInMap({ place.x, place.y + i }) &&
				operateBoard.GetMapPiece({ place.x, place.y + i }) == nullptr)i++;
		}
		if (operateBoard.isCanThrough({ place.x, place.y + i }, Camp))
			OperationalArea.push_back({ place.x, place.y + i });
		i = 1;
		while (operateBoard.isInMap({ place.x, place.y - i }) &&
			operateBoard.GetMapPiece({ place.x, place.y - i }) == nullptr)
		{
			OperationalArea.push_back({ place.x, place.y - i });
			i++;
		}
		if (isGun)
		{
			i++;
			while (operateBoard.isInMap({ place.x, place.y - i }) &&
				operateBoard.GetMapPiece({ place.x, place.y - i }) == nullptr)i++;
		}
		if (operateBoard.isCanThrough({ place.x, place.y - i }, Camp))
			OperationalArea.push_back({ place.x, place.y - i });
		i = 1;
		while (operateBoard.isInMap({ place.x - i, place.y }) &&
			operateBoard.GetMapPiece({ place.x - i, place.y }) == nullptr)
		{
			OperationalArea.push_back({ place.x - i, place.y });
			i++;
		}
		if (isGun)
		{
			i++;
			while (operateBoard.isInMap({ place.x - i, place.y }) &&
				operateBoard.GetMapPiece({ place.x - i, place.y }) == nullptr)i++;
		}
		if (operateBoard.isCanThrough({ place.x - i, place.y }, Camp))
			OperationalArea.push_back({ place.x - i, place.y });
		i = 1;
		while (operateBoard.isInMap({ place.x + i, place.y }) &&
			operateBoard.GetMapPiece({ place.x + i, place.y }) == nullptr)
		{
			OperationalArea.push_back({ place.x + i, place.y });
			i++;
		}
		if (isGun)
		{
			i++;
			while (operateBoard.isInMap({ place.x + i, place.y }) &&
				operateBoard.GetMapPiece({ place.x + i, place.y }) == nullptr)i++;
		}
		if (operateBoard.isCanThrough({ place.x + i, place.y }, Camp))
			OperationalArea.push_back({ place.x + i, place.y });
	}
	// 设置象的可移动区域
	void SetElephant(Vec2 place, COLORREF Ele_Camp)
	{
		COLORREF Camp = operateBoard.GetCamp();
		if (Ele_Camp == Camp)
		{
			if (operateBoard.isInMap({ place.x + 1, place.y + 1 }) &&
				operateBoard.GetMapPiece({ place.x + 1, place.y + 1 }) == nullptr &&
				operateBoard.isCanThrough({ place.x + 2, place.y + 2 }, Ele_Camp))
				OperationalArea.push_back({ place.x + 2, place.y + 2 });
			if (operateBoard.isInMap({ place.x + 1, place.y - 1 }) &&
				operateBoard.GetMapPiece({ place.x + 1, place.y - 1 }) == nullptr &&
				operateBoard.isCanThrough({ place.x + 2, place.y - 2 }, Ele_Camp) &&
				place.y - 2 >= 5)
				OperationalArea.push_back({ place.x + 2, place.y - 2 });
			if (operateBoard.isInMap({ place.x - 1, place.y + 1 }) &&
				operateBoard.GetMapPiece({ place.x - 1, place.y + 1 }) == nullptr &&
				operateBoard.isCanThrough({ place.x - 2, place.y + 2 }, Ele_Camp))
				OperationalArea.push_back({ place.x - 2, place.y + 2 });
			if (operateBoard.isInMap({ place.x - 1, place.y - 1 }) &&
				operateBoard.GetMapPiece({ place.x - 1, place.y - 1 }) == nullptr &&
				operateBoard.isCanThrough({ place.x - 2, place.y - 2 }, Ele_Camp) &&
				place.y - 2 >= 5)
				OperationalArea.push_back({ place.x - 2, place.y - 2 });
		}
		else
		{
			if (operateBoard.isInMap({ place.x + 1, place.y + 1 }) &&
				operateBoard.GetMapPiece({ place.x + 1, place.y + 1 }) == nullptr &&
				operateBoard.isCanThrough({ place.x + 2, place.y + 2 }, Ele_Camp) &&
				place.y + 2 < 5)
				OperationalArea.push_back({ place.x + 2, place.y + 2 });
			if (operateBoard.isInMap({ place.x + 1, place.y - 1 }) &&
				operateBoard.GetMapPiece({ place.x + 1, place.y - 1 }) == nullptr &&
				operateBoard.isCanThrough({ place.x + 2, place.y - 2 }, Ele_Camp))
				OperationalArea.push_back({ place.x + 2, place.y - 2 });
			if (operateBoard.isInMap({ place.x - 1, place.y + 1 }) &&
				operateBoard.GetMapPiece({ place.x - 1, place.y + 1 }) == nullptr &&
				operateBoard.isCanThrough({ place.x - 2, place.y + 2 }, Ele_Camp) &&
				place.y + 2 < 5)
				OperationalArea.push_back({ place.x - 2, place.y + 2 });
			if (operateBoard.isInMap({ place.x - 1, place.y - 1 }) &&
				operateBoard.GetMapPiece({ place.x - 1, place.y - 1 }) == nullptr &&
				operateBoard.isCanThrough({ place.x - 2, place.y - 2 }, Ele_Camp))
				OperationalArea.push_back({ place.x - 2, place.y - 2 });
		}
	}
	// 设置士的可移动区域
	void SetKnight(Vec2 place, COLORREF Kni_Camp)
	{
		COLORREF Camp = operateBoard.GetCamp();
		if (Kni_Camp == Camp)
		{
			if (operateBoard.isCanThrough({ place.x + 1, place.y + 1 }, Kni_Camp) &&
				place.x + 1 <= 5)
				OperationalArea.push_back({ place.x + 1, place.y + 1 });
			if (operateBoard.isCanThrough({ place.x - 1, place.y + 1 }, Kni_Camp) &&
				place.x - 1 >= 3)
				OperationalArea.push_back({ place.x - 1, place.y + 1 });
			if (operateBoard.isCanThrough({ place.x + 1, place.y - 1 }, Kni_Camp) &&
				place.x + 1 <= 5 && place.y - 1 >= 7)
				OperationalArea.push_back({ place.x + 1, place.y - 1 });
			if (operateBoard.isCanThrough({ place.x - 1, place.y - 1 }, Kni_Camp) &&
				place.x - 1 >= 3 && place.y - 1 >= 7)
				OperationalArea.push_back({ place.x - 1, place.y - 1 });
		}
		else
		{
			if (operateBoard.isCanThrough({ place.x + 1, place.y + 1 }, Kni_Camp) &&
				place.x + 1 <= 5 && place.y + 1 <= 2)
				OperationalArea.push_back({ place.x + 1, place.y + 1 });
			if (operateBoard.isCanThrough({ place.x - 1, place.y + 1 }, Kni_Camp) &&
				place.x - 1 >= 3 && place.y + 1 <= 2)
				OperationalArea.push_back({ place.x - 1, place.y + 1 });
			if (operateBoard.isCanThrough({ place.x + 1, place.y - 1 }, Kni_Camp) &&
				place.x + 1 <= 5)
				OperationalArea.push_back({ place.x + 1, place.y - 1 });
			if (operateBoard.isCanThrough({ place.x - 1, place.y - 1 }, Kni_Camp) &&
				place.x - 1 >= 3)
				OperationalArea.push_back({ place.x - 1, place.y - 1 });
		}
	}
	// 设置将帅的可移动区域
	void SetKing(Vec2 place, COLORREF Soldier_Camp)
	{
		COLORREF Camp = operateBoard.GetCamp();
		if (Soldier_Camp == Camp)
		{
			if (operateBoard.isCanThrough({ place.x, place.y - 1 }, Soldier_Camp) &&
				place.y - 1 >= 7)
				OperationalArea.push_back({ place.x, place.y - 1 });
			if (operateBoard.isCanThrough({ place.x - 1, place.y }, Soldier_Camp) &&
				place.x - 1 >= 3)
				OperationalArea.push_back({ place.x - 1, place.y });
			if (operateBoard.isCanThrough({ place.x + 1, place.y }, Soldier_Camp) &&
				place.x + 1 <= 5)
				OperationalArea.push_back({ place.x + 1, place.y });
			if (operateBoard.isCanThrough({ place.x, place.y + 1 }, Soldier_Camp))
				OperationalArea.push_back({ place.x, place.y + 1 });
			char i = 1;
			while (operateBoard.isInMap({ place.x, place.y - i }) &&
				operateBoard.GetMapPiece({ place.x, place.y - i }) == nullptr)i++;
			if (operateBoard.GetMapPiece({ place.x, place.y - i })->getpiece_Name() == "帅" ||
				operateBoard.GetMapPiece({ place.x, place.y - i })->getpiece_Name() == "将")
				OperationalArea.push_back({ place.x, place.y - i });
		}
		else
		{
			if (operateBoard.isCanThrough({ place.x, place.y - 1 }, Soldier_Camp))
				OperationalArea.push_back({ place.x, place.y - 1 });
			if (operateBoard.isCanThrough({ place.x - 1, place.y }, Soldier_Camp) &&
				place.x - 1 >= 3)
				OperationalArea.push_back({ place.x - 1, place.y });
			if (operateBoard.isCanThrough({ place.x + 1, place.y }, Soldier_Camp) &&
				place.x + 1 <= 5)
				OperationalArea.push_back({ place.x + 1, place.y });
			if (operateBoard.isCanThrough({ place.x, place.y + 1 }, Soldier_Camp) &&
				place.y + 1 <= 2)
				OperationalArea.push_back({ place.x, place.y + 1 });
			char i = 1;
			while (operateBoard.isInMap({ place.x, place.y + i }) &&
				operateBoard.GetMapPiece({ place.x, place.y + i }) == nullptr)i++;
			if (operateBoard.isInMap({ place.x, place.y + i }) &&
				(operateBoard.GetMapPiece({ place.x, place.y + i })->getpiece_Name() == "帅" ||
					operateBoard.GetMapPiece({ place.x, place.y + i })->getpiece_Name() == "将"))
				OperationalArea.push_back({ place.x, place.y + i });
		}
	}
	// 设置特殊的动画效果
	void AnimationSpecial(std::string name, double delay, double FPS)
	{
		Act.SetBasicValue(delay, FPS, 0);
		Act.SetSign(name);
		Act.SetAct([&](double progress)
			{
				double Size = (2 - progress) * min(operateBoard.GetSize().x,
					operateBoard.GetSize().y) * 0.1;
				DrawTaiJi(operateBoard.GetPlace(), Size, operatePiece->getCamp());
				settextstyle(Size, Size / 2.0, L"Consolas");
				settextcolor(operatePiece->getCamp());
				setbkmode(TRANSPARENT);
				TCHAR* arr = TransformCharToWideChar(Act.GetSign().c_str());
				outtextxy(operateBoard.GetPlace().x - Size / 2.0,
					operateBoard.GetPlace().y - Size / 2.0, arr);
				delete[]arr;
				return true;
			});
		Act.SetEnd_Act([&]()
			{
				isBlocking = false;
				operatePiece = nullptr;
				return true;
			});
	}
	void ConnectAnimationSpecial(std::string name, double delay, double FPS)
	{
		Act.SetBasicValue(delay, FPS, 0);
		Act.SetSign(name);
		Act.SetAct([&](double progress)
			{
				double Size = (2 - progress) * min(operateBoard.GetSize().x,
					operateBoard.GetSize().y) * 0.1;
				DrawTaiJi(operateBoard.GetPlace(), Size, operatePiece->getCamp());
				settextstyle(Size, Size / 2.0, L"Consolas");
				settextcolor(operatePiece->getCamp());
				setbkmode(TRANSPARENT);
				TCHAR* arr = TransformCharToWideChar(Act.GetSign().c_str());
				outtextxy(operateBoard.GetPlace().x - Size / 2.0,
					operateBoard.GetPlace().y - Size / 2.0, arr);
				delete[]arr;
				return true;
			});
		Act.SetEnd_Act([&]()
			{
				isBlocking = false;
				operatePiece = nullptr;
				isRedTurn = !isRedTurn;
				return true;
			});
	}
	// 判断是否将军
	bool isCheck()
	{
		SetOperationalArea(*operatePiece);
		for (Vec2 i : OperationalArea)
			if (operateBoard.GetMapPiece(i) && (operateBoard.GetMapPiece(i)->getpiece_Name() == "帅" ||
				operateBoard.GetMapPiece(i)->getpiece_Name() == "将"))return true;
		return false;
	}
	// 设置棋子移动的动画效果
	void Animation(Vec2 goal, double delay, double FPS)// FPS 是 1/10.0 之类表示一秒多少帧
	{
		MGB::Vec2 begin = { operatePiece->getPos().x * operateBoard.getGrid().x + operateBoard.getStart().x,
			operatePiece->getPos().y * operateBoard.getGrid().y + operateBoard.getStart().y };
		MGB::Vec2 end = { goal.x * operateBoard.getGrid().x + operateBoard.getStart().x,
			goal.y * operateBoard.getGrid().y + operateBoard.getStart().y };
		Act.SetBasicValue(delay, FPS, 0);
		Act.SetMoveAct_Route(begin, end);
		Act.SetAct([&](double progress)
			{
				operatePiece->DrawPiece_Pick(Act.GetMoveAct_Current(), operateBoard.getGrid());
				return true;
			});
		Act.SetEnd_Act([&]()
			{
				Vec2 Goal = operateBoard.GetMapVec2(Act.GetEnd().x -
					operateBoard.GetPlace().x + operateBoard.GetSize().x / 2.0,
					Act.GetEnd().y - operateBoard.GetPlace().y + operateBoard.GetSize().y / 2.0);
				for (Vec2 i : OperationalArea)
				{
					if (i.x == Goal.x && i.y == Goal.y)
					{
						isRedTurn = !isRedTurn;	// 自己下如果在原地踏步就很麻烦
						operatePiece->setPos(Goal);
						break;
					}
				}
				if (isCheck())
				{
					operateBoard.PutPieceInBoard(operatePiece);
					AnimationSpecial("将", 0.5, 0.05);
				}
				else if (!operateBoard.PutPieceInBoard(operatePiece))
					AnimationSpecial("吃", 0.5, 0.05);
				else
				{
					isBlocking = false;
					operatePiece = nullptr;
				}
				return true;
			});
	}
	void ConnectAnimation(Vec2 goal, double delay, double FPS)
	{
		MGB::Vec2 begin = { operatePiece->getPos().x * operateBoard.getGrid().x + operateBoard.getStart().x,
			operatePiece->getPos().y * operateBoard.getGrid().y + operateBoard.getStart().y };
		MGB::Vec2 end = { goal.x * operateBoard.getGrid().x + operateBoard.getStart().x,
			goal.y * operateBoard.getGrid().y + operateBoard.getStart().y };
		Act.SetBasicValue(delay, FPS, 0);
		Act.SetMoveAct_Route(begin, end);
		Act.SetAct([&](double progress)
			{
				operatePiece->DrawPiece_Pick(Act.GetMoveAct_Current(), operateBoard.getGrid());
				return true;
			});
		Act.SetEnd_Act([&]()
			{
				Vec2 Goal = operateBoard.GetMapVec2(Act.GetEnd().x -
					operateBoard.GetPlace().x + operateBoard.GetSize().x / 2.0,
					Act.GetEnd().y - operateBoard.GetPlace().y + operateBoard.GetSize().y / 2.0);
				operatePiece->setPos(Goal);
				if (isCheck())
				{
					operateBoard.PutPieceInBoard(operatePiece);
					ConnectAnimationSpecial("将", 0.5, 0.05);
				}
				else if (!operateBoard.PutPieceInBoard(operatePiece))
				{
					ConnectAnimationSpecial("吃", 0.5, 0.05);
				}
				else
				{
					isRedTurn = !isRedTurn;	// 由于可以肯定不会原地踏步,所以可以这么写
					isBlocking = false;
					operatePiece = nullptr;
				}
				return true;
			});
	}
public:
	Manager_Chess() :ispress(false), operatePiece(nullptr), operateBoard(), isRedTurn(true),
		isBlocking(false), Act(), isConnectWeb(false)
	{
		this->beginplace = { 0, 0 };
		this->endplace = { 0, 0 };
	}
	Manager_Chess(MGB::Vec2 pos, MGB::Vec2 size, COLORREF Camp) :ispress(false), operatePiece(nullptr),
		operateBoard(pos, size, Camp), isRedTurn(true), isBlocking(false), Act(), isConnectWeb(false)
	{
		this->beginplace = { 0, 0 };
		this->endplace = { 0, 0 };
		operateBoard.InitMap();
	}
	~Manager_Chess()
	{
		if (operatePiece != nullptr)delete operatePiece;
		OperationalArea.clear();
	}
	Piece* GetOperatePiece(double x, double y)
	{
		Vec2 place = operateBoard.GetMapVec2(x - operateBoard.GetPlace().x + operateBoard.GetSize().x / 2.0,
			y - operateBoard.GetPlace().y + operateBoard.GetSize().y / 2.0);
		if (!operateBoard.isInMap(place))return nullptr;
		if (isRedTurn && operateBoard.GetMapPiece(place) != nullptr &&
			operateBoard.GetMapPiece(place)->getCamp() == RED ||
			!isRedTurn && operateBoard.GetMapPiece(place) != nullptr &&
			operateBoard.GetMapPiece(place)->getCamp() == BLACK)
			return operateBoard.GetOperatePiece(x - operateBoard.GetPlace().x + operateBoard.GetSize().x / 2.0,
				y - operateBoard.GetPlace().y + operateBoard.GetSize().y / 2.0);
		return nullptr;
	}
	void SetOperationalArea(Piece num)
	{
		OperationalArea.clear();
		if (num.getpiece_Name() == "兵" || num.getpiece_Name() == "卒")SetSoldier(num.getPos(), num.getCamp());
		else if (num.getpiece_Name() == "马")SetHorse(num.getPos(), num.getCamp());
		else if (num.getpiece_Name() == "車")SetCarOrGun(num.getPos(), num.getCamp(), false);
		else if (num.getpiece_Name() == "相" || num.getpiece_Name() == "象")SetElephant(num.getPos(), num.getCamp());
		else if (num.getpiece_Name() == "士")SetKnight(num.getPos(), num.getCamp());
		else if (num.getpiece_Name() == "炮")SetCarOrGun(num.getPos(), num.getCamp(), true);
		else if (num.getpiece_Name() == "帅" || num.getpiece_Name() == "将")SetKing(num.getPos(), num.getCamp());
	}
	bool DealMouseMsg(ExMessage* msg)
	{
		if (isBlocking)return false;
		if (!ispress && msg->lbutton)
		{
			ispress = true;
			if (operatePiece == nullptr)
			{
				operatePiece = GetOperatePiece(msg->x, msg->y);
				if (operatePiece != nullptr)SetOperationalArea(*operatePiece);
			}
			else
			{
				Vec2 finalPlace = operateBoard.GetMapVec2(msg->x - operateBoard.GetPlace().x +
					operateBoard.GetSize().x / 2.0,
					msg->y - operateBoard.GetPlace().y + operateBoard.GetSize().y / 2.0);
				if (operateBoard.isInMap(finalPlace))
				{
					if (!operateBoard.isCanThrough(finalPlace, operatePiece->getCamp()))
					{
						operateBoard.PutPieceInBoard(operatePiece);
						operatePiece = GetOperatePiece(msg->x, msg->y);
						SetOperationalArea(*operatePiece);
					}
					else
					{
						isBlocking = true;
						bool t_result = false;
						Animation(finalPlace, 0.5, 0.05);
						if (this->isConnectWeb)
						{
							for (Vec2 i : OperationalArea)
							{
								if (i.x == finalPlace.x && i.y == finalPlace.y)
								{
									this->beginplace = operatePiece->getPos();
									this->endplace = finalPlace;
									t_result = true;
									break;
								}
							}
							return t_result;
						}
					}
				}
			}
		}
		else if (ispress && !msg->lbutton)
		{
			ispress = false;
		}
		return false;
	}
	void DealMessage(Vec2 begin, Vec2 end)
	{
		begin.x = 8 - begin.x;
		begin.y = 9 - begin.y;
		end.x = 8 - end.x;
		end.y = 9 - end.y;
		operatePiece = operateBoard.GetOperatePiece(begin);
		isBlocking = true;
		ConnectAnimation(end, 0.5, 0.05);
	}
	void DrawBoard()
	{
		operateBoard.Draw_Board();
		operateBoard.PutPiece();
		if (isBlocking)
		{
			Act.RunEveryFrameAction();
		}
		else if (operatePiece != nullptr)
		{
			operateBoard.TakePiece(*operatePiece);
			operateBoard.DrawOperationalArea(OperationalArea);
		}
	}
	void Reopen()
	{
		if (isBlocking)return;
		operateBoard.ClearMap();
		operateBoard.CampOpposite();
		operateBoard.InitMap();
		isRedTurn = true;
		isBlocking = false;
		Act.SetBasicValue(0, 0, 0);
	}
	void Reset()
	{
		operateBoard.ClearMap();
		operateBoard.InitMap();
		if (operatePiece != nullptr)delete operatePiece;
		operatePiece = nullptr;
		isRedTurn = true;
		isBlocking = false;
		Act.SetBasicValue(0, 0, 0);
	}
	ChessBoard& GetChessBoard()
	{
		return this->operateBoard;
	}
	bool IsRedTurn() { return this->isRedTurn; }
	void SetIsConnect(bool isconnect, SOCKET clientSocket)
	{
		this->isConnectWeb = isconnect;
		this->clientSocket = clientSocket;
	}
	Vec2 GetBeginPlace()
	{
		return this->beginplace;
	}
	Vec2 GetEndPlace()
	{
		return this->endplace;
	}
	bool GetIsBlocking() { return this->isBlocking; }
};

客户端 client:

#define SENDMESSAGEMAXLEN 300
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include<strsafe.h>
#include<WinSock2.h>// 这东西一定要在 Windows.h 头文件上面
#include<Windows.h>
#include<iostream>
#include<conio.h>
#include<time.h>
#include"Chess.h"
#pragma comment(lib, "Ws2_32.lib")
#define WIDTH 640
#define HEIGHT 640

#define ISERRORMESSAGE 0
#define ISSENDMESSAGE 1
#define ISCONNECTNUMCHANGE 2
#define ISENTERROOM 3
#define ISEXITROOM 4
#define ISCONNECTMAXNUM 5
#define ISHALLMESSAGE 6
#define ISCHESSMOVE 7
#define ISUNDO 8
#define ISGIVEUP 9
#define ISGETREADY 10

size_t TransformCharToWideChar(const char* target, wchar_t** output)
{
	size_t len = MultiByteToWideChar(CP_ACP, 0, target, -1, NULL, 0);
	TCHAR* arr = new TCHAR[len];
	MultiByteToWideChar(CP_ACP, 0, target, -1, arr, len);
	*output = arr;
	return len;
}

void WINAPI My_Printf(const char* arr)
{
	wchar_t* tarr;
	size_t szlen = TransformCharToWideChar(arr, &tarr);
	WriteConsole(GetStdHandle(STD_OUTPUT_HANDLE), tarr, szlen - 1, NULL, NULL);
	delete[] tarr;
}

void WINAPI My_Printf(const char* arr, const char* another)
{
	wchar_t* tarr;
	wchar_t* tanother;
	size_t szlen = TransformCharToWideChar(arr, &tarr);
	szlen += TransformCharToWideChar(another, &tanother);
	wchar_t* OutMsg = new wchar_t[szlen];
	StringCchPrintf(OutMsg, szlen, tarr, tanother);
	WriteConsole(GetStdHandle(STD_OUTPUT_HANDLE), OutMsg, szlen - 4, NULL, NULL);
	delete[] tarr;
	delete[] tanother;
	delete[] OutMsg;
}

void WINAPI My_Printf(const char* arr, int num)
{
	wchar_t* tarr;
	size_t szlen = TransformCharToWideChar(arr, &tarr);
	wchar_t* OutMsg = new wchar_t[szlen + 12];
	StringCchPrintf(OutMsg, szlen + 12, tarr, num);
	StringCchLength(OutMsg, szlen + 12, &szlen);
	WriteConsole(GetStdHandle(STD_OUTPUT_HANDLE), OutMsg, szlen, NULL, NULL);
	delete[] tarr;
	delete[] OutMsg;
}

bool WINAPI My_Strcmp(const char* target, const char* source, size_t len)
{
	for (size_t i = 0; i < len;i++)
		if (target[i] != source[i])return false;
	return true;
}

void My_Strcpy(char* target, const char* source, size_t len)
{
	for (int i = 0; i < len; i++)
	{
		target[i] = source[i];
	}
}

struct RecvMessageParam
{
	SOCKET clientSocket;
	bool isExit;
	unsigned int* connectnum;
	unsigned int* maxconnectnum;
	bool** hall;
	int* roomnumber;
	HWND hwnd;
	bool* isMove;
	char* Place;
	bool* isoppositeReady;
	bool* isselfReady;
	wchar_t* RecvSM;
	size_t* RecvSMLen;
};

struct DealErrorMessageParam
{
	const char* message;
	size_t len;
	HWND hwnd;
	bool* isoppositeReady;
	bool* isselfReady;
};

void DealErrorMessage(DealErrorMessageParam* param)
{
	if (param->len == 12 && My_Strcmp(param->message, "no this room", 12))
	{
		MessageBox(param->hwnd, L"没有这个房间", L"错误", MB_OK);
	}
	else if (param->len == 16 && My_Strcmp(param->message, "the room is full", 16))
	{
		MessageBox(param->hwnd, L"房间满了", L"错误", MB_OK);
	}
	else if (param->len == 15 && My_Strcmp(param->message, "you are in room", 15))
	{
		MessageBox(param->hwnd, L"你在房间里", L"错误", MB_OK);
	}
	else if (param->len == 15 && My_Strcmp(param->message, "you are in hall", 15))
	{
		MessageBox(param->hwnd, L"你在大厅里", L"错误", MB_OK);
	}
	else if (param->len == 21 && My_Strcmp(param->message, "no people in opposite", 21))
	{
		*param->isoppositeReady = false;
		*param->isselfReady = false;
		MessageBox(param->hwnd, L"对面没人", L"错误", MB_OK);
	}
}

DWORD WINAPI RecvMessage(LPVOID lParam)
{
	RecvMessageParam* param = (RecvMessageParam*)lParam;
	while (!param->isExit)
	{
		char code;
		size_t messagelen = 0;
		char recvBuf[SENDMESSAGEMAXLEN];
		int retVal = 0;
		while (!param->isExit && recv(param->clientSocket, &code, sizeof(char), 0) == SOCKET_ERROR)
		{
			retVal = WSAGetLastError();
			if (retVal == WSAEWOULDBLOCK)
			{
				Sleep(1000);
				continue;
			}
		}
		while (!param->isExit && recv(param->clientSocket, (char*)&messagelen, sizeof(size_t), 0) == SOCKET_ERROR)
		{
			retVal = WSAGetLastError();
			if (retVal == WSAEWOULDBLOCK)
			{
				Sleep(100);
				continue;
			}
		}
		messagelen = ntohll(messagelen);
		while (!param->isExit && recv(param->clientSocket, recvBuf, messagelen, 0) == SOCKET_ERROR)
		{
			retVal = WSAGetLastError();
			if (retVal == WSAEWOULDBLOCK)
			{
				Sleep(100);
				continue;
			}
		}

		if (param->isExit)break;

		switch (code)
		{
		case ISERRORMESSAGE:
		{
			DealErrorMessageParam t_param;
			t_param.message = recvBuf;
			t_param.len = messagelen;
			t_param.hwnd = param->hwnd;
			t_param.isoppositeReady = param->isoppositeReady;
			t_param.isselfReady = param->isselfReady;
			DealErrorMessage(&t_param);
		}
		break;
		case ISSENDMESSAGE:
		{
			*param->RecvSMLen = messagelen >> 1;
			for (int i = 0; i < messagelen >> 1; i++)*(param->RecvSM + i) = *((wchar_t*)recvBuf + i);
		}
		break;
		case ISCONNECTNUMCHANGE:
			*(param->connectnum) = ntohl(*(unsigned int*)recvBuf);
			break;
		case ISCONNECTMAXNUM:
			*(param->maxconnectnum) = ntohl(*(unsigned int*)recvBuf);
			break;
		case ISHALLMESSAGE:
		{
			while (*(param->hall) == nullptr)Sleep(100);
			for (int i = 0; i < messagelen; i++)*(*(param->hall) + i) = recvBuf[i];
		}
		break;
		case ISENTERROOM:
		{
			unsigned int index = ntohl(*(unsigned int*)recvBuf);
			*(*(param->hall) + index) = true;
		}
		break;
		case ISEXITROOM:
		{
			unsigned int index = ntohl(*(unsigned int*)recvBuf);
			*(*(param->hall) + index) = false;
		}
		break;
		case ISCHESSMOVE:
		{
			*param->isMove = true;
			for (int i = 0; i < messagelen; i++)
				*(param->Place + i) = recvBuf[i];
		}
		break;
		case ISGETREADY:
		{
			*param->isoppositeReady = true;
		}
		break;
		case ISGIVEUP:
		{
			*param->isoppositeReady = false;
			*param->isselfReady = false;
		}
		break;
		default:
			break;
		}
	}
	return 0;
}

void DrawTaiJi(double width, double height, COLORREF Camp1, COLORREF Camp2)
{
	MGB::Vec2 pericious = { width * 0.5, height * 0.5 };
	double radius = min(width, height) * (1 / 3.0);
	setlinecolor(RGB(185, 122, 87));
	circle(pericious.x, pericious.y, radius);
	arc(pericious.x - radius / 2.0, pericious.y - radius, pericious.x + radius / 2.0, pericious.y, PI / 2.0, PI * 3 / 2.0);
	arc(pericious.x - radius / 2.0, pericious.y, pericious.x + radius / 2.0, pericious.y + radius, -PI / 2.0, PI / 2.0);
	setfillcolor(Camp1);
	floodfill(pericious.x, pericious.y - radius * (0.5), RGB(185, 122, 87));
	setfillcolor(Camp2);
	floodfill(pericious.x, pericious.y + radius * (0.5), RGB(185, 122, 87));

	circle(pericious.x, pericious.y - radius * (0.5), radius * (0.25));
	circle(pericious.x, pericious.y + radius * (0.5), radius * (0.25));
	setfillcolor(Camp1);
	floodfill(pericious.x, pericious.y + radius * (0.5), RGB(185, 122, 87));
	setfillcolor(Camp2);
	floodfill(pericious.x, pericious.y - radius * (0.5), RGB(185, 122, 87));
}

struct RoomSceneParam
{
	SOCKET clientSocket;
	COLORREF Camp;
	bool* isMove;
	char* Place;
	bool* opposite;
	bool* isoppositeReady;
	bool* isselfReady;
	wchar_t* RecvSM;
	size_t* RecvSMLen;
	HWND hwnd;
};

void OutTextMessage(double xx, double yy, const wchar_t* message, size_t len)
{
	if (len == 0)return;
	int rows = 0;
	settextcolor(BLACK);
	settextstyle(HEIGHT * (1 / 36.0), WIDTH * (1 / 80.0), _T("Consolas"));
	while (rows * 20 < len)
	{
		wchar_t t_message[21];
		int i = 0;
		for (i = 0; i < 20 && i + rows * 20 < len; i++)t_message[i] = message[i + rows * 20];
		t_message[i] = L'\0';
		outtextxy(xx, yy + rows * (WIDTH * (1 / 80.0) + 4), t_message);
		rows++;
	}
}

bool GiveUpFunc(SOCKET clientSocket, bool* isoppositeReady, bool* isselfReady)
{
	*isoppositeReady = false;
	*isselfReady = false;
	char code = ISGIVEUP;
	while (true)
	{
		int retVal = send(clientSocket, &code, 1, 0);
		if (SOCKET_ERROR == retVal)
		{
			int err = WSAGetLastError();
			if (err == WSAEWOULDBLOCK)
			{
				Sleep(5);
				continue;
			}
			else
			{
				printf("send failed\n");
				closesocket(clientSocket);
				WSACleanup();
				return false;
			}
		}
		break;
	}
	return true;
}

bool RoomScene(RoomSceneParam* param)
{
	// 认输,对话,悔棋,准备,移动棋子,在线否

	Manager_Chess manager_Chess({ WIDTH * 0.5, HEIGHT * 0.5 }, { WIDTH * 0.5, HEIGHT * 0.5 }, param->Camp);
	manager_Chess.SetIsConnect(true, param->clientSocket);
	bool isexit = false;
	MGB::List_Button manager = Create_List_Button();
	wchar_t myselfmessage[SENDMESSAGEMAXLEN >> 1];
	size_t MMLen = 0;
	addChild_List_Button(manager, CreateList_Node_Button(
		CreateButton({ WIDTH * (7 / 8.0), HEIGHT * (1 / 8.0) }, { 0.5, 0.5 },
			{ WIDTH * (1 / 6.0), HEIGHT * (1 / 6.0) }, "返回",
			[&]()
			{
				char code = ISEXITROOM;
				while (true)
				{
					int retVal = send(param->clientSocket, &code, 1, 0);
					if (SOCKET_ERROR == retVal)
					{
						int err = WSAGetLastError();
						if (err == WSAEWOULDBLOCK)
						{
							Sleep(5);
							continue;
						}
						else
						{
							printf("send failed\n");
							closesocket(param->clientSocket);
							WSACleanup();
							return false;
						}
					}
					break;
				}
				isexit = true;
				*param->isselfReady = false;
				*param->isoppositeReady = false;
				return true;
			})));

	addChild_List_Button(manager, CreateList_Node_Button(
		CreateButton({ WIDTH * (7 / 8.0), HEIGHT * (0.25 + 0.1) }, { 0.5, 0.5 },
			{ WIDTH * (1 / 8.0), HEIGHT * (1 / 11.0) }, "准备",
			[&]()
			{
				*param->isselfReady = true;
				char code = ISGETREADY;
				while (true)
				{
					int retVal = send(param->clientSocket, &code, 1, 0);
					if (SOCKET_ERROR == retVal)
					{
						int err = WSAGetLastError();
						if (err == WSAEWOULDBLOCK)
						{
							Sleep(5);
							continue;
						}
						else
						{
							printf("send failed\n");
							closesocket(param->clientSocket);
							WSACleanup();
							return false;
						}
					}
					break;
				}
				return true;
			})));
	addChild_List_Button(manager, CreateList_Node_Button(
		CreateButton({ WIDTH * (7 / 8.0), HEIGHT * (0.25 + 0.2) }, { 0.5, 0.5 },
			{ WIDTH * (1 / 8.0), HEIGHT * (1 / 11.0) }, "悔棋",
			[&]()
			{
				MessageBox(param->hwnd, L"落子无悔大丈夫", L"哲理", MB_OK);
				return true;
			})));
	addChild_List_Button(manager, CreateList_Node_Button(
		CreateButton({ WIDTH * (7 / 8.0), HEIGHT * (0.25 + 0.3) }, { 0.5, 0.5 },
			{ WIDTH * (1 / 8.0), HEIGHT * (1 / 11.0) }, "认输",
			[&]()
			{
				GiveUpFunc(param->clientSocket, param->isoppositeReady, param->isselfReady);
				return true;
			})));
	addChild_List_Button(manager, CreateList_Node_Button(
		CreateButton({ WIDTH * (7 / 8.0), HEIGHT * (0.25 + 0.4) }, { 0.5, 0.5 },
			{ WIDTH * (1 / 8.0), HEIGHT * (1 / 11.0) }, "对话",
			[&]()
			{
				InputBox(myselfmessage, SENDMESSAGEMAXLEN >> 1, 0, L"请输入要发送的信息");
				char code = ISGETREADY;
				StringCchLength(myselfmessage, SENDMESSAGEMAXLEN >> 1, &MMLen);
				char sendBuf[sizeof(char) + sizeof(size_t) + SENDMESSAGEMAXLEN];
				sendBuf[0] = ISSENDMESSAGE;
				size_t temp = htonll(MMLen * 2);
				My_Strcpy(&sendBuf[sizeof(char)], (char*)&temp, sizeof(size_t));
				My_Strcpy(&sendBuf[sizeof(char) + sizeof(size_t)], (char*)myselfmessage, MMLen * 2);
				while (true)
				{
					int retVal = send(param->clientSocket, sendBuf,
						sizeof(char) + sizeof(size_t) + MMLen * 2, 0);
					if (SOCKET_ERROR == retVal)
					{
						int err = WSAGetLastError();
						if (err == WSAEWOULDBLOCK)
						{
							Sleep(5);
							continue;
						}
						else
						{
							printf("send failed\n");
							closesocket(param->clientSocket);
							WSACleanup();
							return false;
						}
					}
					break;
				}
				return true;
			})));
	ExMessage msg;
	bool haveStarted = false;
	bool haveJudged = false;
	while (true)
	{
		while (peekmessage(&msg, EM_MOUSE));
		cleardevice();
		if (*param->isoppositeReady && *param->isselfReady)
		{
			if (!haveStarted)haveStarted = true;
			if (!((manager_Chess.IsRedTurn()) ^ (manager_Chess.GetChessBoard().GetCamp() == RED)))
			{
				if (!haveJudged)
				{
					haveJudged = true;
					if (manager_Chess.GetChessBoard().isFailure())
					{
						GiveUpFunc(param->clientSocket, param->isoppositeReady, param->isselfReady);
						continue;
					}
				}
				if (manager_Chess.DealMouseMsg(&msg))
				{
					char sendBuf[sizeof(char) * 5 + sizeof(size_t)];
					sendBuf[0] = ISCHESSMOVE;
					size_t messagelen = htonll(4);
					My_Strcpy((char*)&sendBuf[sizeof(char)], (char*)&messagelen, sizeof(size_t));
					sendBuf[sizeof(char) + sizeof(size_t) + 0] = manager_Chess.GetBeginPlace().x;
					sendBuf[sizeof(char) + sizeof(size_t) + 1] = manager_Chess.GetBeginPlace().y;
					sendBuf[sizeof(char) + sizeof(size_t) + 2] = manager_Chess.GetEndPlace().x;
					sendBuf[sizeof(char) + sizeof(size_t) + 3] = manager_Chess.GetEndPlace().y;
					while (true)
					{
						int retVal = send(param->clientSocket, sendBuf, sizeof(char) * 5 + sizeof(size_t), 0);
						if (SOCKET_ERROR == retVal)
						{
							int err = WSAGetLastError();
							if (err == WSAEWOULDBLOCK)
							{
								Sleep(5);
								continue;
							}
							else
							{
								printf("send failed\n");
								closesocket(param->clientSocket);
								WSACleanup();
								return false;
							}
						}
						break;
					}
				}
			}
			else if (*(param->isMove))
			{
				haveJudged = false;
				manager_Chess.DealMessage({ *(param->Place + 0), *(param->Place + 1) }, { *(param->Place + 2), *(param->Place + 3) });
				*(param->isMove) = false;
			}
			manager_Chess.DrawBoard();
		}
		else
		{
			manager_Chess.GetChessBoard().Draw_Board();
			if (*param->isselfReady)
			{
				settextcolor(manager_Chess.GetChessBoard().GetCamp());
				settextstyle(HEIGHT * (0.125), WIDTH * (1 / 24.0), _T("Consolas"));
				outtextxy(WIDTH * (0.25 + 1 / 8.0), HEIGHT * (0.5 + 1 / 16.0), L"已准备");
			}
			if (*param->isoppositeReady)
			{
				settextcolor((manager_Chess.GetChessBoard().GetCamp() == RED ? BLACK : RED));
				settextstyle(HEIGHT * (0.125), WIDTH * (1 / 24.0), _T("Consolas"));
				outtextxy(WIDTH * (0.25 + 1 / 8.0), HEIGHT * (0.25 + 1 / 16.0), L"已准备");
			}
			if (haveStarted)
			{
				manager_Chess.Reset();
				haveStarted = false;
			}
		}

		setfillcolor(manager_Chess.GetChessBoard().GetCamp());
		fillrectangle(WIDTH * (19 / 24.0), HEIGHT * (19 / 24.0), WIDTH * (23 / 24.0), HEIGHT * (23 / 24.0));
		if (*param->opposite)setfillcolor((manager_Chess.GetChessBoard().GetCamp() == RED ? BLACK : RED));
		else
		{
			*param->isoppositeReady = false;
			*param->isselfReady = false;
			setfillcolor(WHITE);
		}
		fillrectangle(WIDTH * (1 / 24.0), HEIGHT * (1 / 24.0), WIDTH * (5 / 24.0), HEIGHT * (5 / 24.0));
		setlinecolor(YELLOW);
		if (!((manager_Chess.IsRedTurn()) ^ (manager_Chess.GetChessBoard().GetCamp() == RED)))
			rectangle(WIDTH * (19 / 24.0), HEIGHT * (19 / 24.0), WIDTH * (23 / 24.0), HEIGHT * (23 / 24.0));
		else rectangle(WIDTH * (1 / 24.0), HEIGHT * (1 / 24.0), WIDTH * (5 / 24.0), HEIGHT * (5 / 24.0));
		// 一行 20 字
		setlinecolor(BLACK);

		rectangle(WIDTH * (0.25), HEIGHT * (1 / 24.0), WIDTH * (0.75), HEIGHT * (5 / 24.0));
		rectangle(WIDTH * (0.25), HEIGHT * (19 / 24.0), WIDTH * (0.75), HEIGHT * (23 / 24.0));
		OutTextMessage(WIDTH * (0.25), HEIGHT * (19 / 24.0), myselfmessage, MMLen);
		OutTextMessage(WIDTH * (0.25), HEIGHT * (1 / 24.0), param->RecvSM, *param->RecvSMLen);

		Trigger_Button(&msg, manager);
		FlushBatchDraw();
		if (isexit)break;
	}
	Clear_List_Button(manager);

	return true;
}

bool HallScene(HWND hwnd, unsigned long ipaddressnum, unsigned short portnum)
{
	WSAData wsa;
	WSAStartup(MAKEWORD(2, 2), &wsa);

	SOCKET clientSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

	sockaddr_in serveAddress;
	serveAddress.sin_addr.S_un.S_addr = ipaddressnum;	// ip 地址,在自己主机上玩就改成 127.0.0.1
	serveAddress.sin_port = portnum;	// 端口号
	serveAddress.sin_family = AF_INET;

	if (SOCKET_ERROR == connect(clientSocket, (sockaddr*)&serveAddress, sizeof(serveAddress)))
	{
		MessageBox(hwnd, L"连接到服务器失败", L"错误", MB_OK);
		return false;
	}

	int iMode = 1;
	int retVal = ioctlsocket(clientSocket, FIONBIO, (u_long*)&iMode);
	if (retVal == SOCKET_ERROR)
	{
		printf("ioctlsocket failed!");
		WSACleanup();
		return false;
	}
	My_Printf("clinet is running...\n");
	while (true)
	{
		retVal = connect(clientSocket, (sockaddr*)&serveAddress, sizeof(serveAddress));
		if (SOCKET_ERROR == retVal)
		{
			int err = WSAGetLastError();
			if (err == WSAEWOULDBLOCK || err == WSAEINVAL)// windows 异步系统将要阻塞
			{
				Sleep(1);
				printf("check connect\n");
				continue;
			}
			else if (err == WSAEISCONN)break;
			else
			{
				printf("connection failed\n");
				closesocket(clientSocket);
				WSACleanup();
				return false;
			}
		}
	}

	unsigned int connectnum = 0;
	unsigned int maxconnectnum = 0;
	int roomnumber = 0;
	bool isMove = false;
	bool* hall = nullptr;
	char* Place = new char[4];
	bool isoppositeReady = false;
	bool isselfReady = false;
	wchar_t RecvSM[SENDMESSAGEMAXLEN >> 1];
	size_t RecvSMLen = 0;


	RecvMessageParam recvmessageparam;
	recvmessageparam.clientSocket = clientSocket;
	recvmessageparam.connectnum = &connectnum;
	recvmessageparam.isExit = false;
	recvmessageparam.maxconnectnum = &maxconnectnum;
	recvmessageparam.hall = &hall;
	recvmessageparam.roomnumber = &roomnumber;
	recvmessageparam.isMove = &isMove;
	recvmessageparam.Place = Place;
	recvmessageparam.isoppositeReady = &isoppositeReady;
	recvmessageparam.isselfReady = &isselfReady;
	recvmessageparam.hwnd = hwnd;
	recvmessageparam.RecvSM = (wchar_t*)RecvSM;
	recvmessageparam.RecvSMLen = &RecvSMLen;

	HANDLE recvMes;
	recvMes = CreateThread(NULL, 0, RecvMessage, &recvmessageparam, 0, NULL);
	while (!maxconnectnum)Sleep(100);	// 这里会像是程序卡住,姑妄任之
	if (maxconnectnum > 1000 || maxconnectnum < 0)
	{
		MessageBox(hwnd, L"错误", L"连接异常", MB_OK);
		shutdown(clientSocket, 0);
		recvmessageparam.isExit = true;
		delete[] Place;
		WaitForSingleObject(recvMes, INFINITE);
		CloseHandle(recvMes);
		closesocket(clientSocket);
		WSACleanup();
		return false;
	}
	hall = new bool[maxconnectnum];

	COLORREF Camp1, Camp2;
	Camp1 = Camp2 = WHITE;
	bool isexit = false;
	MGB::List_Button manager = Create_List_Button();
	// 尺寸为 1/9
	addChild_List_Button(manager, CreateList_Node_Button(
		CreateButton({ WIDTH * (11 / 12.0), HEIGHT * (1 / 12.0) }, { 0.5, 0.5 },
			{ WIDTH * (1 / 9.0), HEIGHT * (1 / 9.0) }, "返回",
			[&]()
			{
				isexit = true;
				return true;
			})
	));
	addChild_List_Button(manager, CreateList_Node_Button(
		CreateButton({ WIDTH * (0.5), HEIGHT * (11 / 12.0) }, { 0.5, 0.5 },
			{ WIDTH * (1 / 9.0), HEIGHT * (1 / 9.0) }, "进入",
			[&]()
			{
				if (*(hall + roomnumber * 2) && *(hall + roomnumber * 2 + 1))
				{
					MessageBox(hwnd, L"错误", L"房间人满了", MB_OK);
					return false;
				}
				RoomSceneParam roomsceneparam;
				roomsceneparam.clientSocket = clientSocket;
				roomsceneparam.isMove = &isMove;
				roomsceneparam.opposite = (hall + roomnumber * 2 + !(*(hall + roomnumber * 2)));
				roomsceneparam.Place = Place;
				roomsceneparam.Camp = ((*(hall + roomnumber * 2)) ? BLACK : RED);
				roomsceneparam.isoppositeReady = &isoppositeReady;
				roomsceneparam.isselfReady = &isselfReady;
				roomsceneparam.RecvSM = (wchar_t*)RecvSM;
				roomsceneparam.RecvSMLen = &RecvSMLen;
				roomsceneparam.hwnd = hwnd;
				RecvSMLen = 0;

				char sendBuf[sizeof(unsigned int) + sizeof(char) + sizeof(size_t)];
				unsigned int t_roomnumber = htonl(roomnumber);
				sendBuf[0] = ISENTERROOM;
				size_t messagelen = sizeof(unsigned int);
				messagelen = htonll(messagelen);
				My_Strcpy(sendBuf + sizeof(char), (char*)&messagelen, sizeof(size_t));
				My_Strcpy(sendBuf + sizeof(char) + sizeof(size_t), (char*)&t_roomnumber, sizeof(unsigned int));
				while (true)
				{
					retVal = send(clientSocket, sendBuf, sizeof(unsigned int) + sizeof(char) + sizeof(size_t), 0);
					if (SOCKET_ERROR == retVal)
					{
						int err = WSAGetLastError();
						if (err == WSAEWOULDBLOCK)
						{
							Sleep(5);
							continue;
						}
						else
						{
							printf("send failed\n");
							closesocket(clientSocket);
							WSACleanup();
							return false;
						}
					}
					break;
				}

				RoomScene(&roomsceneparam);

				return true;
			})
	));

	addChild_List_Button(manager, CreateList_Node_Button(
		CreateButton({ WIDTH * (1 / 12.0), HEIGHT * (0.5) }, { 0.5, 0.5 },
			{ WIDTH * (1 / 9.0), HEIGHT * (1 / 9.0) }, "左",
			[&]()
			{
				roomnumber = (roomnumber + (maxconnectnum >> 1) - 1) % (maxconnectnum >> 1);
				return true;
			})
	));
	addChild_List_Button(manager, CreateList_Node_Button(
		CreateButton({ WIDTH * (11 / 12.0), HEIGHT * (0.5) }, { 0.5, 0.5 },
			{ WIDTH * (1 / 9.0), HEIGHT * (1 / 9.0) }, "右",
			[&]()
			{
				roomnumber = (roomnumber + 1) % (maxconnectnum >> 1);
				return true;
			})
	));
	ExMessage msg;
	while (true)
	{
		while (peekmessage(&msg, EM_MOUSE));
		cleardevice();
		if (*(hall + 2 * roomnumber))Camp1 = RED;
		else Camp1 = WHITE;
		if (*(hall + 2 * roomnumber + 1))Camp2 = BLACK;
		else Camp2 = WHITE;
		Trigger_Button(&msg, manager);
		DrawTaiJi(WIDTH, HEIGHT, Camp1, Camp2);
		wchar_t* tarr;
		size_t szlen = TransformCharToWideChar("在线人数:%d/%d", &tarr);
		wchar_t* OutMsg = new wchar_t[szlen + 24];
		StringCchPrintf(OutMsg, szlen + 24, tarr, connectnum, maxconnectnum);
		StringCchLength(OutMsg, szlen + 24, &szlen);
		settextstyle(HEIGHT * (1 / 6), WIDTH * (0.125) * (1 / (double)szlen), _T("Consolas"));
		outtextxy(0, 0, OutMsg);
		delete[] tarr;
		delete[] OutMsg;
		FlushBatchDraw();
		if (isexit)break;
	}
	delete[] hall;
	delete[] Place;
	Clear_List_Button(manager);

	shutdown(clientSocket, 0);
	recvmessageparam.isExit = true;
	WaitForSingleObject(recvMes, INFINITE);
	CloseHandle(recvMes);

	closesocket(clientSocket);
	WSACleanup();
	return true;
}

bool SinglePlayerScene()
{
	MGB::List_Button manager = Create_List_Button();
	bool isexit = false;
	Manager_Chess manager_Chess({ WIDTH * 0.375, HEIGHT * 0.375 }, { WIDTH * 0.75, HEIGHT * 0.75 }, RED);
	addChild_List_Button(manager, CreateList_Node_Button(
		CreateButton({ WIDTH * 0.875, HEIGHT * 0.125 }, { 0.5, 0.5 }, { WIDTH / 5.0, HEIGHT * 0.1875 }, "悔棋",
			[&]()
			{
				manager_Chess.GetChessBoard().
					setPos({ manager_Chess.GetChessBoard().GetPlace().x,
						manager_Chess.GetChessBoard().GetPlace().y + 100 });
				return true;
			})
	));
	addChild_List_Button(manager, CreateList_Node_Button(
		CreateButton({ WIDTH * 0.875, HEIGHT * 0.375 }, { 0.5, 0.5 }, { WIDTH / 5.0, HEIGHT * 0.1875 }, "重开",
			[&]()
			{
				manager_Chess.Reopen();
				return true;
			})
	));
	addChild_List_Button(manager, CreateList_Node_Button(
		CreateButton({ WIDTH * 0.875, HEIGHT * 0.625 }, { 0.5, 0.5 }, { WIDTH / 5.0, HEIGHT * 0.1875 }, "结束游戏",
			[&]()
			{
				isexit = true;
				return true;
			})
	));
	ExMessage msg;
	while (true)
	{
		while (peekmessage(&msg, EM_MOUSE));
		cleardevice();
		manager_Chess.DealMouseMsg(&msg);
		manager_Chess.DrawBoard();
		Trigger_Button(&msg, manager);
		FlushBatchDraw();
		if (isexit)break;
	}
	Clear_List_Button(manager);

	return true;
}

unsigned short TransferWideCharToUSHORT(wchar_t* arr, unsigned int len)
{
	unsigned short result = 0;
	int i = 0;
	while (i<len)
	{
		result *= 10;
		switch (arr[i++])
		{
		case L'0':result += 0; break;
		case L'1':result += 1; break;
		case L'2':result += 2; break;
		case L'3':result += 3; break;
		case L'4':result += 4; break;
		case L'5':result += 5; break;
		case L'6':result += 6; break;
		case L'7':result += 7; break;
		case L'8':result += 8; break;
		case L'9':result += 9; break;
		default:
			break;
		}
	}
	return result;
}

unsigned short TransferCharToUSHORT(const char* arr, unsigned int len)
{
	unsigned short result = 0;
	int i = 0;
	while (i < len)
	{
		result *= 10;
		result += arr[i] - '0';
		i++;
	}
	return result;
}

int main()
{
	HWND hwnd = initgraph(WIDTH, HEIGHT);
	BeginBatchDraw();
	setbkcolor(RGB(200, 191, 231));
	bool isexit = false;

	MGB::List_Button manager = Create_List_Button();
	addChild_List_Button(manager, CreateList_Node_Button(
		CreateButton({ WIDTH * (0.5), HEIGHT * (0.25) }, { 0.5, 0.5 }, { WIDTH * (0.2), HEIGHT * (0.2) }, "单机",
			[&]()
			{
				SinglePlayerScene();
				return true;
			})));
	addChild_List_Button(manager, CreateList_Node_Button(
		CreateButton({ WIDTH * (0.5), HEIGHT * (0.5) }, { 0.5, 0.5 }, { WIDTH * (0.2), HEIGHT * (0.2) }, "联机",
			[&]()
			{
				wchar_t IpAddress[21];
				InputBox(IpAddress, 21, 0, L"请输入 ipv4:port", L"127.0.0.1:6000");
				// wchar_t PortNum[6];
				// InputBox(PortNum, 6, 0, L"请输入端口号", L"6000");
				size_t t_len = WideCharToMultiByte(CP_UTF8, 0, IpAddress, -1, NULL, 0, NULL, FALSE);
				char* t_IAN = new char[t_len];
				WideCharToMultiByte(CP_UTF8, 0, IpAddress, -1, t_IAN, t_len, NULL, FALSE);
				char* t_child = strstr(t_IAN, ":");
				*t_child = '\0';
				unsigned long ipaddressnum = inet_addr(t_IAN);
				// unsigned long ipaddressnum = inet_addr(t_IAN);
				// StringCchLength(PortNum, 6, &t_len);
				// unsigned short t_num = TransferWideCharToUSHORT(PortNum, t_len);
				// unsigned short portnum = htons(t_num);
				HallScene(hwnd, ipaddressnum, htons(TransferCharToUSHORT(t_child + 1, strlen(t_child + 1))));
				delete[] t_IAN;
				return true;
			})));
	addChild_List_Button(manager, CreateList_Node_Button(
		CreateButton({ WIDTH * (0.5), HEIGHT * (0.75) }, { 0.5, 0.5 }, { WIDTH * (0.2), HEIGHT * (0.2) }, "退出",
			[&]()
			{
				isexit = true;
				return true;
			})));
	ExMessage msg;
	while (true)
	{
		while (peekmessage(&msg, EM_MOUSE));
		cleardevice();
		Trigger_Button(&msg, manager);
		FlushBatchDraw();
		if (isexit)break;
	}
	Clear_List_Button(manager);
	EndBatchDraw();
	closegraph();
	return 0;
}

服务器端在服务器上运行,客户端的 ip 地址连接到服务器端的 ip 地址就行,客户端的项目要包含 My_Gui_Button.h 和 Chess.h 两个头文件。

由于我租了一个服务器,需要的可以输入 ipv4 地址为 119.29.157.70,端口号为 6000 来连上。

评论 (3) -

  • 网络传输那里好像没解决粘包拆包问题,想问一下楼主在访问量密集的时候会不会出现丢失信息的情况,因为之前我没处理粘包拆包的时候经常这样
    • 没做过压力测试,不过六个客户端连接上的时候就会出现各种各样的问题了,比如三个人进入同一个房间,写这篇的时候技术还不成熟,现在还在学习,想学一下其他的模型,这个实现太繁琐了。谢谢指出问题。

添加评论