[游戏学习22] MFC 井字棋 双人对战
>_<:太多啦,感觉用英语说的太慢啦,没想到一年做的东西竟然这么多.....接下来要加速啦!
>_<:注意这里必须用MFC和前面的Win32不一样啦!
>_<:这也是第一次出现MFC游戏,其框架和逻辑的写法和Win32有很大的区别,建议先看一下MFC的基础再理解代码:
>_<:TicTac.h
1 #define EX 1 //该点左鼠标 2 #define OH 2 //该点右鼠标 3 4 class CMyApp : public CWinApp 5 { 6 public: 7 virtual BOOL InitInstance (); 8 }; 9 10 class CMainWindow : public CWnd //不是继承CFrameWnd 因此需要在CMainWindow()自己定义窗口类了 11 { 12 protected: 13 static const CRect m_rcSquares[9]; // Grid coordinates 14 int m_nGameGrid[9]; // 9个格子的状态是否被下0没下;1左下了;2右下了 15 int m_nNextChar; // 下一个鼠标状态左or右 (EX or OH) 16 bool ptab[9][8]; //玩家的获胜的状态表 17 bool ctab[9][8]; //电脑的获胜的状态表 18 int win[2][8]; //每种状态表里的棋子数 19 20 int GetRectID (CPoint point); 21 void DrawBoard (CDC* pDC); 22 void DrawX (CDC* pDC, int nPos); 23 void DrawO (CDC* pDC, int nPos); 24 void CpDraw(CDC* pDC); 25 void InitGame(); 26 void out(); 27 void ResetGame (); 28 bool CheckForGameOver (); 29 int IsWinner (); 30 BOOL IsDraw (); 31 32 public: 33 CMainWindow (); 34 35 protected: 36 virtual void PostNcDestroy ();//在程序终止之前销毁CMainWindow对象 37 38 afx_msg void OnPaint (); 39 afx_msg void OnLButtonDown (UINT nFlags, CPoint point); 40 afx_msg void OnLButtonDblClk (UINT nFlags, CPoint point); 41 afx_msg void OnRButtonDown (UINT nFlags, CPoint point); 42 43 DECLARE_MESSAGE_MAP () 44 };
>_<:TicTac.cpp
1 #include <afxwin.h> 2 #include "TicTac.h" 3 #include <fstream> 4 #include <iostream> 5 #include<iomanip> 6 using namespace std; 7 CMyApp myApp; 8 /*ofstream Cout("out.txt"); 9 void CMainWindow::out(){ 10 Cout<<"ptab[][]=:\n"; 11 for(int i=0;i<9;i++){ 12 for(int j=0;j<8;j++) 13 Cout<<setw(3)<<ptab[i][j]<<' '; 14 Cout<<'\n'; 15 } 16 Cout<<"ctab[][]=:\n"; 17 for(int i=0;i<9;i++){ 18 for(int j=0;j<8;j++) 19 Cout<<setw(3)<<ctab[i][j]<<' '; 20 Cout<<'\n'; 21 } 22 Cout<<"win[][]=:\n"; 23 for(int i=0;i<2;i++){ 24 for(int j=0;j<8;j++) 25 Cout<<setw(3)<<win[i][j]<<' '; 26 Cout<<'\n'; 27 } 28 }*/ 29 ///////////////////////////////////////////////////////////////////////// 30 // CMyApp member functions 31 32 BOOL CMyApp::InitInstance () 33 { 34 m_pMainWnd = new CMainWindow; 35 m_pMainWnd->ShowWindow (m_nCmdShow); 36 m_pMainWnd->UpdateWindow (); 37 return TRUE; 38 } 39 40 ///////////////////////////////////////////////////////////////////////// 41 // CMainWindow message map and member functions 42 43 BEGIN_MESSAGE_MAP (CMainWindow, CWnd) 44 ON_WM_PAINT () 45 ON_WM_LBUTTONDOWN () 46 ON_WM_LBUTTONDBLCLK () 47 ON_WM_RBUTTONDOWN () 48 END_MESSAGE_MAP () 49 50 //9个矩形区域用来判定鼠标是否点进某一区域 51 const CRect CMainWindow::m_rcSquares[9] = { 52 CRect ( 16, 16, 112, 112), 53 CRect (128, 16, 224, 112), 54 CRect (240, 16, 336, 112), 55 CRect ( 16, 128, 112, 224), 56 CRect (128, 128, 224, 224), 57 CRect (240, 128, 336, 224), 58 CRect ( 16, 240, 112, 336), 59 CRect (128, 240, 224, 336), 60 CRect (240, 240, 336, 336) 61 }; 62 63 CMainWindow::CMainWindow () 64 { 65 //初始化游戏 66 InitGame(); 67 68 69 70 //注册一个 WNDCLASS 窗口类. 71 CString strWndClass = AfxRegisterWndClass ( 72 CS_DBLCLKS, // Class style(有双击时间发生的窗口类型) 73 AfxGetApp ()->LoadStandardCursor (IDC_ARROW), // Class cursor(加载一个系统光标,也可自己定义) 74 (HBRUSH) (COLOR_3DFACE + 1), // Background brush(每次::BeginPaint时用它清空客户区);COLOR_3DFACE+1是指定窗口具有与按钮对话框一致的背景色和其他一些3D属性;默认为灰亮色 75 AfxGetApp ()->LoadStandardIcon (IDI_WINLOGO) // Class icon(加载系统图标,也可自己定义) 76 ); 77 78 //调用CWnd::CreateEx()创建主窗口 79 //第一个参数表示0个或是多个WS_EX标志组合;2:AfxRegisterWndClass()返回的WNDCLASS名称; 80 //3、标题;4、窗口样式 81 CreateEx (0, strWndClass, _T ("井字棋"), 82 WS_OVERLAPPED | WS_SYSMENU | WS_CAPTION | WS_MINIMIZEBOX, //WS_THICKFRAME窗口可调大小属性(这里不用) 83 CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, //初始位置和大小,这里用CW_USEDEFAULT让Windows拾取窗口和大小 84 NULL, NULL); 85 86 //处理窗口位置和尺寸 87 CRect rect (0, 0, 352, 352); //理想客户区窗口矩形形状 88 CalcWindowRect (&rect); //根据分辨率、菜单...计算窗口矩形大小(必须在窗口创建后调用) 89 90 SetWindowPos (NULL, 0, 0, rect.Width (), rect.Height (), 91 SWP_NOZORDER | SWP_NOMOVE | SWP_NOREDRAW); 92 } 93 94 //在程序结束之前销毁创建的CMainWindow对象 95 void CMainWindow::PostNcDestroy () 96 { 97 delete this; 98 } 99 100 //OnPaint()响应每次重绘棋盘 101 void CMainWindow::OnPaint () 102 { 103 CPaintDC dc (this); 104 DrawBoard (&dc); 105 } 106 107 108 //单击鼠标左键响应 109 void CMainWindow::OnLButtonDown (UINT nFlags, CPoint point) 110 { 111 CClientDC dc (this); 112 113 //如果不该左键响应(即不该左键下,返回) 114 if (m_nNextChar != EX){ 115 return ; 116 } 117 118 //获得点击矩形区域编号 119 //如果没有点中或者已经被下棋了,返回 120 int nPos = GetRectID (point); 121 if ((nPos == -1) || (m_nGameGrid[nPos] != 0)) 122 return; 123 124 //标记已下并改变下一个点击状态 125 m_nGameGrid[nPos] = EX; 126 m_nNextChar = OH; 127 128 //画上图并判断游戏是否结束 129 DrawX (&dc, nPos); 130 if(CheckForGameOver ())return; 131 132 //后续改变胜利表和各人、机各胜利组合的棋子数 133 for(int i=0;i<8;i++){ 134 if(ptab[nPos][i]){ 135 win[0][i]++; 136 ctab[nPos][i]=false; 137 win[1][i]=5; 138 } 139 } 140 141 //电脑下棋 142 CpDraw(&dc); 143 if(CheckForGameOver ())return; 144 } 145 146 //单击鼠标右键响应(同左键) 147 void CMainWindow::OnRButtonDown (UINT nFlags, CPoint point) 148 { 149 if (m_nNextChar != OH) 150 return; 151 152 int nPos = GetRectID (point); 153 if ((nPos == -1) || (m_nGameGrid[nPos] != 0)) 154 return; 155 156 m_nGameGrid[nPos] = OH; 157 m_nNextChar = EX; 158 159 CClientDC dc (this); 160 DrawO (&dc, nPos); 161 CheckForGameOver (); 162 } 163 164 //左键双击边框重新开始 165 //dc.GetPixel (Point point)获取当前光标下像素颜色判断与黑色匹配 166 void CMainWindow::OnLButtonDblClk (UINT nFlags, CPoint point) 167 { 168 CClientDC dc (this); 169 if (dc.GetPixel (point) == RGB (0, 0, 0)) 170 ResetGame (); 171 } 172 173 //判定鼠标是否点进矩形某一区域,点进返回区域编号,没有返回-1 174 //此处用了一个rect.PtInRect(Point point)函数帮助判定 175 int CMainWindow::GetRectID (CPoint point) 176 { 177 for (int i=0; i<9; i++) { 178 if (m_rcSquares[i].PtInRect (point)) 179 return i; 180 } 181 return -1; 182 } 183 184 //画上棋盘并画上圈和叉 185 void CMainWindow::DrawBoard (CDC* pDC) 186 { 187 //画上棋盘 188 CPen pen (PS_SOLID, 16, RGB (0, 0, 0)); 189 CPen* pOldPen = pDC->SelectObject (&pen); 190 191 pDC->MoveTo (120, 16); 192 pDC->LineTo (120, 336); 193 194 pDC->MoveTo (232, 16); 195 pDC->LineTo (232, 336); 196 197 pDC->MoveTo (16, 120); 198 pDC->LineTo (336, 120); 199 200 pDC->MoveTo (16, 232); 201 pDC->LineTo (336, 232); 202 203 //画上叉和圈 204 for (int i=0; i<9; i++) { 205 if (m_nGameGrid[i] == EX) 206 DrawX (pDC, i); 207 else if (m_nGameGrid[i] == OH) 208 DrawO (pDC, i); 209 } 210 pDC->SelectObject (pOldPen); 211 } 212 213 //画叉函数 214 void CMainWindow::DrawX (CDC* pDC, int nPos) 215 { 216 CPen pen (PS_SOLID, 16, RGB (255, 0, 0));//宽为16像素的红笔 217 CPen* pOldPen = pDC->SelectObject (&pen); 218 219 CRect rect = m_rcSquares[nPos]; 220 rect.DeflateRect (16, 16);//把矩形每个方向都缩进16个像素作为线条边框 221 pDC->MoveTo (rect.left, rect.top); 222 pDC->LineTo (rect.right, rect.bottom); 223 pDC->MoveTo (rect.left, rect.bottom); 224 pDC->LineTo (rect.right, rect.top); 225 226 pDC->SelectObject (pOldPen); 227 } 228 229 //画圈函数 230 void CMainWindow::DrawO (CDC* pDC, int nPos) 231 { 232 CPen pen (PS_SOLID, 16, RGB (0, 0, 255));//宽为16像素的红笔 233 CPen* pOldPen = pDC->SelectObject (&pen); 234 pDC->SelectStockObject (NULL_BRUSH); //空画刷是为了防止画出的圆内部出现白色遮住背景 235 236 CRect rect = m_rcSquares[nPos]; 237 rect.DeflateRect (16, 16);//把矩形每个方向都缩进16个像素作为圆的边框 238 pDC->Ellipse (rect); 239 240 pDC->SelectObject (pOldPen); 241 } 242 243 //电脑画图 244 void CMainWindow::CpDraw(CDC* pDC) 245 { 246 int grades[2][9]; 247 int m,i,max=0; 248 int u; 249 250 for(m=0;m<9;m++) 251 { 252 grades[0][m]=0; 253 grades[1][m]=0; 254 255 if(m_nGameGrid[m]==0) 256 { 257 for(i=0;i<8;i++) 258 { 259 //计算玩家在空棋格上的获胜分数 260 if(ptab[m][i] && win[0][i]!=5) 261 { 262 switch(win[0][i]) 263 { 264 case 0: 265 grades[0][m]+=1; 266 break; 267 case 1: 268 grades[0][m]+=2000; 269 break; 270 case 2: 271 grades[0][m]+=10000; 272 break; 273 } 274 } 275 276 //计算计算机在空格上的获胜分数 277 if(ctab[m][i] && win[1][i]!=5) 278 { 279 switch(win[1][i]) 280 { 281 case 0: 282 grades[1][m]+=1; 283 break; 284 case 1: 285 grades[1][m]+=2001; 286 break; 287 case 2: 288 grades[1][m]+=10001; 289 break; 290 } 291 } 292 } 293 294 if(max==0)u=m; 295 296 if(grades[0][m]>max){ 297 max=grades[0][m]; 298 u=m; 299 } 300 else if(grades[0][m]==max){ 301 if(grades[1][m]>grades[1][u])u=m; 302 } 303 304 if(grades[1][m]>max){ 305 max=grades[1][m]; 306 u=m; 307 } 308 else if(grades[1][m]==max){ 309 if(grades[0][m]>grades[0][u])u=m; 310 } 311 } 312 } 313 314 //标记已下并改变下一个点击状态 315 m_nGameGrid[u]=OH; 316 m_nNextChar = EX; 317 318 //画上图 319 DrawO(pDC,u); 320 321 //后续改变胜利表和各人、机各胜利组合的棋子数 322 for(i=0;i<8;i++){ 323 if(ctab[u][i]){ 324 win[1][i]++; 325 ptab[u][i]=false; 326 win[0][i]=5; 327 } 328 } 329 } 330 331 //响应胜利结束的函数 332 bool CMainWindow::CheckForGameOver () 333 { 334 int nWinner; 335 336 //通过调用IsWinner ()函数获取谁获胜;并用MessageBox输出胜利消息;响应OK后重开一局 337 //==Message(CString,_T(标题),类型) 338 if (nWinner = IsWinner ()) { 339 CString string = (nWinner == EX) ? 340 _T ("X wins!") : _T ("O wins!"); 341 MessageBox (string, _T ("Game Over"), MB_ICONEXCLAMATION | MB_OK); 342 ResetGame (); 343 return 1; 344 } 345 346 //通过IsDraw ()函数判断是否平局 347 else if (IsDraw ()) { 348 MessageBox (_T ("It's a draw!"), _T ("Game Over"), 349 MB_ICONEXCLAMATION | MB_OK); 350 ResetGame (); 351 return 1; 352 } 353 return 0; 354 } 355 356 //判断输赢EX左胜;OH右胜;0没有胜 357 int CMainWindow::IsWinner () 358 { 359 //用静态数组存储获胜组合 360 static int nPattern[8][3] = { 361 0, 1, 2, 362 3, 4, 5, 363 6, 7, 8, 364 0, 3, 6, 365 1, 4, 7, 366 2, 5, 8, 367 0, 4, 8, 368 2, 4, 6 369 }; 370 371 for (int i=0; i<8; i++) { 372 if ((m_nGameGrid[nPattern[i][0]] == EX) && 373 (m_nGameGrid[nPattern[i][1]] == EX) && 374 (m_nGameGrid[nPattern[i][2]] == EX)) 375 return EX; 376 377 if ((m_nGameGrid[nPattern[i][0]] == OH) && 378 (m_nGameGrid[nPattern[i][1]] == OH) && 379 (m_nGameGrid[nPattern[i][2]] == OH)) 380 return OH; 381 } 382 return 0; 383 } 384 385 //判断是否平局函数 386 BOOL CMainWindow::IsDraw () 387 { 388 for (int i=0; i<9; i++) { 389 if (m_nGameGrid[i] == 0) 390 return FALSE; 391 } 392 return TRUE; 393 } 394 395 //初始化游戏 396 void CMainWindow::InitGame() 397 { 398 399 int i,k; 400 int count=0; 401 402 //设定玩家与计算机在各个获胜组合中的棋子数 403 for(i=0;i<8;i++) 404 { 405 win[0][i]=0; 406 win[1][i]=0; 407 } 408 409 //初始化棋盘状态 410 ::ZeroMemory (m_nGameGrid,9*sizeof(int)); 411 memset(ctab,0,sizeof(ctab)); 412 memset(ptab,0,sizeof(ptab)); 413 //设定水平方向的获胜组合 414 for(i=0;i<=6;i+=3) 415 { 416 for(k=0;k<3;k++)//3个棋子1个获胜组合 417 { 418 ptab[i+k][count]=true; 419 ctab[i+k][count]=true; 420 } 421 count++; 422 } 423 424 //设定垂直方向的获胜组合 425 for(k=0;k<3;k++) 426 { 427 for(i=0;i<=6;i+=3)//3个棋子1个获胜组合 428 { 429 ptab[i+k][count]=true; 430 ctab[i+k][count]=true; 431 } 432 count++; 433 } 434 435 436 //设定对角线方向上的获胜组合 437 for(i=2;i<=6;i+=2){ 438 ptab[i][count]=true; 439 ctab[i][count]=true; 440 }count++; 441 for(i=0;i<=8;i+=4){ 442 ptab[i][count]=true; 443 ctab[i][count]=true; 444 } 445 446 447 srand(unsigned(time(NULL))); 448 449 m_nNextChar = EX;//玩家先走 450 } 451 //重新开始初始化 452 void CMainWindow::ResetGame () 453 { 454 InitGame(); 455 Invalidate (); //使控件的整个图面无效并导致重绘控件 456 }