一、程序说明
基于CSock的远程五子棋对弈程序设计
(1)按Socket异步网络通信方式设计具有C/S模式的数据传输模块;
(2)在服务器与客户端各设计一个五子棋棋盘,用于博弈。
(3)博弈开始双方选定黑白棋子,按规则下子,两端同时显示棋局状态,并计时,
超时者判负。
(4)当任何方向上某一色棋子连成五个,判定该方赢棋。
二、程序界面
三、 程序分析
1.设计思想
(1)五子棋软件即可作为服务器端也可以作为客户端,需要在程序中同时顾及软件作
为服务器或客户端的两种情况。
(2)当五子棋作为服务器开启时,就开始在某个端口(本系统采用了1234端口)下监听客户端的连接请求,再开启另一个五子棋软件,输入服务器的IP地址并请求连接,当服务器响应客户端请求时,就可以开始游戏了。
(3)程序进行CSocket来进行数据交流,传递落子坐标。
(4)设计各种自定义算法,如棋盘棋子绘制、落子距离计算、胜负判断、状态变化等。
2.系统设计步骤
(1)程序基于Visual Studio 2010开发,以“MFC应用程序”作为程序开发模板。利用Windwos平台的消息驱动机制,设定当用户执行了某些操作(如点击按钮)时则触发特定函数。
(2)系统的使用需要同时具备服务器与客户端两个角色,服务器要先于客户端开启。
(3)开启五子棋软件,将其设为服务器。服务器端建立一个CMySocket继承于CSocket,增添自定义变量:关联窗口指针(CWnd *pWnd;),自定义函数:窗口绑定函数(void AttachCWnd(CWnd *pW);),以适应本系统实际情况。Create一个CMySocket对象在某个指定端口下(如本系统使用的1234端口)监听(listen)是否有客户端程序请求连接,套接字类型采用流式套接字(SOCK_STREAM)。调用accept阻塞,等待客户端连接。在这时如果有另一个五子棋软件作为客户端初始化一个Socket请求连接(connect)服务器,服务器对该请求进行应答。如果连接成功,则客户端与服务器端的连接就建立了。
(4)当两个玩家之间(服务器与客户端之间)的连接建立起来后,就可以开始游戏了。程序通过m_status来表示当前软件是作为服务器或客户端而存在。自定义结构体Point来存储玩家的落子坐标,然后利用SOCKET来传递给另一位玩家。此外,因为当玩家落子后,需要等到另一位玩家传递来落子坐标后才可以再次落子,通过canPlayChess来控制这种变化。
//用来标识类型,服务器、未指定、客户端
enum status{SERVER=-1,INITIAL=0,CLIENT=1};
//标明当前状态是客户端还是服务器端,服务器=-1,客户端=1,未指定=0
enum status m_status;
//落子坐标
struct Point{
int x;
int y;
};
//标识当前是否能落子
BOOL canPlayChess;
(5)自定义CSokcet类的以下虚函数,用来处理当“客户端请求连接,玩家关闭游戏、接收消息”等动作。
virtual void OnAccept(int nErrorCode);
virtual void OnClose(int nErrorCode);
virtual void OnReceive(int nErrorCode);
例如当有一方玩家关闭游戏或连接断开时,则
void CMySocket::OnClose(int nErrorCode)
函数将被触发,另一方玩家将弹出对话框进行提示。
四、 系统的具体实现
1. 本系统在Windows8.1系统下基于Visual Studio 2010开发,系统既可以作为服务器与也可以充当客户端。
2. 界面绘制模块:
界面的绘制主要包括窗口界面设置、棋盘绘制、棋子绘制等。主要运用的是CDC。
绘制棋盘代码:
CDC *cdc=GetDC();
CPen di;
di.CreatePen(PS_SOLID,1.3,RGB(0,0,0));
cdc->SelectObject(&di);
//画棋盘横线
for(int i=0;i<ROW;i++)
{
cdc->MoveTo(WIDTH,WIDTH+WIDTH*i);
cdc->LineTo(WIDTH*ROW,WIDTH+WIDTH*i);
}
//画棋盘竖线
for(int j=0;j<ROW;j++)
{
cdc->MoveTo(WIDTH+WIDTH*j,WIDTH);
cdc->LineTo(WIDTH+WIDTH*j,WIDTH*ROW);
}
添加黄色背景代码:
CPaintDC dc(this);
CDC dcMem;
dcMem.CreateCompatibleDC(&dc);
CBitmap bmpBackground;
bmpBackground.LoadBitmap(IDB_BACK);
BITMAP bitmap;
bmpBackground.GetBitmap(&bitmap);
CBitmap* pbmpPri = dcMem.SelectObject(&bmpBackground);
dc.StretchBlt(WIDTH/2,WIDTH/2,WIDTH*(ROW-1)+WIDTH, WIDTH*(ROW-1)+WIDTH, &dcMem,0,0,bitmap.bmWidth, bitmap.bmHeight, SRCCOPY);
棋子绘制代码:
void CgameDlg::drawChessPiece(double a,double b,int m_status)
{
int x=(int)a+1;
int y=(int)b+1;
double width=WIDTH/2-2;
CDC *cdc=GetDC();
CPen di;
di.CreatePen(PS_SOLID,1,RGB(0,0,0));
cdc->SelectObject(&di);//定义画笔
CBrush brush;
//如果当前状态是服务器,则画黑子
if(m_status==SERVER)
{
brush.CreateSolidBrush(RGB(0,0,0));
}else{
//当前状态是客户端,则画白子
brush.CreateSolidBrush(RGB(255,255,255));
}
cdc->SelectObject(&brush);
cdc->Ellipse(x*WIDTH-width,y*WIDTH-width,x*WIDTH+width,y*WIDTH+width;
Points[(int)a][(int)b]=m_status;
}
3.网络设置模块:
下图为网络设置界面。当用户选中“作为服务器”按钮并点击确定后,将Create一个CMySocket对象在1234端口下监听(listen)是否有客户端程序请求连接,套接字类型采用流式套接字(SOCK_STREAM)。调用accept阻塞,等待客户端连接。
这时如果再开启另一个五子棋软件,并选中“作为客户端”按钮,输入服务器的IP地址,点击确定,客户端将初始化一个Socket请求连接(connect)服务器,服务器对该请求进行应答。如果连接成功,则客户端与服务器端的连接就建立了。
void CgameDlg::OnBnClickedSet()
{
// TODO: 在此添加控件通知处理程序代码
CSet cset;
//如果没有按确认键则退出程序
if(cset.DoModal() != IDOK)
{
return;
}
//作为服务器
if(!cset.isServer)
{
if(IDOK==AfxMessageBox("确定开始侦听客户端的连接请求吗",MB_OKCANCEL))
{
m_socket.AttachCWnd(this);
//端口1234,流式套接字
BOOL isSuccess=m_socket.Create(1234,SOCK_STREAM);
if(isSuccess){
m_socket.Listen();
MessageBox("已开始侦听");
SetWindowText("五子棋——正在等待客户端连接");
return;
}
WORD error=m_socket.GetLastError();
CString s;
s.Format("服务器开启失败\r\n错误代代码:%d",error);
MessageBox(s);
}
}else{
//作为客户端
if(IDOK==AfxMessageBox("确定连接到服务器吗",MB_OKCANCEL))
{
SetWindowText("五子棋——正在连接服务器");
CString msg;
DWORD error;
m_socket.AttachCWnd(this);
if(!m_socket.Create())
{
error = GetLastError();
msg.Format("创建Socket失败\r\n错误代号:%d",error);
goto msgbox;
}
//连接服务器
if(!m_socket.Connect(cset.m_ip,1234))
{
error = GetLastError();
msg.Format("连接服务器失败\r\n错误代号:%d",error);
msgbox:
MessageBox(msg);
return;
}
m_status=CLIENT;
MessageBox("已连接好友,可以开始游戏啦!");
SetWindowText("五子棋(白子)——等待对方下子");
canPlayChess=false;
//开启定时任务
SetTimer(1,1000,NULL);
m_hint="对方剩余思考时间:";
}
}
}
4.网络通讯模块:
当用户鼠标点击时,将触发OnLButtonDown(UINT nFlags, CPoint point)函数,由参数point可以得到鼠标点击的X轴和Y轴坐标值。然后再来根据judge(double &x,double &y)函数来获取该坐标值该对应棋盘中的哪一个点,在该点绘制棋子,然后再向另一位玩家发送该坐标信息,或者是判定该点击无效。
void CgameDlg::OnLButtonDown(UINT nFlags, CPoint point)
{
// TODO: 在此添加消息处理程序代码和/或调用默认值
if(!canPlayChess || m_status==INITIAL)
{
return;
}
long x=point.x;
long y=point.y;
if(x>=WIDTH && x<=WIDTH*ROW && y>=WIDTH && y<=WIDTH*ROW)
{
double a=x/(WIDTH*1.0)-1;
double b=y/(WIDTH*1.0)-1;
if(judge(a,b))
{
if(Points[(int)a][(int)b]!=INITIAL)
{
return;
}
drawChessPiece(a,b,m_status);
point.x=a;
point.y=b;
if(m_status==SERVER)
{
if(!sc->Send((char *)&point,sizeof(point)))
{
MessageBox("发送数据失败");
return;
}
}else if(m_status==CLIENT){
if(!m_socket.Send((char *)&point,sizeof(point)))
{
MessageBox("发送数据失败");
return;
}
}
if(!isWin(m_status))
{
if(m_status==SERVER)
{
SetWindowText("五子棋(黑子)——等待对方下子");
}else{
SetWindowText("五子棋(白子)——等待对方下子");
}
canPlayChess=false;
m_hint="对方剩余思考时间:";
THINK_TIME=31;
}
}
}
}
5.倒计时模块
用于设置一个定时任务,动态改变一个静态文本的内容,从而实现倒计时的效果。在需要的时候可以调用KillTimer(1);取消定时任务。
void CgameDlg::OnTimer(UINT_PTR nIDEvent)
{
// TODO: 在此添加消息处理程序代码和/或调用默认值
if(nIDEvent==1)
{
THINK_TIME--;
if(THINK_TIME==0)
{
if(m_status==SERVER)
{
if(canPlayChess)
{
SetWindowText("五子棋(黑子)——思考时间超时,对方获胜");
}else{
SetWindowText("五子棋(黑子)——对方思考时间超时,你已获胜");
}
}else{
if(canPlayChess)
{
SetWindowText("五子棋(白子)——思考时间超时,对方获胜");
}else{
SetWindowText("五子棋(白子)——对方思考时间超时,你已获胜");
}
}
canPlayChess=false;
KillTimer(1);
}
m_thinkTime.Format("%d",THINK_TIME);
UpdateData(FALSE);
}
DialogEx::OnTimer(nIDEvent);
}
代码下载地址,包括实验报告:基于CSock的远程五子棋对弈程序