上一章我们实现了棋盘的绘制,现在来实现用鼠标下棋的功能吧。
首先添加一个Engine类,然后添加若干方法和成员,代码如下:
#ifndef FIVE_ENGINE_H
#define FIVE_ENGINE_H
#include <vector>
using namespacestd;
// 游戏状态
enum GAME_STATUS
{
GAME_READY = 0, // 游戏准备
GAME_WAITING, // 电脑等待中
GAME_THINKING, // 电脑思考中
GAME_OVER // 游戏结束
};
// 棋盘交叉点属性
enum CHESS_TYPE
{
CHESS_SPACE = 0, // 无棋子
CHESS_BLACK, // 此处有黑子
CHESS_WHITE // 此处有白子
};
// 棋盘交叉点结构
struct CHESS_POINT
{
int x; // 水平方向位置
int y; // 垂直方向位置
int type; // 棋子类型
};
class Engine
{
public:
Engine(void);
~Engine(void);
void Init();
void StartGame();
// 用户落子
BOOL UserDown(int x,inty);
vector<CHESS_POINT*>getChessList() {returnm_chessList;}
private:
int m_gameState; // 游戏状态
int m_userType; // 用户持子类型 1表示持黑子 2表示持白子
int m_AIType; // 电脑持子类型
CHESS_POINT m_chessTable[15][15]; // 棋盘状态
vector<CHESS_POINT*>m_chessList; // 已落子列表
};
#endif
以上基本满足了对棋盘和棋子状态的保存。接下来就要添加鼠标事件来完成落子了。
切换到资源视图,右键对话框,选择属性,右侧面板会出现属性框,点击消息,添加WM_LBUTTONUP事件,然后添加相应的处理代码:
void CFiveDlg::OnLButtonUp(UINTnFlags,CPointpoint)
{
// TODO: 在此添加消息处理程序代码和/或调用默认值
int x= (point.x+m_blockSize / 2 -m_left)/m_blockSize;
int y= (point.y+m_blockSize / 2 -m_top)/m_blockSize;
if (m_engine->UserDown(x,y))
{
// 重绘
Invalidate();
}
CDialog::OnLButtonUp(nFlags,point);
}
Engine::UserDown的实现如下:
BOOL Engine::UserDown(intx,inty )
{
// 不在棋盘上,返回
if ((x> 14) || (x < 0) || (y > 14) || (y< 0))
{
return FALSE;
}
// 该位置有子,返回
if (m_chessTable[x][y].type !=CHESS_SPACE)
{
return FALSE;
}
m_chessTable[x][y].type =m_userType;
m_chessList.push_back(&m_chessTable[x][y]);
return TRUE;
}
绘制棋子的函数要做相应的修改:
void CFiveDlg::DrawChess(Graphics *g)
{
if ((m_whiteImg==NULL) || (m_blackImg==NULL))
{
return;
}
int chessLeft= m_left - m_blockSize/ 2;
int chessTop= m_top - m_blockSize/ 2;
int chessSize= m_blockSize;
// 从m_engine中获取棋子信息,然后一个一个绘制
vector<CHESS_POINT*>chessList =m_engine->getChessList();
vector<CHESS_POINT*>::iteratoritor =chessList.begin();
while (itor!=chessList.end())
{
if ((*itor)->type ==CHESS_WHITE)
{
g->DrawImage(m_whiteImg,chessLeft + (*itor)->x * m_blockSize,chessTop + (*itor)->y *m_blockSize,chessSize,chessSize);
}
else if((*itor)->type=CHESS_BLACK)
{
g->DrawImage(m_blackImg,chessLeft + (*itor)->x * m_blockSize,chessTop + (*itor)->y *m_blockSize,chessSize,chessSize);
}
itor++;
}
}
做好上面的一切后运行程序,看看效果吧。
看,已经能够用鼠标下棋了。可能有同学要问了,怎么都是黑子啊?不急不急,电脑现在智商为零,还不会下棋呢,后面我会让它越来越聪明的,人机对弈的日子不太遥远。
有一个问题,在运行时我发现每下一颗子,画面重绘时会有闪烁现象,我是绝对不能容忍这种伤眼睛的问题存在的,明天重点解决这个问题,不知道有没有高手能够提供解决方法。
继续昨天的问题,大家应该都想到了用双缓存就可以解决闪烁的问题,下面我对自己的绘制部分代码做下修改。
// 建立一块虚拟画布Bitmap bmp(m_width, m_height);
Graphics bmpGraphics(&bmp);
// 绘制棋盘
DrawChessBoard(&bmpGraphics);
// 绘制棋子
DrawChess(&bmpGraphics);
// 下面是在对话框上绘图了
CPaintDC dc(this);
Graphics graphics(dc.m_hDC);
// 建立一个CacheBitmap用于快速绘图
CachedBitmap cachedBmp(&bmp, &graphics);
graphics.DrawCachedBitmap(&cachedBmp,0,0);
试一下效果,还是有闪烁现象。为了解决这个问题,在网上找了许多资料,经大神指点知道了原来调用Invalidate()之后会发出两个消息WM_ERASEBKGND和WM_PAINT。WM_ERASEBKGND消息会擦除背景,先擦除后描画导致了闪烁现象。解决方法是截获WM_ERASEBKGND,代码如下:
BOOL CFiveDlg::OnEraseBkgnd(CDC* pDC)
{
// TODO: 在此添加消息处理程序代码和/或调用默认值
//return CDialog::OnEraseBkgnd(pDC);
return TRUE;
}
再试一下效果,真的一点都不闪了!!!除此之外,调用Invalidate(FALSE)进行重绘的效果也一样。