1. 程式人生 > >Win32 遊戲開發:TicTacToe(井字遊戲) 下篇

Win32 遊戲開發:TicTacToe(井字遊戲) 下篇

上篇進行講解了遊戲的規則、介面設計、遊戲流程、······

下面我們繼續碼程式碼吧,(#^.^#)

在寫程式碼前先說之前所漏掉的兩個列舉,分別是:

① 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);
}

1. 定義一個邏輯字型

2. 設定邏輯字型的字符集為GB_2312(詳細見GB_2312百度百科)

3. 設定邏輯字型的高度為引數的高度

4. 建立此邏輯字型

ヽ( ̄▽ ̄)و,井字遊戲就此介紹完畢啦,下面貼遊戲圖吧,(#^.^#)

井字遊戲執行圖井字遊戲結束圖

原始碼:

避免有些沒有C幣的小夥伴(就像我),所以將原始碼上傳到GitHub啦~

PS:以後遊戲的原始碼都會放入此GitHub網址中哦~