Win32 遊戲開發:TicTacToe(井字遊戲) 下篇
阿新 • • 發佈:2019-02-06
上篇進行講解了遊戲的規則、介面設計、遊戲流程、······
下面我們繼續碼程式碼吧,(#^.^#)
在寫程式碼前先說之前所漏掉的兩個列舉,分別是:
① ClickPlayer (列舉點選玩家)
/* 點選玩家列舉 */
typedef enum _ClickPlayer {
ClickPlayer_Player1 = 0x00000001, // 玩家1
ClickPlayer_Player2 = 0x00000002 // 玩家2
}ClickPlayer;
② GameOverType (列舉遊戲結束型別)
/* 遊戲結局 */ typedef enum _GameOverType { GameOverType_Tie = 0x00000001, // 平局 GameOverType_Player1 = 0x00000002, // 玩家1 GameOverType_Player2 = 0x00000004 // 玩家2 }GameOverType;
下面開始繼續程式碼的講解:
2)遊戲初始化(TicTacToe_Init)
①TicTacToe_Init
VOID TicTacToe_Init(HWND hWnd)
{
/* 遊戲初始化 */
g_game.Init(hWnd);
/* 建立相容DC和相容點陣圖 */
Util::CreateDoubleBuffer(hWnd, g_mdc, g_bitmap);
::SelectObject(g_mdc, g_bitmap);
}
g_game.Init(hWnd),進行初始化遊戲中的遊戲物件
Util::CreateDoubleBuffer進行建立雙緩衝(Util在後面進行講解,此處先為了解 )
::SelectObject將相容點陣圖放入相容DC中
②Game::Init(遊戲初始化)
void Game::Init(HWND hWnd)
{
/* 遊戲初始化 */
m_bIsGameOver = false;
m_hWnd = hWnd;
/* 初始化棋盤 */
m_board.Init(hWnd);
}
m_bIsGameOver = false,遊戲設定為未結束
m_hWnd = hWnd,儲存視窗控制代碼為後面使用
m_board.Init(hWnd),初始化棋盤
③Board::Init(棋盤初始化)
void Board::Init(HWND hWnd) { /* 設定玩家1為開始玩家*/ m_cur_click = ClickPlayer::ClickPlayer_Player1; ::GetClientRect(hWnd, &m_rect); /* 初始化九個格子 m_ppPreces不為NULL則為上局棋子 */ if (m_ppPreces != NULL) { delete m_ppPreces; m_ppPreces = NULL; } m_ppPreces = new Prece*[9]; /* 兩邊餘量 */ int x_margin = 20; int y_margin = 20; /* 設定棋盤矩形大小 */ m_rect.left += x_margin; m_rect.top += y_margin; m_rect.right -= x_margin; m_rect.bottom -= y_margin; /* 棋盤寬高 */ int width = m_rect.right - m_rect.left; int height = m_rect.bottom - m_rect.top; /* 棋盤左上角(x, y) 以及棋子的寬和高 */ int x_start = m_rect.left; int y_start = m_rect.top; int w_distance = width / 3; int h_distance = height / 3; for (int c = 0, col = 3; c < col; ++c) { for (int r = 0, row = 3; r < row; ++r) { /* 建立棋盤格子,並儲存到棋盤中 */ m_ppPreces[c * 3 + r] = new Prece(x_start + (r * w_distance), y_start + (c * h_distance), w_distance, h_distance, c * 3 + r); } } }
拆分成小部分講解:
/* 設定玩家1為開始玩家 */
m_cur_click = ClickPlayer::ClickPlayer_Player1;
設定玩家1為先手(ClickPlayer為點選玩家列舉,後面進行講解)
/* 初始化九個格子 m_ppPreces不為NULL則為上局棋子 */
if (m_ppPreces != NULL) {
delete m_ppPreces;
m_ppPreces = NULL;
}
m_ppPreces = new Prece*[9];
如果已經建立了9個棋子,則釋放此9個棋子。(如果不為NULL則是上一局的棋子)
然後分配9個格子空間進行儲存格子
/* 兩邊餘量 */
int x_margin = 20;
int y_margin = 20;
/* 設定棋盤矩形大小 */
m_rect.left += x_margin;
m_rect.top += y_margin;
m_rect.right -= x_margin;
m_rect.bottom -= y_margin;
設定棋盤的矩形大小,也就是給視窗四邊留有相應的餘量
/* 棋盤寬高 */
int width = m_rect.right - m_rect.left;
int height = m_rect.bottom - m_rect.top;
/* 棋盤左上角(x, y) 以及棋子的寬和高 */
int x_start = m_rect.left;
int y_start = m_rect.top;
int w_distance = width / 3;
int h_distance = height / 3;
width和height,分別為棋盤的寬度和高度
x_left_top和y_left_top,分別為棋盤的左上角(x, y)
w_distance和h_distance,分別為棋子的寬度和高度
然後進行設定棋子相應的位置
for (int c = 0, col = 3; c < col; ++c)
{
for (int r = 0, row = 3; r < row; ++r)
{
/* 建立棋盤格子,並儲存到棋盤中 */
m_ppPreces[c * 3 + r] = new Prece(x_start + (r * w_distance), y_start + (c * h_distance), w_distance, h_distance, c * 3 + r);
}
}
兩個for迴圈為建立3x3的棋子,col(列) row(行)
m_ppPreces[c * 3 + r]為將棋子放入陣列對應的下標中,也就是1對應棋子1、2對應棋子2···
new Prece(x_start + (r * w_distance), y_start + (c * h_distance), w_distance, h_distance, c * 3 + r)
Prece::Prece(int x, int y, int w, int h, int index)
{
/* 格子初始化 */
m_x = x;
m_y = y;
m_w = w;
m_h = h;
m_index = index;
m_bIsClick = false;
}
new 棋子,建立棋子,並且初始化位置和大小以及棋子的下標(也就是1,2,3,···標識棋子位置)
和點選狀態為未點選
PS:所以在Board::Init中為初始化棋盤大小和初始化棋盤上的9個棋子的位置等資訊
2) 遊戲滑鼠點選事件處理(TicTacToe_MouseDown)
① case WM_LBUTTONDOWN
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
······
case WM_LBUTTONDOWN:
/* 井字遊戲滑鼠點選訊息處理 */
TicTacToe_MouseDown(LOWORD(lParam), HIWORD(lParam));
break;
······
}
return ((LRESULT)0);
}
lParam為附加訊息(不同訊息的附加訊息都不同)
低位儲存x座標(使用LOWORD獲取低位資料)
高位儲存y座標(使用HIWORD獲取低位資料)
最後將(x, y)傳入TicTacToe_MouseDown
② TicTacToe_MouseDown(點選事件處理)
VOID TicTacToe_MouseDown(int x, int y)
{
/* 遊戲滑鼠處理 */
g_game.MouseDown(x, y);
}
傳遞滑鼠訊息給遊戲物件
③Game::MouseDown(遊戲點選事件處理)
void Game::MouseDown(int x, int y)
{
if (m_bIsGameOver)
{
/* 如果遊戲結束,點選重新開始 */
Init(m_hWnd);
return;
}
/* 遊戲滑鼠點選訊息處理 */
m_board.MouseDown(x, y);
/* 檢測是否遊戲結束 */
CheckGameOver();
}
拆成小部分進行講解:
if (m_bIsGameOver)
{
/* 如果遊戲結束,點選重新開始 */
Init(m_hWnd);
return;
}
如果遊戲已經結束,則點選為重新開始遊戲
此處的Init(m_hWnd)為上面所講的初始化遊戲
/* 遊戲滑鼠點選訊息處理 */
m_board.MouseDown(x, y);
講滑鼠訊息傳遞到棋盤中
/* 檢測是否遊戲結束 */
CheckGameOver();
檢測是否遊戲結束(判斷條件為是否三個點連成一線,或9個格子都填滿了)
(CheckGameOver方法後面進行講解,此處先了解)
④ Board::MouseDown(棋盤點選事件處理)
void Board::MouseDown(int x, int y)
{
/* 檢測是否點選到格子 */
for (int i = 0, count = 9; i < 9; ++i)
{
if (m_ppPreces[i]->CheckClick(x, y))
{
/* 設定棋子被當前落棋玩家點選 */
m_ppPreces[i]->Click(m_cur_click);
/* 點選到格子,則切換玩家下棋 */
m_cur_click = (m_cur_click == ClickPlayer::ClickPlayer_Player1 ?
ClickPlayer::ClickPlayer_Player2 :
ClickPlayer::ClickPlayer_Player1);
}
}
}
for迴圈為遍歷9個棋子
if (m_ppPreces[i]->CheckClick(x, y))
通過滑鼠點選的位置(x, y)進行判斷是否點選到棋子(Prece::CheckClick方法下面進行講解)
/* 設定棋子被當前落棋玩家點選 */
m_ppPreces[i]->Click(m_cur_click);
設定棋子被當前落棋玩家點選
/* 點選到格子,則切換玩家下棋 */
m_cur_click = (m_cur_click == ClickPlayer::ClickPlayer_Player1 ?
ClickPlayer::ClickPlayer_Player2 :
ClickPlayer::ClickPlayer_Player1);
切換落棋玩家
⑤Prece::CheckClick(判斷棋子是否被點選)
bool Prece::CheckClick(int x, int y)
{
/* 判斷滑鼠點選的位置是否在格子內 */
return (!m_bIsClick) && (x <= m_x + m_w && y <= m_y + m_h && x >= m_x && y >= m_y);
}
通過判斷棋子是否已被點選,並且滑鼠點選的(x, y)是否在棋子的矩形中
⑥Prece::Click(設定棋子被玩家點選)
void Prece::Click(ClickPlayer sender)
{
/* 格子被點選 */
m_bIsClick = true;
/* 設定點選玩家 */
m_click_player = sender;
}
設定棋子被點選,設定點選玩家
3) 遊戲渲染 (TicTacToe_Render)
① TicTacToe_Render(遊戲渲染)
VOID TicTacToe_Render(HWND hWnd)
{
if (g_mdc == NULL)
return;
HDC hdc = ::GetDC(hWnd);
RECT clientRect;
::GetClientRect(hWnd, &clientRect);
int width = clientRect.right - clientRect.left;
int height = clientRect.bottom - clientRect.top;
/* 遊戲繪製 */
g_game.Render(g_mdc);
/* 將相容DC繪製到裝置DC中 */
::BitBlt(hdc, 0, 0, width, height, g_mdc, 0, 0, SRCCOPY);
::ReleaseDC(hWnd, hdc);
}
GetDC()獲取裝置DC,GetClientRect()獲取客戶端區域矩形
獲取客戶端的寬和高,主要為了將相容DC繪製到裝置DC中(使用了BitBlt函式)
用了GetDC(),不要忘了使用ReleaseDC()進行釋放裝置DC
/* 遊戲繪製 */
g_game.Render(g_mdc);
繪製遊戲中的遊戲物件,將其繪製到相容DC中
② Game::Render(繪製遊戲中的遊戲物件)
void Game::Render(HDC hdc)
{
/* 繪製遊戲背景 */
DrawBackground(hdc);
/* 繪製棋盤 */
m_board.Render(hdc);
/* 繪製遊戲結束 */
DrawGameOver(hdc);
}
在遊戲繪製中分別有:繪製遊戲背景、繪製畫板、繪製遊戲結束
③ Game::DrawBackground(繪製遊戲背景)
void Game::DrawBackground(HDC hdc)
{
/* 建立背景顏色畫刷 */
HBRUSH brush = ::CreateSolidBrush(RGB(22, 22, 22));
RECT rect;
::GetClientRect(m_hWnd, &rect);
/* 填充背景顏色 */
::FillRect(hdc, &rect, brush);
::DeleteObject(brush); brush = NULL;
}
1. 首先建立背景顏色畫刷
2. 獲取視窗客戶端矩形
3. 填充背景畫刷顏色到視窗客戶端矩形
4. 最後釋放畫刷物件
④ Game::DrawGameOver(繪製遊戲結束)
void Game::DrawGameOver(HDC hdc)
{
/* 繪製遊戲結束資訊 */
if (m_bIsGameOver)
{
LPCWSTR lpszTitle = _T("遊戲結束");
LPCWSTR lpszBody = NULL;
LPCWSTR lpszTips = _T("點選螢幕重新開始遊戲");
/* 設定顯示訊息 */
if (m_over_type == GameOverType::GameOverType_Tie)
lpszBody = _T(" 平局");
else if (m_over_type == GameOverType::GameOverType_Player1)
lpszBody = _T("玩家1獲勝");
else
lpszBody = _T("玩家2獲勝");
// 設定繪製的文字字型
HFONT hFont, hOldFont;
Util::CreateLogFont(hFont, 45);
hOldFont = (HFONT)SelectObject(hdc, hFont);
/* 文字背景為透明 */
::SetBkMode(hdc, TRANSPARENT);
/* 繪製標題 */
::SetTextColor(hdc, RGB(197, 31, 31));
::TextOut(hdc, 150, 100, lpszTitle, lstrlen(lpszTitle));
/* 繪製資訊 */
::SetTextColor(hdc, RGB(87, 105, 60));
::TextOut(hdc, 150, 225, lpszBody, lstrlen(lpszBody));
/* 繪製提示訊息 */
::SetTextColor(hdc, RGB(91, 74, 66));
::TextOut(hdc, 0, 350, lpszTips, lstrlen(lpszTips));
::SelectObject(hdc, hOldFont);
::DeleteObject(hFont); hFont = NULL;
}
}
拆分進行講解:
if (m_bIsGameOver)
當遊戲結束才進行繪製
LPCWSTR lpszTitle = _T("遊戲結束");
LPCWSTR lpszBody = NULL;
LPCWSTR lpszTips = _T("點選螢幕重新開始遊戲");
遊戲結束頁面顯示:標題(lpszTitle)、獲勝內容(lpszBody)、重新開始提示(lpszTips)
/* 設定顯示訊息 */
if (m_over_type == GameOverType::GameOverType_Tie)
lpszBody = _T(" 平局");
else if (m_over_type == GameOverType::GameOverType_Player1)
lpszBody = _T("玩家1獲勝");
else
lpszBody = _T("玩家2獲勝");
根據結局,進行顯示不同的獲勝內容
// 設定繪製的文字字型
HFONT hFont, hOldFont;
Util::CreateLogFont(hFont, 45);
hOldFont = (HFONT)SelectObject(hdc, hFont);
設定文字的字型
/* 文字背景為透明 */
::SetBkMode(hdc, TRANSPARENT);
設定文字背景顏色為透明
/* 繪製標題 */
::SetTextColor(hdc, RGB(197, 31, 31));
::TextOut(hdc, 150, 100, lpszTitle, lstrlen(lpszTitle));
/* 繪製資訊 */
::SetTextColor(hdc, RGB(87, 105, 60));
::TextOut(hdc, 150, 225, lpszBody, lstrlen(lpszBody));
/* 繪製提示訊息 */
::SetTextColor(hdc, RGB(91, 74, 66));
::TextOut(hdc, 0, 350, lpszTips, lstrlen(lpszTips));
將標題、獲勝內容、重新開始提示顯示到螢幕
SetTextColor(設定顯示文字的顏色)
TextOut(輸出文字到指定位置)
::SelectObject(hdc, hOldFont);
::DeleteObject(hFont); hFont = NULL;
恢復到之前的狀態,銷燬字型物件
PS:說了這麼多其實就是顯示(標題、獲勝內容、重新開始)
⑤ Board::Render (繪製棋盤)
void Board::Render(HDC hdc)
{
/* 繪製"井" */
DrawBoard(hdc);
/* 繪製棋子 */
for (int i = 0, count = 9; i < 9; ++i)
{
m_ppPreces[i]->Render(hdc);
}
}
在棋盤繪製中分別是繪製棋盤和繪製9個棋子
這裡的棋盤是指棋盤上面的"井"
⑥ Board::DrawBoard (繪製棋盤)
void Board::DrawBoard(HDC hdc)
{
/* 建立畫筆 */
HPEN hPen = ::CreatePen(PS_SOLID, 3, RGB(0, 0, 0));
HPEN hOldPen = (HPEN)::SelectObject(hdc, hPen);
/* 棋盤寬高 */
int width = m_rect.right - m_rect.left;
int height = m_rect.bottom - m_rect.top;
int x_left_top = m_rect.left;
int y_left_top = m_rect.top;
int w_distance = width / 3;
int h_distance = height / 3;
/* "井"四標邊 */
int points[4][4];
/* 豎線第一條 */
points[0][0] = x_left_top + w_distance;
points[0][1] = y_left_top;
points[0][2] = x_left_top + w_distance;
points[0][3] = y_left_top + height;
/* 豎線第二條 */
points[1][0] = x_left_top + 2 * w_distance;
points[1][1] = y_left_top;
points[1][2] = x_left_top + 2 * w_distance;
points[1][3] = y_left_top + height;
/* 橫線第一條 */
points[2][0] = x_left_top;
points[2][1] = y_left_top + h_distance;
points[2][2] = x_left_top + width;
points[2][3] = y_left_top + h_distance;
/* 橫線第二條 */
points[3][0] = x_left_top;
points[3][1] = y_left_top + 2 * h_distance;
points[3][2] = x_left_top + width;
points[3][3] = y_left_top + 2 * h_distance;
Util::DrawLine(hdc, points[0]);
Util::DrawLine(hdc, points[1]);
Util::DrawLine(hdc, points[2]);
Util::DrawLine(hdc, points[3]);
::SelectObject(hdc, hOldPen);
::DeleteObject(hPen); hPen = NULL;
}
拆分進行講解:
/* 建立畫筆 */
HPEN hPen = ::CreatePen(PS_SOLID, 3, RGB(0, 0, 0));
HPEN hOldPen = (HPEN)::SelectObject(hdc, hPen);
建立顏色畫筆,使用此顏色畫筆進行繪製線條
/* 棋盤寬高 */
int width = m_rect.right - m_rect.left;
int height = m_rect.bottom - m_rect.top;
int x_left_top = m_rect.left;
int y_left_top = m_rect.top;
int w_distance = width / 3;
int h_distance = height / 3;
width和height,分別為棋盤的寬度和高度
x_left_top和y_left_top,分別為棋盤的左上角(x, y)
w_distance和h_distance,分別為棋子的寬度和高度
/* "井"四標邊 */
int points[4][4];
定義一個4x4的陣列,用於儲存四條直線的起點和終點座標(x, y)
/* 豎線第一條 */
points[0][0] = x_left_top + w_distance;
points[0][1] = y_left_top;
points[0][2] = x_left_top + w_distance;
points[0][3] = y_left_top + height;
/* 豎線第二條 */
points[1][0] = x_left_top + 2 * w_distance;
points[1][1] = y_left_top;
points[1][2] = x_left_top + 2 * w_distance;
points[1][3] = y_left_top + height;
/* 橫線第一條 */
points[2][0] = x_left_top;
points[2][1] = y_left_top + h_distance;
points[2][2] = x_left_top + width;
points[2][3] = y_left_top + h_distance;
/* 橫線第二條 */
points[3][0] = x_left_top;
points[3][1] = y_left_top + 2 * h_distance;
points[3][2] = x_left_top + width;
points[3][3] = y_left_top + 2 * h_distance;
"井"字四條邊的起點和終點座標(x, y)
Util::DrawLine(hdc, points[0]);
Util::DrawLine(hdc, points[1]);
Util::DrawLine(hdc, points[2]);
Util::DrawLine(hdc, points[3]);
呼叫Util的繪製線段(Util在後面進行講解,此處先為了解)
::SelectObject(hdc, hOldPen);
::DeleteObject(hPen); hPen = NULL;
恢復為先前狀態,銷燬畫筆物件
⑦ Prece::Render (繪製棋子)
void Prece::Render(HDC hdc)
{
/* 繪製標記 */
DrawGraphics(hdc);
}
在繪製棋子中為繪製標記
⑧ Prece::DrawGraphics(繪製棋子標記)
void Prece::DrawGraphics(HDC hdc)
{
/* 判斷棋子是否被玩家點選 */
if (!m_bIsClick)
return;
if (m_click_player == ClickPlayer::ClickPlayer_Player1)
{
/* 繪製玩家1圖形 */
DrawPlayer1Graphics(hdc);
}
else
{
/* 繪製玩家2圖形 */
DrawPlayer2Graphics(hdc);
}
}
拆分進行講解:
/* 判斷棋子是否被玩家點選 */
if (!m_bIsClick)
return;
當棋子沒有被玩家點選,不進行繪製標記(因為只有點選了才會有玩家的標記)
if (m_click_player == ClickPlayer::ClickPlayer_Player1)
{
/* 繪製玩家1圖形 */
DrawPlayer1Graphics(hdc);
}
如果棋子為玩家1點選,則繪製玩家1的標記
else
{
/* 繪製玩家2圖形 */
DrawPlayer2Graphics(hdc);
}
否則為玩家2點選,則繪製玩家2的標記
⑨ Prece::DrawPlayer1Graphics (繪製玩家1標記)
void Prece::DrawPlayer1Graphics(HDC hdc)
{
// 棋子中心點座標
int x_center = m_x + (m_w / 2);
int y_center = m_y + (m_h / 2);
/* 繪製 "×" */
double len = m_w / 3.0;
float angles[] = {
45, 135, 225, 315
};
int points[2][4];
float rad = 3.1415926f / 180.0f;
/* 第一條 */
int x_lt = (int)(x_center + len * cos(angles[0] * rad));
int y_lt = (int)(y_center + len * sin(angles[0] * rad));
int x_rd = (int)(x_center + len * cos(angles[2] * rad));
int y_rd = (int)(y_center + len * sin(angles[2] * rad));
/* 第二條 */
int x_rt = (int)(x_center + len * cos(angles[1] * rad));
int y_rt = (int)(y_center + len * sin(angles[1] * rad));
int x_ld = (int)(x_center + len * cos(angles[3] * rad));
int y_ld = (int)(y_center + len * sin(angles[3] * rad));
points[0][0] = x_lt;
points[0][1] = y_lt;
points[0][2] = x_rd;
points[0][3] = y_rd;
points[1][0] = x_rt;
points[1][1] = y_rt;
points[1][2] = x_ld;
points[1][3] = y_ld;
HPEN hPen = ::CreatePen(PS_SOLID, 3, RGB(153, 77, 82));
HPEN hOldPen = (HPEN)::SelectObject(hdc, hPen);
/* 繪製 */
Util::DrawLine(hdc, points[0]);
Util::DrawLine(hdc, points[1]);
::SelectObject(hdc, hOldPen);
::DeleteObject(hPen); hPen = NULL;
}
拆分進行講解:
// 棋子中心點座標
int x_center = m_x + (m_w / 2);
int y_center = m_y + (m_h / 2);
計算出棋子的中心點座標(x, y)
double len = m_w / 3.0;
表示"×"兩條線段的長度的一半
也就是比如這個"\"為兩條線段其中一條,則此len變量表示這條線段的一半
float angles[] = {
45, 135, 225, 315
};
四個點的角度
int points[2][4];
兩條線段的起點和終點的座標(x, y)
float rad = 3.1415926f / 180.0f;
計算1°相對的弧度
/* 第一條 */
int x_lt = (int)(x_center + len * cos(angles[0] * rad));
int y_lt = (int)(y_center + len * sin(angles[0] * rad));
int x_rd = (int)(x_center + len * cos(angles[2] * rad));
int y_rd = (int)(y_center + len * sin(angles[2] * rad));
/* 第二條 */
int x_rt = (int)(x_center + len * cos(angles[1] * rad));
int y_rt = (int)(y_center + len * sin(angles[1] * rad));
int x_ld = (int)(x_center + len * cos(angles[3] * rad));
int y_ld = (int)(y_center + len * sin(angles[3] * rad));
points[0][0] = x_lt;
points[0][1] = y_lt;
points[0][2] = x_rd;
points[0][3] = y_rd;
points[1][0] = x_rt;
points[1][1] = y_rt;
points[1][2] = x_ld;
points[1][3] = y_ld;
通過三角函式計算出四個點的座標(x, y)
其中lt(left-top)、rt(right-top)、ld(left-down)、rd(right-down)
HPEN hPen = ::CreatePen(PS_SOLID, 3, RGB(153, 77, 82));
HPEN hOldPen = (HPEN)::SelectObject(hdc, hPen);
建立顏色畫筆,並且使用此畫筆
/* 繪製 */
Util::DrawLine(hdc, points[0]);
Util::DrawLine(hdc, points[1]);
進行繪製兩條線段線段
::SelectObject(hdc, hOldPen);
::DeleteObject(hPen); hPen = NULL;
恢復到先前的狀態,並且銷燬畫筆物件
⑩ Prece::DrawPlayer2Graphics (繪製玩家2標記)
void Prece::DrawPlayer2Graphics(HDC hdc)
{
/* 棋子中心點座標 */
int x_center = m_x + (m_w / 2);
int y_center = m_y + (m_h / 2);
/* "○"半徑 */
int r = m_w / 3;
/* 繪製 "○" */
HPEN hPen = ::CreatePen(PS_SOLID, 3, RGB(64, 116, 52));
HPEN hOldPen = (HPEN)::SelectObject(hdc, hPen);
HBRUSH hBrush = (HBRUSH)::GetStockObject(NULL_BRUSH);
HBRUSH bOldBrush = (HBRUSH)::SelectObject(hdc, hBrush);
::Ellipse(hdc, x_center - r, y_center - r, x_center + r, y_center + r);
::SelectObject(hdc, bOldBrush);
::SelectObject(hdc, hOldPen);
::DeleteObject(hBrush); hBrush = NULL;
::DeleteObject(hPen); hPen = NULL;
}
拆分進行講解:
/* 棋子中心點座標 */
int x_center = m_x + (m_w / 2);
int y_center = m_y + (m_h / 2);
計算出棋子的中心座標(x, y)
/* "○"半徑 */
int r = m_w / 3;
定義圓的半徑
HPEN hPen = ::CreatePen(PS_SOLID, 3, RGB(64, 116, 52));
HPEN hOldPen = (HPEN)::SelectObject(hdc, hPen);
HBRUSH hBrush = (HBRUSH)::GetStockObject(NULL_BRUSH);
HBRUSH bOldBrush = (HBRUSH)::SelectObject(hdc, hBrush);
建立顏色畫筆,並選用此畫筆
建立空心畫刷,並選用此畫刷(選用空心畫刷就是讓圓的中心為透明,不填充顏色)
::Ellipse(hdc, x_center - r, y_center - r, x_center + r, y_center + r);
繪製圓
::SelectObject(hdc, bOldBrush);
::SelectObject(hdc, hOldPen);
::DeleteObject(hBrush); hBrush = NULL;
::DeleteObject(hPen); hPen = NULL;
恢復先前的狀態,並銷燬畫筆和畫刷物件
4) Game::CheckGameOver (檢測遊戲是否結束)
void Game::CheckGameOver()
{
/* 獲取棋盤格子 */
Prece** ppPreces = m_board.GetPreces();
/* 獲取玩家點選了的格子 */
int p1[9];
int p2[9];
memset((void*)p1, -1, sizeof(p1));
memset((void*)p2, -1, sizeof(p2));
int index1 = 0;
int index2 = 0;
for (int i = 0, count = 9; i < count; ++i)
{
if (ppPreces[i] != NULL && ppPreces[i]->IsClick())
{
ppPreces[i]->GetClickPlayer() == ClickPlayer::ClickPlayer_Player1 ?
p1[index1++] = ppPreces[i]->GetIndex() :
p2[index2++] = ppPreces[i]->GetIndex();
}
}
/* 不足3個取消比較 */
if (index1 < 3 && index2 < 3)
return;
/* 8種獲勝結果集合 */
int win_set[8][3] = {
{0, 1, 2},
{3, 4, 5},
{6, 7, 8},
{0, 3, 6},
{1, 4, 7},
{2, 5, 8},
{0, 4, 8},
{2, 4, 6}
};
/* 進行比較 */
int nP1Match = 0;
int nP2Match = 0;
for (int i = 0; i < 8; ++i)
{
nP1Match = 0;
nP2Match = 0;
for (int j = 0; j < 3; ++j)
{
for (int k = 0; k < index1; ++k)
{
if (p1[k] == win_set[i][j])
++nP1Match;
else if (p2[k] == win_set[i][j])
++nP2Match;
if (nP1Match == 3)
{
m_over_type = GameOverType::GameOverType_Player1;
m_bIsGameOver = true;
return;
}
else if (nP2Match == 3)
{
m_over_type = GameOverType::GameOverType_Player2;
m_bIsGameOver = true;
return;
}
}
}
}
/* 9個為平局 */
if (index1 + index2 >= 9)
{
m_over_type = GameOverType::GameOverType_Tie;
m_bIsGameOver = true;
}
}
拆分進行講解:
/* 獲取棋盤格子 */
Prece** ppPreces = m_board.GetPreces();
獲取棋盤上的9個棋子
/* 獲取玩家點選了的格子 */
int p1[9];
int p2[9];
memset((void*)p1, -1, sizeof(p1));
memset((void*)p2, -1, sizeof(p2));
定義p1、p2兩個陣列進行儲存 玩家1 和 玩家2 點選的棋子下標,並設定陣列中的元素為-1
int index1 = 0;
int index2 = 0;
for (int i = 0, count = 9; i < count; ++i)
{
if (ppPreces[i] != NULL && ppPreces[i]->IsClick())
{
ppPreces[i]->GetClickPlayer() == ClickPlayer::ClickPlayer_Player1 ?
p1[index1++] = ppPreces[i]->GetIndex() :
p2[index2++] = ppPreces[i]->GetIndex();
}
}
遍歷9個棋子,將點選的棋子儲存到對應的陣列中
/* 不足3個取消比較 */
if (index1 < 3 && index2 < 3)
return;
如果棋子點選數量不足三個,則不進行比較(因為最低也要三個棋子連成一線)
/* 8種獲勝結果集合 */
int win_set[8][3] = {
{0, 1, 2},
{3, 4, 5},
{6, 7, 8},
{0, 3, 6},
{1, 4, 7},
{2, 5, 8},
{0, 4, 8},
{2, 4, 6}
};
列舉八種勝利方式
int nP1Match = 0;
int nP2Match = 0;
定義玩家1和玩家2匹配的數量
for (int i = 0; i < 8; ++i)
{
nP1Match = 0;
nP2Match = 0;
for (int j = 0; j < 3; ++j)
{
for (int k = 0; k < index1; ++k)
{
......
}
}
}
每次進行一種勝利方式匹配前,都重新設定玩家1和玩家2的匹配數為0
變數八種勝利方式,然後遍歷每種裡面的三個棋子下標,
遍歷玩家1和玩家2所下的所有棋子,如果存在此種組合的三個棋子下標則獲勝
if (p1[k] == win_set[i][j])
++nP1Match;
如果玩家1存在此種組合的其中一個下標,則對匹配變數(nP1Match)進行++
else if (p2[k] == win_set[i][j])
++nP2Match;
如果玩家2存在此種組合的其中一個下標,則對匹配變數(nP2Match)進行++
if (nP1Match == 3)
{
m_over_type = GameOverType::GameOverType_Player1;
m_bIsGameOver = true;
return;
}
else if (nP2Match == 3)
{
m_over_type = GameOverType::GameOverType_Player2;
m_bIsGameOver = true;
return;
}
如果匹配數量為3,則為八種獲勝方式中的其中一種方式中的下標全部匹配,那麼此玩家獲勝
設定遊戲結束型別(m_over_type = ···)(在繪製遊戲結束中使用)
並且設定遊戲結束(m_bIsGameOver = true)
/* 9個為平局 */
if (index1 + index2 >= 9)
{
m_over_type = GameOverType::GameOverType_Tie;
m_bIsGameOver = true;
}
如果上面兩個玩家都沒有匹配成功,並且九個棋子都被玩家點選,那麼為平局
設定遊戲結束型別(m_over_type = ···)(在繪製遊戲結束中使用)
並且設定遊戲結束(m_bIsGameOver = true)
5) Util (常用工具)類
是否發現在上面經常使用到了Util這個類呢。
正是因為經常使用,所以避免太多重複程式碼,故新增此類
PS:以後遊戲中會繼續使用此Util,並且不斷進行新增常用的方法
在Util中定義了三個方法,分別是:
static void DrawLine(HDC, int[4]); // 繪製一條直線
static void CreateDoubleBuffer(HWND, HDC &, HBITMAP &); // 建立創緩衝
static void CreateLogFont(HFONT &, int); // 建立邏輯字型
① Util::DrawLine (繪製線段)
void Util::DrawLine(HDC hdc, int points[4])
{
/* *
* int[4] 表示兩個點的 (x, y)
* 第一個點為 (points[0], points[1])
* 第二個點為 (points[2], points[3])
* */
::MoveToEx(hdc, points[0], points[1], NULL);
::LineTo(hdc, points[2], points[3]);
}
通過MoveToEx移動到線段起點,在通過LineTo繪製一條從起點到終點的線段
② Util::CreateDoubleBuffer (建立雙緩衝)
void Util::CreateDoubleBuffer(HWND hWnd, HDC &mdc, HBITMAP &bitmap)
{
/* *
* 建立雙緩衝
* 也就是: 相容DC和相容點陣圖
* */
HDC hdc = ::GetDC(hWnd);
RECT clientRect;
::GetClientRect(hWnd, &clientRect);
mdc = ::CreateCompatibleDC(hdc);
bitmap = ::CreateCompatibleBitmap(hdc, clientRect.right - clientRect.left, clientRect.bottom - clientRect.top);
::ReleaseDC(hWnd, hdc);
}
1. 通過GetDC獲取裝置DC
2. 獲取客戶端矩形大小
3. 通過CreateCompatibleDC建立相容DC
4. 通過CreateCompatibleBitmap建立一張跟客戶端矩形大小的相容點陣圖
5. 最後不要忘了ReleaseDC,釋放獲取的裝置DC
③ Util::CreateLogFont (建立邏輯字型)
void Util::CreateLogFont(HFONT &hFont, int nFontHeight)
{
/* *
* 建立邏輯字型
* */
LOGFONT logfont;
ZeroMemory(&logfont, sizeof(LOGFONT));
logfont.lfCharSet = GB2312_CHARSET;
logfont.lfHeight = nFontHeight;
hFont = ::CreateFontIndirect(&logfont);
}