C++ Easyx 三子棋
目录
思路
框架编辑
读取操作
数据操作
绘制画面
游戏的数据结构
用二维数组来模拟棋盘格
赢的情况
平局情况
Code
代码细节部分
(1)初始化棋盘格
(2) 初始化棋子类型编辑
事件处理部分
落子
框架内代码的完善
数据处理框架代码的完善
检查是否赢了 (函数)
绘制图形框架代码的完善
绘制棋盘网格(函数)
绘制棋子 (函数)
绘制提示信息 (函数)
DBUG
优化
代码托管
三子棋/Test.cpp · 孙鹏宇/孙鹏宇的第一个仓库 - 码云 - 开源中国 (gitee.com)
思路
我们遵循先框架,后思路得的思路
框架
首先是读取操作:
读取操作
读取鼠标单击之后的信息
接下来是数据操作:
数据操作
我们只需要对游戏胜负进行判断
胜的清空:一条线三颗棋子都一样
平的情况:棋格全部填满还未分出胜负。
游戏结束时使用弹出告诉玩家游戏结果,然后退出主循环。
绘制画面
我们使用line函数绘制一个3*3的棋盘格:
x玩家时使用line函数对角线画两个线,轮到O玩家落子时时使用cirlce函数画个圆:
游戏的数据结构
用二维数组来模拟棋盘格
棋盘内容为字符串,初始化为‘-'
赢的情况
我们对赢得情况进行穷举:
一共有八种
平局情况
如果9个网格均被棋子填满却没有获胜的一方,那么就是平局:
Code
写代码同样遵循先框架再细节的原则:
我们先把上面的内容转化为代码:
框架:
#include<easyx.h>
#include<iostream>
using namespace std;
//处理数据
//
//检测玩家是否赢了
bool Checkwin(char str)
{
}
//检测是否平局
bool Checkavg(char str)
{
}
//绘制图像
//
//绘制棋盘格
void DrawBorad()
{
}
//绘制棋子
void DrawPiece()
{
}
//绘制提示信息
void DrawTipText()
{
}
int main()
{
initgraph(60,600);//绘制窗口
bool flag = true;//退出主循环的标识列
ExMessage msg;//存储消息
BeginBatchDraw();//渲染缓冲区
//主循环
while (flag)
{
//读取操作
while(peekmessage(&msg))//读取消息
{
//读取到的细节如何处理稍息再说
}
cleardevice();//清屏
//重新绘图
DrawBorad();
DrawPiece();
DrawTipText();
FlushBatchDraw();//刷新缓冲区
}
EndBatchDraw();//刷新缓冲区
return 0;
}
代码细节部分
如果 x玩家赢了,我们可以用MessageBox()函数弹出了一个框,显示x玩家赢了:
代码如下:
同样的,O玩家获胜的情况和平局的情况也写一下:
设置两个全局
(1)初始化棋盘格
(2) 初始化棋子类型
事件处理部分
用msg来表示鼠标的位置:
怎么把鼠标的位置映射到数组下标呢?我们绘制的棋盘格的大小为600x600,分成三等份之后每个格子的大小为200x200:
所以我们求横坐标可以有这样一个公式:代码为:
落子
(1)首先判断是否可以落子
(2) 落完子之后要切换下次落子的棋子类型
框架内代码的完善
把之前只写了框架没写实现的函数补全:
数据处理框架代码的完善
检查是否赢了 (函数)
按照我们之前列的八种赢的情况写:
Checkavg()函数
用两个for循环来遍历棋盘格中每一个元素,如果还有空格没有落子就返回false代表没有平局,最后如果没有返回false说明平局了,返回true:
绘制图形框架代码的完善
绘制棋盘网格(函数)
棋盘格的网格其实就是四条线:
我们可以通过图形绘制相关函数->line()函数来绘制:
首先棋盘格总大小是600x600,每个小格子是200x200:
绘制棋子 (函数)
(1)绘制棋子,首先用二维数组遍历一下棋盘,如果要落的棋子是'O',那就在棋格中间画圆。
(2)棋盘中心的求法:一个小格子长宽200x200,中心坐标为左上角坐标+100:
(3)画圆的方法:
(4)当棋子为'x'时就按小格子对角线画两条线
(5)对角线点求法:如下图(6)画对角线的方法:
(7)如果不是'O'或者',那就什么都不用做。
(8)代码:
绘制提示信息 (函数)
适用settextcolor()函数将提示文本设高亮:
outtextxy()函数用来在窗口指定位置输出提示信息:
code:
DBUG
这样三子棋基本功能就做好了,但是有bug:
(1)闪退
(2)闪退过程中可以看见我们画的棋盘,发现棋盘网格线条错位:
原因:
(1)绘制棋盘时手误
(2)闪退的原因是我们在判断是否平局用的else,没设条件,此刻没输值就直接平局了(只要不是'O',‘x’就直接平局了),这显示不是我们要的,我们想要的是棋盘满了还没赢才平局,因此我们应该引用checkagv()函数。
修改:
修改完之后运行:
发现有两个错误
(1)鼠标左键不用点击,就可以落子
(2)落子位置和我们鼠标落点位置不配置
鼠标左键不用点击,就可以落子的原因:
应该选择ExMessage的WM_LBUTTONDOWN表实列,我选成第一个了:
落子位置不配置的原因:
(1)切换棋子类型应该包含在可以落子的前提下,如果不能落子也就没必要切换棋子类型了:
修改之后:
(2)
二维数组是按照横纵坐标系的:
但是我们的窗口确是纵横坐标系:
错误:
修改:
交互功能正常,但是渲染功能有问题,最后一颗棋子不会显示:
原因:
我们把重新绘图放在判断胜负的后面,当玩家赢了的时候会执行flag=fale,此时会执行重新绘图。
当再次循环时,flag因为false,所以退出循环。也就是绘图只执行了一次,一闪而过。
我们把绘图放到胜负判断之前,这样即便是不再进入循环,也是最后一次落子的下一次绘图不会显示,而最后一次落子的绘图会显示在当前窗口。
正常运行:
优化
当我们的程序跑起来之后,查看任务管理器,发现我们的程序消耗内存空间特别大;
这是因为计算机在执行while循环时特别快,我们编写的主循环在顷刻间已经被执行了成千上万次。
因此,为了不避免的销毁,我们可以使用sleep()函数使循环休眠几毫秒。
我们可以在主循环开头写一个开始数获取GetTickCount(),主循环结束位置写一个结尾数获取GetTickCount()函数。
通过计算 二者落差 可以得到 该主循环实际运行所需要的 毫秒数,简称实需数。
如果我们想在60帧率下刷新,那么就让1000/60=16,16为我们的期望值。
如果 实需数 < 期望值,说明不用休眠。
否则,实需数-期望值= 休眠数。