上篇进行讲解了游戏的规则、界面设计、游戏流程、······
下面我们继续码代码吧,(#^.^#)
在写代码前先说之前所漏掉的两个枚举,分别是:
① 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. 定义一个逻辑字体
3. 设置逻辑字体的高度为参数的高度
4. 创建此逻辑字体
ヽ( ̄▽ ̄)و,井字游戏就此介绍完毕啦,下面贴游戏图吧,(#^.^#)
![井字游戏运行图](https://i-blog.csdnimg.cn/blog_migrate/ab37f4827584a5c6ecece9e65de7dca4.png)
![井字游戏结束图](https://i-blog.csdnimg.cn/blog_migrate/8c86e212799f1254fa74ca0c262bdf97.png)
源代码:
避免有些没有C币的小伙伴(就像我),所以将源代码放到Github啦~