[游戏学习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. 这道题让我们判断玩家是否能赢井字棋游戏, ...
随机推荐
- 编程范式 epesode7,8 stack存放指针类型and heap,register
这一节从后往前写. ____stack and heap ___stack由 汇编语言操控管理,数据先入后出. 栈是存放局部变量,函数调用子函数时,该函数在栈中占用的空间会增大,用于存放子函数的局部变 ...
- jstl格式化数字
jstl中的<fmt:formatNumber>标签 设置显示时间戳<% request.setAttribute("currentTimeStamp" ...
- [转载] Android.Hook框架xposed开发篇
本文转载自: http://www.52pojie.cn/thread-396793-1-1.html 原帖:http://drops.wooyun.org/tips/7488 作者:瘦蛟舞 官方教程 ...
- (引用) unittest测试驱动之执行测试(三)
转载:http://www.wtoutiao.com/p/ydeoyY.html 在unittest的模块中,提供了TestRunner类来进行运行测试用例,在实际的应用中,经常使用的是TextTes ...
- ArcGIS删除部分数据后全图范围不正确
我有一个全国地图的图层,现在删除图层中其他省份,只保留山东省的图形,但是点击全图后,全图范围仍然是全国地图时候的全图范围,使用的版本是ArcGIS9.3,数据存放在9.3的个人数据库中(Perso ...
- 用T-sql 实现Oracle Connect by 的功能
; with subDepartment as ( select BesonDepartmentID, DepartmentName, ParentBesonDepartmentID, 1 as Hi ...
- php插入排序
//php版插入排序 $arr=array('','5','3','7','6','4','8','2'); for($i=2;$i<count($arr);$i++) { ...
- Hibernate之创建命名策略
在开发软件时,通常会要求每个开发人员遵守共同的命名策略.例如,数据库的表名及字段名的所有字符都要大写,表名以“S”结尾.对于Customer类,对应的数据库表名为CUSTOMERS.为了在映射文件中遵 ...
- 【JSP】Tiles框架的基本使用
Tiles介绍 Tiles 是一种JSP布局框架,主要目的是为了将复杂的jsp页面作为一个的页面的部分机能,然后用来组合成一个最终表示用页面用的,这样的话,便于对页面的各个机能的变更及维护. Tile ...
- 编程语言java-并发(锁)
文章转载自http://www.importnew.com/22078.html 悲观锁和乐观锁 我们都知道,CPU是时分复用的,就是CPU把时间片,分配给不同的thread/process轮流执行, ...