[游戏学习22] MFC 井字棋 双人对战
>_<:太多啦,感觉用英语说的太慢啦,没想到一年做的东西竟然这么多.....接下来要加速啦!
>_<:注意这里必须用MFC和前面的Win32不一样啦!
>_<:这也是第一次出现MFC游戏,其框架和逻辑的写法和Win32有很大的区别,建议先看一下MFC的基础再理解代码:
>_<:TicTac.h
#define EX 1 //该点左鼠标
#define OH 2 //该点右鼠标 class CMyApp : public CWinApp
{
public:
virtual BOOL InitInstance ();
}; class CMainWindow : public CWnd //不是继承CFrameWnd 因此需要在CMainWindow()自己定义窗口类了
{
protected:
static const CRect m_rcSquares[]; // Grid coordinates
int m_nGameGrid[]; // 9个格子的状态是否被下0没下;1左下了;2右下了
int m_nNextChar; // 下一个鼠标状态左or右 (EX or OH)
bool ptab[][]; //玩家的获胜的状态表
bool ctab[][]; //电脑的获胜的状态表
int win[][]; //每种状态表里的棋子数 int GetRectID (CPoint point);
void DrawBoard (CDC* pDC);
void DrawX (CDC* pDC, int nPos);
void DrawO (CDC* pDC, int nPos);
void CpDraw(CDC* pDC);
void InitGame();
void out();
void ResetGame ();
bool CheckForGameOver ();
int IsWinner ();
BOOL IsDraw (); public:
CMainWindow (); protected:
virtual void PostNcDestroy ();//在程序终止之前销毁CMainWindow对象 afx_msg void OnPaint ();
afx_msg void OnLButtonDown (UINT nFlags, CPoint point);
afx_msg void OnLButtonDblClk (UINT nFlags, CPoint point);
afx_msg void OnRButtonDown (UINT nFlags, CPoint point); DECLARE_MESSAGE_MAP ()
};
>_<:TicTac.cpp
#include <afxwin.h>
#include "TicTac.h"
#include <fstream>
#include <iostream>
#include<iomanip>
using namespace std;
CMyApp myApp;
/*ofstream Cout("out.txt");
void CMainWindow::out(){
Cout<<"ptab[][]=:\n";
for(int i=0;i<9;i++){
for(int j=0;j<8;j++)
Cout<<setw(3)<<ptab[i][j]<<' ';
Cout<<'\n';
}
Cout<<"ctab[][]=:\n";
for(int i=0;i<9;i++){
for(int j=0;j<8;j++)
Cout<<setw(3)<<ctab[i][j]<<' ';
Cout<<'\n';
}
Cout<<"win[][]=:\n";
for(int i=0;i<2;i++){
for(int j=0;j<8;j++)
Cout<<setw(3)<<win[i][j]<<' ';
Cout<<'\n';
}
}*/
/////////////////////////////////////////////////////////////////////////
// CMyApp member functions BOOL CMyApp::InitInstance ()
{
m_pMainWnd = new CMainWindow;
m_pMainWnd->ShowWindow (m_nCmdShow);
m_pMainWnd->UpdateWindow ();
return TRUE;
} /////////////////////////////////////////////////////////////////////////
// CMainWindow message map and member functions BEGIN_MESSAGE_MAP (CMainWindow, CWnd)
ON_WM_PAINT ()
ON_WM_LBUTTONDOWN ()
ON_WM_LBUTTONDBLCLK ()
ON_WM_RBUTTONDOWN ()
END_MESSAGE_MAP () //9个矩形区域用来判定鼠标是否点进某一区域
const CRect CMainWindow::m_rcSquares[] = {
CRect ( , , , ),
CRect (, , , ),
CRect (, , , ),
CRect ( , , , ),
CRect (, , , ),
CRect (, , , ),
CRect ( , , , ),
CRect (, , , ),
CRect (, , , )
}; CMainWindow::CMainWindow ()
{
//初始化游戏
InitGame(); //注册一个 WNDCLASS 窗口类.
CString strWndClass = AfxRegisterWndClass (
CS_DBLCLKS, // Class style(有双击时间发生的窗口类型)
AfxGetApp ()->LoadStandardCursor (IDC_ARROW), // Class cursor(加载一个系统光标,也可自己定义)
(HBRUSH) (COLOR_3DFACE + ), // Background brush(每次::BeginPaint时用它清空客户区);COLOR_3DFACE+1是指定窗口具有与按钮对话框一致的背景色和其他一些3D属性;默认为灰亮色
AfxGetApp ()->LoadStandardIcon (IDI_WINLOGO) // Class icon(加载系统图标,也可自己定义)
); //调用CWnd::CreateEx()创建主窗口
//第一个参数表示0个或是多个WS_EX标志组合;2:AfxRegisterWndClass()返回的WNDCLASS名称;
//3、标题;4、窗口样式
CreateEx (, strWndClass, _T ("井字棋"),
WS_OVERLAPPED | WS_SYSMENU | WS_CAPTION | WS_MINIMIZEBOX, //WS_THICKFRAME窗口可调大小属性(这里不用)
CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, //初始位置和大小,这里用CW_USEDEFAULT让Windows拾取窗口和大小
NULL, NULL); //处理窗口位置和尺寸
CRect rect (, , , ); //理想客户区窗口矩形形状
CalcWindowRect (&rect); //根据分辨率、菜单...计算窗口矩形大小(必须在窗口创建后调用) SetWindowPos (NULL, , , rect.Width (), rect.Height (),
SWP_NOZORDER | SWP_NOMOVE | SWP_NOREDRAW);
} //在程序结束之前销毁创建的CMainWindow对象
void CMainWindow::PostNcDestroy ()
{
delete this;
} //OnPaint()响应每次重绘棋盘
void CMainWindow::OnPaint ()
{
CPaintDC dc (this);
DrawBoard (&dc);
} //单击鼠标左键响应
void CMainWindow::OnLButtonDown (UINT nFlags, CPoint point)
{
CClientDC dc (this); //如果不该左键响应(即不该左键下,返回)
if (m_nNextChar != EX){
return ;
} //获得点击矩形区域编号
//如果没有点中或者已经被下棋了,返回
int nPos = GetRectID (point);
if ((nPos == -) || (m_nGameGrid[nPos] != ))
return; //标记已下并改变下一个点击状态
m_nGameGrid[nPos] = EX;
m_nNextChar = OH; //画上图并判断游戏是否结束
DrawX (&dc, nPos);
if(CheckForGameOver ())return; //后续改变胜利表和各人、机各胜利组合的棋子数
for(int i=;i<;i++){
if(ptab[nPos][i]){
win[][i]++;
ctab[nPos][i]=false;
win[][i]=;
}
} //电脑下棋
CpDraw(&dc);
if(CheckForGameOver ())return;
} //单击鼠标右键响应(同左键)
void CMainWindow::OnRButtonDown (UINT nFlags, CPoint point)
{
if (m_nNextChar != OH)
return; int nPos = GetRectID (point);
if ((nPos == -) || (m_nGameGrid[nPos] != ))
return; m_nGameGrid[nPos] = OH;
m_nNextChar = EX; CClientDC dc (this);
DrawO (&dc, nPos);
CheckForGameOver ();
} //左键双击边框重新开始
//dc.GetPixel (Point point)获取当前光标下像素颜色判断与黑色匹配
void CMainWindow::OnLButtonDblClk (UINT nFlags, CPoint point)
{
CClientDC dc (this);
if (dc.GetPixel (point) == RGB (, , ))
ResetGame ();
} //判定鼠标是否点进矩形某一区域,点进返回区域编号,没有返回-1
//此处用了一个rect.PtInRect(Point point)函数帮助判定
int CMainWindow::GetRectID (CPoint point)
{
for (int i=; i<; i++) {
if (m_rcSquares[i].PtInRect (point))
return i;
}
return -;
} //画上棋盘并画上圈和叉
void CMainWindow::DrawBoard (CDC* pDC)
{
//画上棋盘
CPen pen (PS_SOLID, , RGB (, , ));
CPen* pOldPen = pDC->SelectObject (&pen); pDC->MoveTo (, );
pDC->LineTo (, ); pDC->MoveTo (, );
pDC->LineTo (, ); pDC->MoveTo (, );
pDC->LineTo (, ); pDC->MoveTo (, );
pDC->LineTo (, ); //画上叉和圈
for (int i=; i<; i++) {
if (m_nGameGrid[i] == EX)
DrawX (pDC, i);
else if (m_nGameGrid[i] == OH)
DrawO (pDC, i);
}
pDC->SelectObject (pOldPen);
} //画叉函数
void CMainWindow::DrawX (CDC* pDC, int nPos)
{
CPen pen (PS_SOLID, , RGB (, , ));//宽为16像素的红笔
CPen* pOldPen = pDC->SelectObject (&pen); CRect rect = m_rcSquares[nPos];
rect.DeflateRect (, );//把矩形每个方向都缩进16个像素作为线条边框
pDC->MoveTo (rect.left, rect.top);
pDC->LineTo (rect.right, rect.bottom);
pDC->MoveTo (rect.left, rect.bottom);
pDC->LineTo (rect.right, rect.top); pDC->SelectObject (pOldPen);
} //画圈函数
void CMainWindow::DrawO (CDC* pDC, int nPos)
{
CPen pen (PS_SOLID, , RGB (, , ));//宽为16像素的红笔
CPen* pOldPen = pDC->SelectObject (&pen);
pDC->SelectStockObject (NULL_BRUSH); //空画刷是为了防止画出的圆内部出现白色遮住背景 CRect rect = m_rcSquares[nPos];
rect.DeflateRect (, );//把矩形每个方向都缩进16个像素作为圆的边框
pDC->Ellipse (rect); pDC->SelectObject (pOldPen);
} //电脑画图
void CMainWindow::CpDraw(CDC* pDC)
{
int grades[][];
int m,i,max=;
int u; for(m=;m<;m++)
{
grades[][m]=;
grades[][m]=; if(m_nGameGrid[m]==)
{
for(i=;i<;i++)
{
//计算玩家在空棋格上的获胜分数
if(ptab[m][i] && win[][i]!=)
{
switch(win[][i])
{
case :
grades[][m]+=;
break;
case :
grades[][m]+=;
break;
case :
grades[][m]+=;
break;
}
} //计算计算机在空格上的获胜分数
if(ctab[m][i] && win[][i]!=)
{
switch(win[][i])
{
case :
grades[][m]+=;
break;
case :
grades[][m]+=;
break;
case :
grades[][m]+=;
break;
}
}
} if(max==)u=m; if(grades[][m]>max){
max=grades[][m];
u=m;
}
else if(grades[][m]==max){
if(grades[][m]>grades[][u])u=m;
} if(grades[][m]>max){
max=grades[][m];
u=m;
}
else if(grades[][m]==max){
if(grades[][m]>grades[][u])u=m;
}
}
} //标记已下并改变下一个点击状态
m_nGameGrid[u]=OH;
m_nNextChar = EX; //画上图
DrawO(pDC,u); //后续改变胜利表和各人、机各胜利组合的棋子数
for(i=;i<;i++){
if(ctab[u][i]){
win[][i]++;
ptab[u][i]=false;
win[][i]=;
}
}
} //响应胜利结束的函数
bool CMainWindow::CheckForGameOver ()
{
int nWinner; //通过调用IsWinner ()函数获取谁获胜;并用MessageBox输出胜利消息;响应OK后重开一局
//==Message(CString,_T(标题),类型)
if (nWinner = IsWinner ()) {
CString string = (nWinner == EX) ?
_T ("X wins!") : _T ("O wins!");
MessageBox (string, _T ("Game Over"), MB_ICONEXCLAMATION | MB_OK);
ResetGame ();
return ;
} //通过IsDraw ()函数判断是否平局
else if (IsDraw ()) {
MessageBox (_T ("It's a draw!"), _T ("Game Over"),
MB_ICONEXCLAMATION | MB_OK);
ResetGame ();
return ;
}
return ;
} //判断输赢EX左胜;OH右胜;0没有胜
int CMainWindow::IsWinner ()
{
//用静态数组存储获胜组合
static int nPattern[][] = {
, , ,
, , ,
, , ,
, , ,
, , ,
, , ,
, , ,
, ,
}; for (int i=; i<; i++) {
if ((m_nGameGrid[nPattern[i][]] == EX) &&
(m_nGameGrid[nPattern[i][]] == EX) &&
(m_nGameGrid[nPattern[i][]] == EX))
return EX; if ((m_nGameGrid[nPattern[i][]] == OH) &&
(m_nGameGrid[nPattern[i][]] == OH) &&
(m_nGameGrid[nPattern[i][]] == OH))
return OH;
}
return ;
} //判断是否平局函数
BOOL CMainWindow::IsDraw ()
{
for (int i=; i<; i++) {
if (m_nGameGrid[i] == )
return FALSE;
}
return TRUE;
} //初始化游戏
void CMainWindow::InitGame()
{ int i,k;
int count=; //设定玩家与计算机在各个获胜组合中的棋子数
for(i=;i<;i++)
{
win[][i]=;
win[][i]=;
} //初始化棋盘状态
::ZeroMemory (m_nGameGrid,*sizeof(int));
memset(ctab,,sizeof(ctab));
memset(ptab,,sizeof(ptab));
//设定水平方向的获胜组合
for(i=;i<=;i+=)
{
for(k=;k<;k++)//3个棋子1个获胜组合
{
ptab[i+k][count]=true;
ctab[i+k][count]=true;
}
count++;
} //设定垂直方向的获胜组合
for(k=;k<;k++)
{
for(i=;i<=;i+=)//3个棋子1个获胜组合
{
ptab[i+k][count]=true;
ctab[i+k][count]=true;
}
count++;
} //设定对角线方向上的获胜组合
for(i=;i<=;i+=){
ptab[i][count]=true;
ctab[i][count]=true;
}count++;
for(i=;i<=;i+=){
ptab[i][count]=true;
ctab[i][count]=true;
} srand(unsigned(time(NULL))); m_nNextChar = EX;//玩家先走
}
//重新开始初始化
void CMainWindow::ResetGame ()
{
InitGame();
Invalidate (); //使控件的整个图面无效并导致重绘控件
}
[游戏学习22] MFC 井字棋 双人对战的更多相关文章
- 井字棋游戏升级版 - TopTicTacToe项目 简介
一.游戏简介 井字棋是一款世界闻名的游戏,不用我说,你一定知道它的游戏规则. 这款游戏简单易学,玩起来很有意思,不过已经证明出这款游戏如果两个玩家都足够聪明的话, 是很容易无法分出胜负的,即我们得到的 ...
- 强化学习实战 | 表格型Q-Learning玩井字棋(四)游戏时间
在 强化学习实战 | 表格型Q-Learning玩井字棋(三)优化,优化 中,我们经过优化和训练,得到了一个还不错的Q表格,这一节我们将用pygame实现一个有人机对战,机机对战和作弊功能的井字棋游戏 ...
- 强化学习实战 | 自定义Gym环境之井字棋
在文章 强化学习实战 | 自定义Gym环境 中 ,我们了解了一个简单的环境应该如何定义,并使用 print 简单地呈现了环境.在本文中,我们将学习自定义一个稍微复杂一点的环境--井字棋.回想一下井字棋 ...
- python 游戏(井字棋)
1. 游戏思路和流程图 实现功能,现实生活中的井字棋玩法 游戏流程图 2. 使用模块和游戏提示 import random def game_info(): print('欢迎来到井字棋游戏') pr ...
- C++井字棋游戏,DOS界面版
据说有一个能保证不败的算法.明天看看先再写个PVC版的. 正题.今天无聊写了个井字棋游戏,顺便逐渐让自己习惯良好的代码风格,放上来给新手学习学习. jzq2.cpp /* N字棋游戏PVP版,DOS版 ...
- 井字棋小游戏(C语言)
最近沉迷于<NetHack>.<DCSS>等字符游戏,对其很感兴趣,于是用C语言写了个字符界面的井字棋小游戏.欢迎大家指教. 编写时遇到了一些问题,我原先准备用循环,直到读取到 ...
- 强化学习实战 | 表格型Q-Learning玩井字棋(一)
在 强化学习实战 | 自定义Gym环境之井子棋 中,我们构建了一个井字棋环境,并进行了测试.接下来我们可以使用各种强化学习方法训练agent出棋,其中比较简单的是Q学习,Q即Q(S, a),是状态动作 ...
- 强化学习实战 | 表格型Q-Learning玩井字棋(二)
在 强化学习实战 | 表格型Q-Learning玩井字棋(一)中,我们构建了以Game() 和 Agent() 类为基础的框架,本篇我们要让agent不断对弈,维护Q表格,提升棋力.那么我们先来盘算一 ...
- [CareerCup] 17.2 Tic Tac Toe 井字棋游戏
17.2 Design an algorithm to figure out if someone has won a game oftic-tac-toe. 这道题让我们判断玩家是否能赢井字棋游戏, ...
随机推荐
- 简单的javascript--test2
插件: jquery, sweetalert (http://t4t5.github.io/sweetalert/ ) 1.index.html <!DOCTYPE HTML> <h ...
- C# 定制 Attribute 简单使用
所谓 “定制Attribute”,就是一个类的实例,它被序列化成驻留在元数据的一个字节流. 我们可以使用 Attribute 来保存注释: namespace AttributeDemo { [Att ...
- 51单片机ALE引脚的控制(摘录)
ALE/PROG: 当访问外部存储器时,地址锁存允许的输出电平用于锁存地址的地位字节. 在FLASH编程期间,此引脚用于输入编程脉冲. 在平时,ALE端以不变的频率周期输出正脉冲信号,此频率为振荡器频 ...
- [转]Linux vi 编辑后如何保存
比如:vi /etc/vsftpd/vsftpd.conf 按ESC键 跳到命令模式,然后: :w 保存文件但不退出vi :w file 将修改另外保存到file中,不退出vi :w! 强制保存,不推 ...
- Android Preference使用
Android Preference经常使用在例如设置的功能,Android提供preference这个键值对的方式来处理这种情况,自动保存这些数据,并立时生效,这种就是使用android share ...
- Windows 设置扩展投影鼠标移出方向
1. 连接数据线,按下 “WINDOWS” + P 按钮,选择“扩展投影”: 2.更改鼠标移出屏幕的方向:桌面右键选择“屏幕分辨率” , 移动“更改显示器外观”中两个图的相对方向即可:
- jquery attr()方法 添加,修改,获取对象的属性值。
jquery attr()方法 添加,修改,获取对象的属性值. jquery中用attr()方法来获取和设置元素属性,attr是attribute(属性)的缩写,在jQuery DOM操作中会经常用到 ...
- 【JSP】自定义标签开发入门
JSP 自定义标签 自定义标签是用户定义的JSP语言元素.当JSP页面包含一个自定义标签时将被转化为servlet,标签转化为对被 称为tag handler的对象的操作,即当servlet执行时We ...
- 在CentOS6.7操作系统上编译安装httpd2.4
功能描述: 在CentOS6.7操作系统上,编译安装apache服务,实现定制功能等 一.安装前提 1)安装编译httpd需要的软件包 [root@bqe6tewv41kx ~]# yum -y i ...
- Android Studio项目结构
我们创建了一个Android Project,打开就如下图所示: 我们选择Project视图,就会有以下的项目文档结构: 上面笔者标注的数字是接下来要讲解的内容. 我们先来看1: 我们创建Appl ...