用C++和Socket开发的本地网络象棋游戏(基于Win32界面,采用CodeBlocks与GCC编译器实现)
目录
成果
运行效果图
过程
1. 首先的问题是下棋的两端应该是什么样的?
2. 接下来的问题是怎么表示,怎么存储?
3. 然后应该怎么通信呢?
代码
main.cpp
chinese_chess.h
Server.h
Client.h
END
成果
运行效果图
左边是在虚拟机里运行的,右边是在Host机上运行的。
最新更改后的界面:
过程
记不起自己为什么要写这个象棋游戏的,大概是因为刚学了点儿Socket ,所以想用用,于是就写个局域网对战的象棋吧。。。
1. 首先的问题是下棋的两端应该是什么样的?
我希望下棋的两个人使用相同的程序。所以就不能像FTP一样,一个客户端,一个服务器端,而只能每个程序都是一样的,既是客户端(Client),又是服务器端(Server)。在通信时,己方的Client 向对方的Server 发送信息,对方的Client 向己方的Server 发送信息。两端都存储棋盘信息,通过通信保持棋盘信息的一致。
然后呢,应该是一端点击界面移子之后,应该能通知对方进行相同的移动。
综合以上两点,运行过程应该是这样的:
当在界面上点击棋子时,先判断当前是否轮到自己落子,如果是,则进行移动,更新界面,并通过Client 向对方Server 发送移动信息。对方Server 收到后,进行同样的移动,更新界面。
这里要求Server能随时接到对方发来的消息,所以Server的监听应该是一个额外的线程。
2. 接下来的问题是怎么表示,怎么存储?
棋盘,应该用二维数组存储比较好,数组坐标(以下所说的"数组坐标 "是指该二维数组中的一个(x,y)数对)对应棋盘坐标。那么数组里存储什么呢,一共有車、马、象、士、将、砲、卒七种棋子,那么设置一个棋子类作为基类,然后设置七个类继承棋子类?基类有一个move函数,每个子类重写该函数?但是移动似乎只是我程序的一小部分,这样似乎没必要。
那么存储整型数值?不同的数值代表不同的棋子?似乎可以。
那么就用7个数代替七种棋子,但是棋子有黑白色,要用一个数表示棋子类型(即是車、马或其他)和棋子颜色两个信息,那就用BLANK =8代表空子,黑方的車、马、象、士、将、砲、卒分别为1到7,白方的車、马、相、士、帅、炮、兵分别为9到15。
这样判断某数组坐标上棋子的颜色,就把其值与BLANK 比较,大于BLANK为白色,否则为黑色。
判断某数组坐标上棋子的类型,则将其值模BLANK 。
另外,因为下棋双方的视角是相反的,所以,棋盘在存储时应该是相反的,移动时的坐标也应该进行转换。
3. 然后应该怎么通信呢?
我希望这个程序打开后,就能找到对方,并确定谁是黑色,谁是白色。
也许可以让Client 在运行之后就对局域网进行端口扫描,然后给出正在运行此程序的IP 地址列表,让用户选择要连接到哪个,如果对方已经有了连接,则对方会拒绝此连接,如果对方没有连接,则对方程序会向对方用户提示该连接请求,如果,对方用户同意,则连接建立,否则依然是拒绝此连接。
但是,我没有采用以上所述方法(因为太复杂,我还是先做好主体工作吧=_=)。
所以在程序开始运行后,会让用户输入对方的IP 地址,然后Server 开始监听。之后Client 开始向对方发出连接请求。
Server 监听时,如果收到连接请求,就看对方的IP 地址是否是用户输入的IP 地址,如果不是,说明连接请求不是用户所希望的对方发送的,那就继续监听。
Client 请求连接时,如果对方同意了,就要开始确定自己的颜色了。
确定颜色这里困扰了我很久,最后采用的解决方法是这样的:
核心思想就是谁先发出连接请求,谁就是黑色。
也就是在Client 连接上对方之后,要判断Server 是不是已经连接了对方,如果Server 已连接,就说明是对方先发出的连接请求,那么对方就是黑色,自己就设为白色。如果Server 没有连接,就说明自己先连接上了对方,也就是自己是黑色。
以上就是编码前及编码时的大致想法。
代码
注: 用 CodeBlocks 编译时若出现类似" undefined reference to `send@16' " 的错误,在Settings->Complier->Global Complier Settings->Linker Settings 中添加 C:\Program Files (x86)\CodeBlocks\MinGW\lib\libwsock32.a
main.cpp
1 #if defined(UNICODE) && !defined(_UNICODE) 2 #define _UNICODE 3 #elif defined(_UNICODE) && !defined(UNICODE) 4 #define UNICODE 5 #endif 6 7 #include <tchar.h> 8 #include <windows.h> 9 #include <pthread.h> 10 #include <windowsx.h> 11 #include "chinese_chess.h" 12 #include "Server.h" 13 #include "Client.h" 14 15 16 #define WIDTH 600 //界面宽度 17 #define HEIGHT 600 //界面高度 18 #define ZERO_X 70 //棋盘左边界 19 #define ZERO_Y 70 //棋盘上边界 20 #define PIECE_BKCOLOR RGB(195,163,109) //棋子背景色 21 #define PIECE_WH 45 //棋盘每个格子的宽度和高度 22 23 HWND hwnd; /* This is the handle for our window */ 24 char* ots_ip; //存储对方IP地址的字符串 25 int port; 26 bool is_connect_alive=false; //是否连接到对方 27 Board * chess_board; //棋盘 28 Server *server; 29 Client *client; 30 int chess_sx=-1; //移动起始位置的数组坐标 31 int chess_sy=-1; 32 int chess_dx=-1; //移动目标位置的数组坐标 33 int chess_dy=-1; 34 35 36 37 38 /* Declare Windows procedure */ 39 LRESULT CALLBACK WindowProcedure (HWND, UINT, WPARAM, LPARAM); 40 41 /* Make the class name into a global variable */ 42 TCHAR szClassName[ ] = _T("Chinese Chess"); 43 44 int WINAPI WinMain (HINSTANCE hThisInstance, 45 HINSTANCE hPrevInstance, 46 LPSTR lpszArgument, 47 int nCmdShow) { 48 MSG messages; /* Here messages to the application are saved */ 49 WNDCLASSEX wincl; /* Data structure for the windowclass */ 50 51 /* The Window structure */ 52 wincl.hInstance = hThisInstance; 53 wincl.lpszClassName = szClassName; 54 wincl.lpfnWndProc = WindowProcedure; /* This function is called by windows */ 55 wincl.style = CS_DBLCLKS; /* Catch double-clicks */ 56 wincl.cbSize = sizeof (WNDCLASSEX); 57 58 /* Use default icon and mouse-pointer */ 59 wincl.hIcon = LoadIcon (NULL, IDI_APPLICATION); 60 wincl.hIconSm = LoadIcon (NULL, IDI_APPLICATION); 61 wincl.hCursor = LoadCursor (NULL, IDC_ARROW); 62 wincl.lpszMenuName = NULL; /* No menu */ 63 wincl.cbClsExtra = 0; /* No extra bytes after the window class */ 64 wincl.cbWndExtra = 0; /* structure or the window instance */ 65 /* Use Windows's default colour as the background of the window */ 66 wincl.hbrBackground = (HBRUSH) COLOR_BACKGROUND; 67 68 /* Register the window class, and if it fails quit the program */ 69 if (!RegisterClassEx (&wincl)) 70 return 0; 71 72 /* The class is registered, let's create the program*/ 73 hwnd = CreateWindowEx ( 74 0, /* Extended possibilites for variation */ 75 szClassName, /* Classname */ 76 _T("Chinese Chess"), /* Title Text */ 77 WS_OVERLAPPEDWINDOW, /* default window */ 78 CW_USEDEFAULT, /* Windows decides the position */ 79 CW_USEDEFAULT, /* where the window ends up on the screen */ 80 WIDTH, /* The programs width */ 81 HEIGHT, /* and height in pixels */ 82 HWND_DESKTOP, /* The window is a child-window to desktop */ 83 NULL, /* No menu */ 84 hThisInstance, /* Program Instance handler */ 85 NULL /* No Window Creation data */ 86 ); 87 88 /* Make the window visible on the screen */ 89 ShowWindow (hwnd, nCmdShow); 90 91 /* Run the message loop. It will run until GetMessage() returns 0 */ 92 while (GetMessage (&messages, NULL, 0, 0)) { 93 /* Translate virtual-key messages into character messages */ 94 TranslateMessage(&messages); 95 /* Send message to WindowProcedure */ 96 DispatchMessage(&messages); 97 } 98 99 /* The program return-value is 0 - The value that PostQuitMessage() gave */ 100 return messages.wParam; 101 } 102 //把数组坐标转换为界面坐标 103 void xy_to_pixel(int x,int y,int*pixelx,int *pixely) { 104 *pixely=x*PIECE_WH+ZERO_Y; 105 *pixelx=y*PIECE_WH+ZERO_X; 106 } 107 //把界面坐标转换为数组坐标 108 void pixel_to_xy(int pixelx,int pixely,int*x,int *y) { 109 int r=PIECE_WH/2; 110 *y=(pixelx-(ZERO_X-r))/PIECE_WH; 111 *x=(pixely-(ZERO_Y-r))/PIECE_WH; 112 } 113 //以数组坐标画线 114 void draw_line(HDC hdc,int sx,int sy,int dx,int dy) { 115 int psx,psy,pdx,pdy; 116 xy_to_pixel(sx,sy,&psx,&psy); 117 xy_to_pixel(dx,dy,&pdx,&pdy); 118 MoveToEx (hdc, psx,psy, NULL) ; 119 LineTo (hdc, pdx, pdy) ; 120 } 121 //以数组坐标画棋子 122 void paint_piece(HDC hdc,int x,int y,int color,int type) { 123 static HBRUSH piece_brush =CreateSolidBrush (PIECE_BKCOLOR); //棋子的背景色 124 if(type==0||color==BLANK)return ; 125 int px,py; 126 xy_to_pixel(x,y,&px,&py); 127 int r=PIECE_WH/2; 128 SelectObject (hdc,piece_brush ) ; 129 SelectObject (hdc, GetStockObject (NULL_PEN)) ; 130 Ellipse(hdc,px-r,py-r,px+r,py+r); 131 char *text=new char[5]; 132 switch(type) { 133 case JU: 134 strcpy(text,"車"); 135 break; 136 case MA: 137 strcpy(text,"马"); 138 break; 139 case XIANG: 140 if(color==BLACK)strcpy(text,"象"); 141 else strcpy(text,"相"); 142 break; 143 case SHI: 144 strcpy(text,"士"); 145 break; 146 case JIANG: 147 if(color==BLACK)strcpy(text,"将"); 148 else strcpy(text,"帅"); 149 break; 150 case PAO: 151 if(color==BLACK)strcpy(text,"砲"); 152 else 153 strcpy(text,"炮"); 154 break; 155 case ZU: 156 if(color==BLACK)strcpy(text,"卒"); 157 else 158 strcpy(text,"兵"); 159 break; 160 default: 161 strcpy(text,""); 162 } 163 SetBkColor(hdc,PIECE_BKCOLOR);//设置文字背景色 164 if(color==BLACK) { 165 SetTextColor(hdc,RGB(0,0,0)); //设置文字颜色 166 } else { 167 SetTextColor(hdc,RGB(255,255,255)); 168 } 169 TextOut (hdc, px-r/2, py-r/2,text , strlen("马")) ; 170 delete text; 171 } 172 173 void* main_listen(void *) { 174 server->listen_message(); 175 return 0; 176 } 177 //创建线程,使server开始监听 178 bool start_listen() { 179 pthread_t listen_p; 180 int ret; 181 ret= pthread_create( &listen_p, NULL, main_listen,NULL ); // 182 if( ret != 0 ) { //创建线程成功返回0 183 //printf("pthread_create error:error_code=%d\n",ret ); 184 handle_error(THREAD_ERROR,true,true); 185 return false; 186 } 187 return true; 188 } 189 190 191 void* chess_connect(void *) { 192 client->connect_to_ots(); //client开始连接对方server,连接成功后返回 193 InvalidateRect(hwnd,NULL,true); 194 } 195 196 void init() { 197 server=new Server();//创建Server对象 198 client=new Client(); //创建Client对象, 199 start_listen(); //创建线程,server开始监听 200 Sleep(1000); 201 pthread_t connect_p; 202 int ret; 203 ret= pthread_create( &connect_p, NULL, chess_connect,NULL); // 204 if( ret != 0 ) { //创建线程成功返回0 205 //printf("pthread_create error:error_code=%d\n",ret ); 206 handle_error(THREAD_ERROR,true,true); 207 return ; 208 } 209 } 210 211 212 213 /* This function is called by the Windows function DispatchMessage() */ 214 215 LRESULT CALLBACK WindowProcedure (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { 216 static POINT mouse; 217 static HDC hdc; 218 static PAINTSTRUCT ps ; 219 static int iofip=0; //index of ots_ip 220 switch (message) { /* handle the messages */ 221 case WM_CREATE: { 222 port=35536; 223 ots_ip=new char[20]; 224 strcpy(ots_ip,""); 225 } 226 break; 227 case WM_KEYDOWN://识别按键,显示输入的内容(对方IP地址) 228 if(server!=NULL)break; 229 if(wParam==13) {//如果是ENTER,则初始化server、client,并开始连接,连接成功后初始化board 230 init(); 231 Sleep(100); 232 InvalidateRect(hwnd,NULL,true); 233 } 234 if(wParam==VK_BACK) {//删除键 235 if(iofip==0)return 0; 236 iofip--; 237 ots_ip[iofip]='\0'; 238 } 239 if(wParam<106&&wParam>95) {//小键盘数字键 240 wParam-=48; 241 } 242 if(wParam<58&&wParam>47) {//主键盘数字键 243 ots_ip[iofip]='0'-48+wParam; 244 iofip++; 245 ots_ip[iofip]='\0'; 246 } 247 if(wParam==110||wParam==229) {//小数点键,小键盘110,主键盘229 248 ots_ip[iofip]='.'; 249 iofip++; 250 ots_ip[iofip]='\0'; 251 } 252 InvalidateRect(hwnd,NULL,true); 253 break; 254 case WM_PAINT: { 255 static HBRUSH bk_brush =CreateSolidBrush (RGB(240,240,240)); //棋子的背景色 256 hdc=BeginPaint (hwnd,&ps) ; 257 static HFONT hFont; 258 LOGFONT lf; 259 lf.lfHeight=PIECE_WH/2; 260 lf.lfWidth=0; 261 lf.lfEscapement=0; 262 lf.lfOrientation=0 ; 263 lf.lfWeight=5; 264 lf.lfItalic=0 ; 265 lf.lfUnderline=0 ; 266 lf.lfStrikeOut=0 ; 267 lf.lfCharSet=DEFAULT_CHARSET ; 268 lf.lfOutPrecision=0 ; 269 lf.lfClipPrecision=0 ; 270 lf.lfQuality=0 ; 271 lf.lfPitchAndFamily=0 ; 272 lstrcpy (lf.lfFaceName, _T("楷体") ); 273 hFont = CreateFontIndirect (&lf) ; 274 SelectFont(hdc,hFont); 275 SelectObject(hdc,bk_brush); 276 Rectangle(hdc,0,0,WIDTH,HEIGHT); 277 SetBkColor(hdc,RGB(240,240,240)); 278 if(chess_board==NULL) {//显示输入的IP地址 279 char tip[20]="请输入对方IP地址:"; 280 Rectangle(hdc,WIDTH/5,HEIGHT/2-10,WIDTH/5*4,HEIGHT/2+30); 281 TextOut(hdc,WIDTH/5,HEIGHT/2-50,tip,strlen(tip)); 282 SetBkColor(hdc,RGB(240,240,240)); 283 TextOut(hdc,WIDTH/5+5,HEIGHT/2,ots_ip,strlen(ots_ip)); 284 if(server!=NULL) { //board==NULL而server!=NULL表示正在连接过程中 285 char tip[20]="正在连接......"; 286 SetBkColor(hdc,RGB(240,240,240)); 287 TextOut(hdc,WIDTH/5,HEIGHT/2+50,tip,strlen(tip)); 288 } 289 EndPaint(hwnd,&ps); 290 break; 291 } 292 char text[10]="你的颜色:"; 293 if(chess_board->get_color()==BLACK) {