五子棋游戏详细设计的目标是为了描述,如何实现五子棋游戏项目需求分析得到的客户功能需求。不仅如此,制作详细设计的目标还是为了指导后续的编码工作。明确的说,就是将某一位软件设计师的设计拿给不同的程序员去编码,写出来的程序处理流程相同。这样可以使后续的评审或者当其他人接替这项工作时,只要查看详细设计文档,就能明白软件设计师当时是怎样设计这些流程的。 1-2:五子棋游戏功能结构及名称定义
在详细的的设计中,将用一系列图表列出五子棋游戏的软件系统内的每个程序(包括每个类和函数)的名称、标识符及其之间的层次关系。
五子棋游戏中各个类的名称及关系。如图:
图:五子棋游戏功能结构汇总
① 游戏规则类:主要负责游戏规则的实现;
② 棋盘窗口类:主要负责棋盘和棋子等的更新和显示;;
③ 设置对话框类:主要负责服务器及客户端参数的设置与连接; ④ 网络通信类:主要负责游戏的网络通信;
⑤ 网络协议类:主要负责游戏网络通信协议的实现。 2-1网络通信协议的设计
分析五子棋游戏需求后可以得出,网络传输的数据有如下3种类型:
① 戏中棋子在棋盘中的坐标,即棋子的位置; ② 游戏中的控制信息,例如“和棋”;
③ 其他扩展信息,例如,以后游戏中如果需要增加聊天功能等。
为了能适应这3中不同种类信息的传输,所以在游戏的网络传输协议中,必须加入一个网络协议包结构。其数据域格式如图所示:
网络协议结构体
注意其中数据长度是包含本身2个字节的。 2-2各种数据类型的详细格式 (1) 棋子位置数据包
棋子位置数据包是网络传输中的主要数据,在这里把棋盘当成二维数组。所以在网络中实际传输的是棋子在二维数组中的行列号。其详细格式如下表:
(2) 游戏控制数据中的“和棋”数据包
游戏控制数据中的“和棋”数据包是指游戏中进行“和棋”操作时,向对方发送的网络传输数据包。 ① 和棋请求”数据格式如表:
② “同意和棋回应”数据格式如表:
③ “拒绝和棋回应”数据格式如表:
(3) 其他扩展数据
其他扩展信息,是留给以后游戏扩展使用的,其数据格式如表:
2-3网络通信协议的实现
为了满足网络通信的需要,已经制定了各种通信需要的数据结构,下面用C++代码实现这些数据结构。代码如下:
通信需要使用的数据类型定义
#ifndef__CONNECT_DATA_H_ #define __CONNECT_DATA_H_ //落子消息
#define MSG_PUTSTEP 0X01 //和棋请求消息
#define MSG_DRAW 0x02 //同意和棋消息
#define MSG_AGREE_DRAW 0X03 //拒绝和棋消息
#define MSG_REFUSE_DRAW 0X04 //其他消息
#define MSG_EXTEND_DRAW 0X05 typedefstruct _tagMsgStruct {
USHORT len;
BYTE msgType;//消息ID int x;//落子消息 int y;
int color;
BYTE byMsg[128];//其他消息内容 }MSGSTRUCT;20 #endif
3-1控制菜单的设计
优秀的交互界面是游戏提高满意度的一种方式。在Windows操作系统中交互界面分为对话框和菜单两种,在游戏设计上也是如此。交互界面的友好性是决定游戏能否吸引用户的要素之一。 经过前面的需求分析后,在五子棋游戏中,有如下3种操作命令需要使用菜单来支持。
① 新游戏操作,当用户进行主界面后,需要这个菜单项来开始新的游戏。开始新游戏之前需要对网络进行相应的设置。 ② 和棋操作,在游戏中由一方用户提出用于提前结束游戏,相当于提出方认输,并重新开始新一轮的游戏。
③ 退出游戏操作,在当前用户不需要再玩游戏时,直接退出整个游戏界面。 开发的这款五子棋游戏由于是在Windows上运行的。为了保持与系统界面的一致性,这里采用标准Windows菜单进行设计,如图:
3-2在五子棋的项目(FiveChess)的资源中加入一个菜单栏(IDR_MAIN_MENU),并按表设置菜单项的ID号。选中UPDATE_COMMAND_UI选项
菜单ID资源 ID ID_NEW_GAME_MENU ID_DRAW_GAME_MENU ID_EXIT_GEME_MENU 菜单响应函数
// FiveChessDlg.h : header file类声明头文件 #if !defined(AFX_FIVECHESSDLG_H__) #define AFX_FIVECHESSDLG_H__
//////////////////////////////////////////////////////////////////////////// // CFiveChessDlg dialog
classCFiveChessDlg : public CDialog {
// Construction public: CFiveChessDlg(CWnd* pParent = NULL); // standard constructor构造函数
// Dialog Data //{{AFX_DATA(CFiveChessDlg) enum { IDD = IDD_FIVECHESS_DIALOG };//资源与类连接 // NOTE: the ClassWizard will add data members here //}}AFX_DATA // ClassWizard generated virtual function overrides //{{AFX_VIRTUAL(CFiveChessDlg) protected: virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support //}}AFX_VIRTUAL
// Implementation protected: HICON m_hIcon;//图标对像 ////////////////////////////////////////////////////////////////////////// CMenum_main_menu;//主菜单对象 ////////////////////////////////////////////////////////////////////////////// // Generated message map functions //{{AFX_MSG(CFiveChessDlg) virtual BOOL OnInitDialog();//初始化函数声明 afx_msg void OnSysCommand(UINT nID, LPARAM lParam); afx_msg void OnPaint();//绘图函数声明 afx_msg HCURSOR OnQueryDragIcon();
菜单名称 新游戏 和棋 退出游戏 afx_msg void OnUpdateNewGameMenu(CCmdUI* pCmdUI);//菜单栏响应函数声明 afx_msg void OnUpdateDrawGameMenu(CCmdUI* pCmdUI); voidOnUpdateExitGameMenu(CCmdUI* pCmdUI); //}}AFX_MSG DECLARE_MESSAGE_MAP() };
//{{AFX_INSERT_LOCATION}}
// Microsoft Visual C++ will insert additional declarations immediately before the previous line.
#endif
// !defined(AFX_FIVECHESSDLG_H__8E94850A_C6_43A4_AD76_FFD02CBA8AB7__INCLUDED_)
//FiveChessDlg.cpp实现文件开始
// FiveChessDlg.cpp : implementation file //
#include \"stdafx.h\"//插入头文件 #include \"FiveChess.h\" #include \"FiveChessDlg.h\"
//////////////////////////////////////////////////////////////////////////// // CAboutDlg dialog used for App About
class CAboutDlg : public CDialog {
public: CAboutDlg();
// Dialog Data //{{AFX_DATA(CAboutDlg) enum { IDD = IDD_ABOUTBOX }; //}}AFX_DATA // ClassWizard generated virtual function overrides //{{AFX_VIRTUAL(CAboutDlg) protected: virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support //}}AFX_VIRTUAL
// Implementation protected: //{{AFX_MSG(CAboutDlg)
//}}AFX_MSG DECLARE_MESSAGE_MAP() };
CAboutDlg::CAboutDlg() : CDialog(CAboutDlg::IDD) { //{{AFX_DATA_INIT(CAboutDlg) //}}AFX_DATA_INIT }
void CAboutDlg::DoDataExchange(CDataExchange* pDX) { CDialog::DoDataExchange(pDX); //{{AFX_DATA_MAP(CAboutDlg) //}}AFX_DATA_MAP }
BEGIN_MESSAGE_MAP(CAboutDlg, CDialog) //{{AFX_MSG_MAP(CAboutDlg) // No message handlers //}}AFX_MSG_MAP END_MESSAGE_MAP()
///////////////////////////////////////////////////////////////////////////// // CFiveChessDlg dialog
CFiveChessDlg::CFiveChessDlg(CWnd* pParent /*=NULL*/) : CDialog(CFiveChessDlg::IDD, pParent) { //{{AFX_DATA_INIT(CFiveChessDlg) // NOTE: the ClassWizard will add member initialization here //}}AFX_DATA_INIT // Note that LoadIcon does not require a subsequent DestroyIcon in Win32 m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);//加载图标资源 }
void CFiveChessDlg::DoDataExchange(CDataExchange* pDX) { CDialog::DoDataExchange(pDX); //{{AFX_DATA_MAP(CFiveChessDlg)调用父类的数据家在函数 // NOTE: the ClassWizard will add DDX and DDV calls here //}}AFX_DATA_MAP }
BEGIN_MESSAGE_MAP(CFiveChessDlg, CDialog)//响应函数映射关系表 //{{AFX_MSG_MAP(CFiveChessDlg) ON_WM_SYSCOMMAND() ON_WM_PAINT()//绘图函数声明 ON_WM_QUERYDRAGICON() ON_UPDATE_COMMAND_UI(ID_NEW_GAME_MENU, OnUpdateNewGameMenu)//NEW_GAME应设函数关系 ON_UPDATE_COMMAND_UI(ID_DRAW_GAME_MENU, OnUpdateDrawGameMenu)//DRAW_GAME应设函数关系 ON_UPDATE_COMMAND_UI(ID_EXIT_GAME_MENU, OnUpdateExitGameMenu)//EXIT_GAME应设函数关系 //}}AFX_MSG_MAP END_MESSAGE_MAP()
///////////////////////////////////////////////////////////////////////////// // CFiveChessDlg message handlers
BOOL CFiveChessDlg::OnInitDialog()//初始化函数实现 { CDialog::OnInitDialog();//调用父类的初始化函数 // Add \"About...\" menu item to system menu. // IDM_ABOUTBOX must be in the system command range. ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX); ASSERT(IDM_ABOUTBOX < 0xF000); CMenu* pSysMenu = GetSystemMenu(FALSE);//加载系统菜单 if (pSysMenu != NULL) { CStringstrAboutMenu; strAboutMenu.LoadString(IDS_ABOUTBOX); if (!strAboutMenu.IsEmpty()) { pSysMenu->AppendMenu(MF_SEPARATOR); pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu); } } // Set the icon for this dialog. The framework does this automatically // when the application's main window is not a dialog SetIcon(m_hIcon, TRUE); // Set big icon设置大图标 SetIcon(m_hIcon, FALSE); // Set small icon设置小图标
// TODO: Add extra initialization here /////////////////////////////////////////////////////////////////////////////////////////// m_main_menu.LoadMenu(IDR_MAIN_MENU);//加载主菜单资源 SetMenu(&m_main_menu);//设置主菜单 /////////////////////////////////////////////////////////////////////////////////////////// return TRUE; // return TRUE unless you set the focus to a control }
void CFiveChessDlg::OnSysCommand(UINT nID, LPARAM lParam) { if ((nID& 0xFFF0) == IDM_ABOUTBOX) { CAboutDlgdlgAbout; dlgAbout.DoModal(); } else { CDialog::OnSysCommand(nID, lParam); } }
// If you add a minimize button to your dialog, you will need the code below // to draw the icon. For MFC applications using the document/view model, // this is automatically done for you by the framework.
void CFiveChessDlg::OnPaint() { if (IsIconic()) { CPaintDC dc(this); // device context for painting SendMessage(WM_ICONERASEBKGND, (WPARAM) dc.GetSafeHdc(), 0); // Center icon in client rectangle intcxIcon = GetSystemMetrics(SM_CXICON); intcyIcon = GetSystemMetrics(SM_CYICON); CRectrect; GetClientRect(&rect); int x = (rect.Width() - cxIcon + 1) / 2;
int y = (rect.Height() - cyIcon + 1) / 2; // Draw the icon dc.DrawIcon(x, y, m_hIcon); } else { CDialog::OnPaint(); } }
// The system calls this to obtain the cursor to display while the user drags // the minimized window.
HCURSOR CFiveChessDlg::OnQueryDragIcon() { return (HCURSOR) m_hIcon; }
void CFiveChessDlg::OnUpdateNewGameMenu(CCmdUI* pCmdUI) //NEW_GAME响应函数实现 { // TODO: Add your command update UI handler code here if(IDOK==m_setup_dlg.DoModal())//调用网络设置对话框 { NewGameStart(m_setup_dlg.m_isHost);//单击确定按钮,调用开始新游戏函数 } }
void CFiveChessDlg::OnUpdateDrawGameMenu(CCmdUI* pCmdUI) //DRAW_GAME响应函数实现 { // TODO: Add your command update UI handler code here DrawGame();//调用和棋函数 }
void CFiveChessDlg::OnUpdateExitGameMenu(CCmdUI* pCmdUI)//EXIT_GAME响应函数实现 { // TODO: Add your command update UI handler code here SetMenu(NULL);//清空主菜单对象 CDialog::OnCancel();//调用父类对话框的退出函数 }
技巧:在基于对话框模式的MFC程序中,系统不会自动加载菜单栏,需要
用户动态的加载。
3-3网络设置对话框的设计
从需求分析得知,当用户开始新游戏时,需要对网络进行设置。其包含的内容如下:
① 可以选择当前用户是主机,还是客户机。如果是主机则执黑子,如是客户机则执白子;
② 但用户选择为主机时,IP地址默认为“127.0.0.1”,端口号由用户填写; ③ 当用户选择为客户机时,则需要再设置连接到的主机IP地址及端口号; ④ 如果前面已经设置过,则重新单击“新游戏”选项时,把上一次设置的IP地址和端口号显示出来。
为了满足上面的要求,笔者设置了一个对话框界面如图所示,并且把这个对话框资源同CSetupDlg类相关联起来。同时,因为在退出游戏后,还要把上一次设置的记录进行保存,多以程序中还可以操作文件。为了保存方便,这里笔者使用了INI系统配置文件来完成相关任务。
网络设置对话框的资源ID及名称 ID 显示名称 资源类型 IDC_HOST_OPTION 主机 单选项 IDC_CLIENT_OPTION 客户机 单选项 IDC_IP_ADDRESS_EDIT 无 IP地址编辑框 IDC_NET_PORT_EDIT 无 文本编辑框 IDOK 确认 按钮 IDCANCLE 取消 按钮 IDC_STATIC 主机类型选择 静态标签 3-4网络设置对话框的实现 根据前面要去设置完相关ID资源和名称后,就需要使用类向导来添加响应函数和成员变量。对话框实现代码如下:
网络设置对话框的实现
support
#if !defined(AFX_SETUPDLG_H__) #define AFX_SETUPDLG_H__
///////////////////////////////////////////////////////////////// // CSetupDlg dialog
class CSetupDlg : public CDialog//声明设置对话框类 {
// Construction public:
CSetupDlg(CWnd* pParent = NULL); // standard constructor
// Dialog Data
//{{AFX_DATA(CSetupDlg)
enum { IDD = IDD_SETUP_DIALOG };
CIPAddressCtrl m_ip_addr;//IP地址输入编辑框控件对象 UINT m_net_port;//端口号 //}}AFX_DATA
// Overrides
// ClassWizard generated virtual function overrides //{{AFX_VIRTUAL(CSetupDlg) protected:
virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV
//}}AFX_VIRTUAL
// Implementation
protected:
// Generated message map functions消息与响应函数映射关系 //{{AFX_MSG(CSetupDlg)
afx_msg void OnHostOption(); afx_msg void OnClientOption(); virtual void OnOK(); virtual void OnCancel(); //}}AFX_MSG public:
BOOL m_isHost;//主机类型,TRUE:主机 FALSE:客户机 DECLARE_MESSAGE_MAP() };
//{{AFX_INSERT_LOCATION}}
// Microsoft Visual C++ will insert additional declarations immediately
before the previous line.
#endif
// !defined(AFX_SETUPDLG_H__DBCA109B_4C0F_4DFF_B8_DFAF37B1EC51__INCLUDED_)
// SetupDlg.cpp : implementation file //
#include \"stdafx.h\"//插入头文件 #include \"FiveChess.h\"
#include \"SetupDlg.h\"//插入类声明头文件
///////////////////////////////////////////////////////////////////////////// // CSetupDlg dialog
CSetupDlg::CSetupDlg(CWnd* pParent /*=NULL*/) : CDialog(CSetupDlg::IDD, pParent) { //{{AFX_DATA_INIT(CSetupDlg) // NOTE: the ClassWizard will add member initialization here m_net_port=0;//³õʼ»¯³ÉÔ± //}}AFX_DATA_INIT }
voidCSetupDlg::DoDataExchange(CDataExchange* pDX) { CDialog::DoDataExchange(pDX);//³ÉÔ±Óë¿Ø¼þ¹ØÁª //{{AFX_DATA_MAP(CSetupDlg) DDX_Control(pDX, IDC_IPADDRESS1, m_ip_addr); DDX_Text(pDX,IDC_NET_PORT_EDIT,m_net_port); DDV_MinMaxUInt(pDX, m_net_port,1,65000); //}}AFX_DATA_MAP }
BEGIN_MESSAGE_MAP(CSetupDlg, CDialog)//ÏìÓ¦º¯ÊýÓë¿Ø¼þ¹ØÁª //{{AFX_MSG_MAP(CSetupDlg) ON_BN_CLICKED(IDC_HOST_OPTION, OnHostOption) ON_BN_CLICKED(IDC_CLIENT_OPTION, OnClientOption)
//}}AFX_MSG_MAP END_MESSAGE_MAP()
///////////////////////////////////////////////////////////////////////////// // CSetupDlg message handlers
voidCSetupDlg::OnHostOption()//ÏìӦѡÖÐÖ÷»úÑ¡Ïî { // TODO: Add your control notification handler code here charstr[128]={0}; m_ip_addr.EnableWindow(FALSE); //ʹIPÊäÈë±à¼-¿òÎÞЧ GetPrivateProfileString(\"CLIENT\ m_ip_addr.SetWindowText(str); memset(str,0,128); GetPrivateProfileString(\"HOST\ m_net_port=atoi(str); m_isHost=TRUE; UpdateData(FALSE);//°Ñ±ä¸üÊý¾Ý¸üÐÂÏÔʾ }
voidCSetupDlg::OnClientOption() //ÏìӦѡÖпͻ§»úÑ¡Ïî { // TODO: Add your control notification handler code here charstr[128]={0}; m_ip_addr.EnableWindow(TRUE); GetPrivateProfileString(\"CLIENT\ÅäÖÃÎļþÖпͻ§»úIPµØÖ· m_ip_addr.SetWindowText(str); memset(str,0,128); GetPrivateProfileString(\"CLIENT\ÅäÖÃÎļþÖпͻ§»ú¶Ë¿ÚºÅ m_net_port=atoi(str); m_isHost=FALSE;//ÉèÖÃÁ¬½ÓÀàÐͱäÁ¿ UpdateData(FALSE);//°Ñ±ä¸üÊý¾Ý¸üÐÂÏÔʾ }
voidCSetupDlg::OnOK() //µ¥»÷È·Èϰ´Å¥ÏìÓ¦ { // TODO: Add extra validation here CStringstrIP;//¶¨ÒåÁÙʱ×Ö·û´®±äÁ¿ CStringstrPort; UpdateData(TRUE);//¸üÐÂÏÔʾµ½±äÁ¿ m_ip_addr.GetWindowText(strIP);//µÃµ½IPµØÖ·±à¼-¿òÖÐÊäÈëµÄ×Ö·û strPort.Format(\"%d\ñʽ»¯¶Ë¿ÚºÅΪ×Ö·û´®
if(m_isHost) { WritePrivateProfileString(\"HOST\ÑÖ÷»úµÄPORTдÈëÅäÖÃÎļþ } else//°Ñ¿Í»§¼°µÄIPºÍPORTдÈëÅäÖÃÎļþ { WritePrivateProfileString(\"CLIENT\ WritePrivateProfileString(\"CLIENT\ } CDialog::OnOK();//µ÷Óø¸Àà³ÉÔ±º¯Êý }
voidCSetupDlg::OnCancel() //µ¥»÷È¡Ïû°´Å¥ÏìÓ¦º¯Êý { // TODO: Add extra cleanup here CDialog::OnCancel();//µ÷Óø¸Àà³ÉÔ±º¯Êý }
BOOL CSetupDlg::OnInitDialog() //³õʼ»¯¶Ô»°¿òº¯Êý { CDialog::OnInitDialog(); // TODO: Add extra initialization here ((CButton*)GetDlgItem(IDC_HOST_OPTION))->SetCheck(1);//ÉèÖÃĬÈÏΪѡÖÐÖ÷»úÑ¡Ïî OnHostOption(); return TRUE; // return TRUE unless you set the focus to a control·µ»ØÕ棬±íʾ³õʼ»¯³É¹¦ // EXCEPTION: OCX Property Pages should return FALSE }
通过上面的代码可以知道,当用户单击设置对话框中的确定按钮中,就会把IP和端口号写入到config.ini配置文件中。在下一次用户再进入程序时,又会读取配置文件中的数据填充到对话框显示中,这样就实现了保存设置的功能。
因篇幅问题不能全部显示,请点此查看更多更全内容
Copyright © 2019- oldu.cn 版权所有 浙ICP备2024123271号-1
违法及侵权请联系:TEL:199 1889 7713 E-MAIL:2724546146@qq.com
本站由北京市万商天勤律师事务所王兴未律师提供法律服务