无引擎游戏开发(3):数据结构设计|功能函数完善
为了简单起见,我们将棋盘的二维数组定义为全局变量。除此之外还要定义一个char类型的全局变量来识别当前的落子类型,我们将其初始化为‘O’。
char Board_data[3][3] = {{'-', '-', '-'},{'-', '-', '-'},{'-', '-', '-'},
};char Cur_piece = 'O';
现在回到“读取操作”部分,通过msg的x与y字段来获取鼠标点击的位置,但是现在需要将鼠标点击的位置映射到数组的索引中,一开始将数组初始化为600*600,九等分后每个格子都是200*200,单看水平方向的二维数组索引便是鼠标点击位置除以200的整数部分,竖直方向一样。
接下来便是尝试落子,也即尝试修改对应数组索引位置的值,记住二维数组是先行后列,也即先Y后X确定落子位置,并在落子后切换棋子类型。
然后是完善功能函数,首先是CheckWin函数,先前已经讨论了所有8种情况:
bool CheckWin(char c) {if (Board_data[0][0] == c && Board_data[0][1] == c && Board_data[0][2] == c) return true;if (Board_data[1][0] == c && Board_data[1][1] == c && Board_data[1][2] == c) return true;if (Board_data[2][0] == c && Board_data[2][1] == c && Board_data[2][2] == c) return true;if (Board_data[0][0] == c && Board_data[1][0] == c && Board_data[2][0] == c) return true;if (Board_data[0][1] == c && Board_data[1][1] == c && Board_data[2][1] == c) return true;if (Board_data[0][2] == c && Board_data[1][2] == c && Board_data[2][2] == c) return true;if (Board_data[0][0] == c && Board_data[1][1] == c && Board_data[2][2] == c) return true;if (Board_data[0][2] == c && Board_data[1][1] == c && Board_data[2][0] == c) return true;return false;
}
任何条件被满足都会被短路,然后返回true并不再向下执行。所有条件检测失败后函数一直向下执行到返回false。
CheckDraw函数与CheckWin函数思想相似,遍历整个棋盘如果还有空则返回false,否则返回true:
bool CheckDraw() {for (size_t i = 0; i < 3; i++) {for (size_t j = 0; j < 3; j++) {if (Board_data[i][j] == '-') return false;}}return true;
}
接下来便是绘制棋盘、棋子、提示信息:
棋盘被四条线切割为九份,使用line函数切割:

void DrawBoard() {line(0, 200, 600, 200);line(0, 400, 600, 400);line(200, 0, 200, 600);line(400, 0, 400, 600);
}
然后是DrawPiece函数,先遍历整个棋盘,然后用switch函数判断所有情况,i对应y,j对应x:
void DrawPiece() {for (size_t i = 0; i < 3; i++) {for (size_t j = 0; j < 3; j++) {switch (Board_data[i][j]) {case 'O':circle(200 * j + 100, 200 * i + 100, 100);break;case 'X':line(200 * j, 200 * i, 200 * (j + 1), 200 * (i + 1));line(200 * (j + 1), 200 * i, 200 * j, 200 * (i + 1));break;case '-':break;}}}
}
最后是绘制提示信息DrawTipText:
void DrawTipText() {static TCHAR str[64];_stprintf_s(str, _T("当前棋子类型: %c"), Cur_piece);settextcolor(RGB(225, 175, 45));outtextxy(0, 0, str);
}
这里使用了_stprintf_s这个字符串格式化函数,并且定义了TCHAR的字符数组作为格式化的缓冲区,这与printf和sprintf很像,只不过是为了在更通用的编码环境下使用。
话接上文,EasyX可以使用outtextxy函数在窗口的指定坐标处绘制文本字符串,不过在绘制前我们使用了settextcolor函数将文本绘制颜色改为橙黄色使其醒目。settextcolor接受一个COLORREF类型的参数,我们可以通过RGB宏传入对应颜色分量组合出COLORREF类型的值。
至此,我们完成了所有代码。
测试:

最后一个棋子在绘制上去前就判断了胜负,有点影响美感,所以应该在绘制后再判断胜负。所以绘制函数应该在判断胜负之前:

改进后效果显著:

完善:发现井字棋程序占用CPU很大
这是因为计算机在执行while循环时非常快,主循环在顷刻间执行成千上万次,占用了大量的CPU时间片,对于机器是一种性能浪费。所以我们可以使用sleep函数来让程序执行完一次循环后休眠一小段时间从而减少计算资源的浪费。
那么该休眠多久呢?
随着游戏体量的增大,程序每次执行主循环所执行的计算任务可能是不同的,以及涉及到操作系统CPU计算资源的分配,这就导致每次执行主循环所消耗的实际时间可能是不一样的,所以我们要根据每一帧执行的实际耗时动态地计算在这之后要休眠多长的时间,所以引入函数GetTickCount,可以使用它获取程序自运行开始以来到现在的毫秒数:

所以我们在循环开头和结尾各调用一次,然后通过相减得出这次循环实际消耗的毫秒数。
/*#include<graphics.h>char Board_data[3][3] = {{'-', '-', '-'},{'-', '-', '-'},{'-', '-', '-'},
};char Cur_piece = 'O';bool CheckWin(char c) {if (Board_data[0][0] == c && Board_data[0][1] == c && Board_data[0][2] == c) return true;if (Board_data[1][0] == c && Board_data[1][1] == c && Board_data[1][2] == c) return true;if (Board_data[2][0] == c && Board_data[2][1] == c && Board_data[2][2] == c) return true;if (Board_data[0][0] == c && Board_data[1][0] == c && Board_data[2][0] == c) return true;if (Board_data[0][1] == c && Board_data[1][1] == c && Board_data[2][1] == c) return true;if (Board_data[0][2] == c && Board_data[1][2] == c && Board_data[2][2] == c) return true;if (Board_data[0][0] == c && Board_data[1][1] == c && Board_data[2][2] == c) return true;if (Board_data[0][2] == c && Board_data[1][1] == c && Board_data[2][0] == c) return true;return false;
}bool CheckDraw() {for (size_t i = 0; i < 3; i++) {for (size_t j = 0; j < 3; j++) {if (Board_data[i][j] == '-') return false;}}return true;
}void DrawBoard() {line(0, 200, 600, 200);line(0, 400, 600, 400);line(200, 0, 200, 600);line(400, 0, 400, 600);
}void DrawPiece() {for (size_t i = 0; i < 3; i++) {for (size_t j = 0; j < 3; j++) {switch (Board_data[i][j]) {case 'O':circle(200 * j + 100, 200 * i + 100, 100);break;case 'X':line(200 * j, 200 * i, 200 * (j + 1), 200 * (i + 1));line(200 * (j + 1), 200 * i, 200 * j, 200 * (i + 1));break;}}}
}void DrawTipText() {static TCHAR str[64];_stprintf_s(str, _T("当前棋子类型: %c"), Cur_piece);settextcolor(RGB(225, 175, 45));outtextxy(0, 0, str);
}
int main() {initgraph(600, 600);ExMessage msg;BeginBatchDraw();bool running = true;*/while (running) {DWORD start_time = GetTickCount();/*while (peekmessage(&msg)) {if (msg.message == WM_LBUTTONDOWN) {int x = msg.x;int y = msg.y;int index_x = x / 200;int index_y = y / 200;if (Board_data[index_y][index_x] == '-') {Board_data[index_y][index_x] = Cur_piece;if (Cur_piece == 'O') Cur_piece = 'X';else Cur_piece = 'O';}}}cleardevice();DrawBoard(); DrawPiece();DrawTipText();FlushBatchDraw();if (CheckWin('X')) {MessageBox(GetHWnd(), _T("X玩家获胜"), _T("游戏结束"), MB_OK);running = false;}else if (CheckWin('O')) {MessageBox(GetHWnd(), _T("O玩家获胜"), _T("游戏结束"), MB_OK);running = false;}else if (CheckDraw()) {MessageBox(GetHWnd(), _T("平局!"), _T("游戏结束"), MB_OK);running = false;}*/DWORD end_time = GetTickCount();DWORD delta_time = end_time - start_time;/*}EndBatchDraw();return 0;}*/
如果要确保画面以最高60帧的速度刷新,那么每次循环的总时间应该是1000 / 60,如果实际消耗的毫秒数小于1000 / 60,便可以通过运用sleep()延时剩下的时间,如果超过了,就直接进入下一次循环:

可以发现CPU占用率显著下降。
完整代码如下:
#include<graphics.h>char Board_data[3][3] = {{'-', '-', '-'},{'-', '-', '-'},{'-', '-', '-'},
};char Cur_piece = 'O';bool CheckWin(char c) {if (Board_data[0][0] == c && Board_data[0][1] == c && Board_data[0][2] == c) return true;if (Board_data[1][0] == c && Board_data[1][1] == c && Board_data[1][2] == c) return true;if (Board_data[2][0] == c && Board_data[2][1] == c && Board_data[2][2] == c) return true;if (Board_data[0][0] == c && Board_data[1][0] == c && Board_data[2][0] == c) return true;if (Board_data[0][1] == c && Board_data[1][1] == c && Board_data[2][1] == c) return true;if (Board_data[0][2] == c && Board_data[1][2] == c && Board_data[2][2] == c) return true;if (Board_data[0][0] == c && Board_data[1][1] == c && Board_data[2][2] == c) return true;if (Board_data[0][2] == c && Board_data[1][1] == c && Board_data[2][0] == c) return true;return false;
}bool CheckDraw() {for (size_t i = 0; i < 3; i++) {for (size_t j = 0; j < 3; j++) {if (Board_data[i][j] == '-') return false;}}return true;
}void DrawBoard() {line(0, 200, 600, 200);line(0, 400, 600, 400);line(200, 0, 200, 600);line(400, 0, 400, 600);
}void DrawPiece() {for (size_t i = 0; i < 3; i++) {for (size_t j = 0; j < 3; j++) {switch (Board_data[i][j]) {case 'O':circle(200 * j + 100, 200 * i + 100, 100);break;case 'X':line(200 * j, 200 * i, 200 * (j + 1), 200 * (i + 1));line(200 * (j + 1), 200 * i, 200 * j, 200 * (i + 1));break;}}}
}void DrawTipText() {static TCHAR str[64];_stprintf_s(str, _T("当前棋子类型: %c"), Cur_piece);settextcolor(RGB(225, 175, 45));outtextxy(0, 0, str);
}
int main(){initgraph(600, 600);ExMessage msg;BeginBatchDraw();bool running = true;while (running) {DWORD start_time = GetTickCount();while (peekmessage(&msg)) {if (msg.message == WM_LBUTTONDOWN) {int x = msg.x;int y = msg.y;int index_x = x / 200;int index_y = y / 200;if (Board_data[index_y][index_x] == '-') {Board_data[index_y][index_x] = Cur_piece;if (Cur_piece == 'O') Cur_piece = 'X';else Cur_piece = 'O';}}}cleardevice();DrawBoard();DrawPiece();DrawTipText();FlushBatchDraw();if (CheckWin('X')) {MessageBox(GetHWnd(), _T("X玩家获胜"), _T("游戏结束"), MB_OK);running = false;}else if (CheckWin('O')) {MessageBox(GetHWnd(), _T("O玩家获胜"), _T("游戏结束"), MB_OK);running = false;}else if (CheckDraw()) {MessageBox(GetHWnd(), _T("平局!"), _T("游戏结束"), MB_OK);running = false;}DWORD end_time = GetTickCount();DWORD delta_time = end_time - start_time;if (delta_time < (1000 / 60)) {Sleep((1000 / 60) - delta_time);}}EndBatchDraw();return 0;}
相关文章:
无引擎游戏开发(3):数据结构设计|功能函数完善
为了简单起见,我们将棋盘的二维数组定义为全局变量。除此之外还要定义一个char类型的全局变量来识别当前的落子类型,我们将其初始化为‘O’。 char Board_data[3][3] {{-, -, -},{-, -, -},{-, -, -}, };char Cur_piece O; 现在回到“读取操作”部分…...
Laravel 高级:了解$loop
Blade 提供 foreach、while、for 和 forelse 等指令来与 PHP 循环配合使用。 您知道吗... 这些指令中有一个方便的 $loop 变量,它指示当前循环迭代?在本文中,我们将探索 $loop 和 loop 指令。😎 使用$loop比foreach更深入 该for…...
深入理解指针(1)
目录: 1. 内存和地址 2. 指针变量和地址 3. 指针变量类型的意义 4. const修饰指针 5. 指针运算 6. 野指针 7. assert断⾔ 8. 指针的使⽤和传址调用 1. 内存和地址 1.1 内存 在讲内存和地址之前,我们想有个⽣活中的案例: 假设有⼀栋宿舍楼&a…...
在无线网中 2.4G、5G、WiFi6、WiFi7 都是什么意思?
有同学问我在无线网中 2.4G/5G/WiFi6/WiFi7 都是什么意思?其实这是两个概念, 2.4G/5G 是频段,WiFi6/WiFi7 是无线协议的版本,千万别把版本和频段搞混了。 WiFi 协议是一系列基于 IEEE 802.11 标准的无线局域网技术协议࿰…...
milvus元数据解析工具milvusmetagui介绍使用
简介 milvusmetagui是一款用来对milvus的元数据进行解析的工具,milvus的元数据存储在etcd上,而且经过了序列化,通过etcd-manager这样的工具来查看是一堆二进制乱码,因此开发了这个工具对value进行反序列化解析。 在这里为了方便交…...
LabVIEW电磁超声热态金属在线缺陷检测系统
LabVIEW软件开发的电磁超声热态金属在线缺陷检测系统针对极端高温环境下的金属材料,进行实时、无损的缺陷检测,具有高精度和高可靠性,能够显著提高材料质量控制的效率和准确性。 项目背景 随着工业技术的发展,高温环境下的金属材…...
leecode代码模板
二分算法: 34. 在排序数组中查找元素的第一个和最后一个位置给你一个按照非递减顺序排列的整数数组 nums,和一个目标值 target。请你找出给定目标值在数组中的开始位置和结束位置。如果数组中不存在目标值 target,返回 [-1, -1]。你必须设计…...
可靠性测试及模型计算
双85高温高湿测试 场景描述: 85℃温度 85%湿度 老化测试 目的: 衡量产品使用寿命 反向推导: 如何根据产品寿命及工况计算双85测试时间 模型介绍 本质是化学反应速率(老化的本质是)随温度的变化 温湿度循环测…...
【Tools】 深入了解Burp Suite:Web应用抓包利器
唱 情 歌 齐齐来一遍 无时无刻都记住掌声 响遍天 来唱 情 歌 由从头再一遍 如情浓有点泪流难避免 音阶起跌拍子改变 每首歌 是每张脸 喜欢我 别遮脸 任由途人发现 🎵 刘德华《十七岁》 在Web应用和移动应用的开发与测试过程中,抓包…...
技术先进、应用广泛、社区活跃的[项目名称]
项目介绍 ---- [项目介绍内容],此项目在开源社区中备受欢迎,其创新性技术和广泛应用领域吸引了大量开发者关注。 代码解释 ---- [代码解释内容],该项目采用[编程语言],通过[技术栈]实现,具有[功能特点]。 …...
Vue中data的属性可以和methods中方法同名吗,为什么?
在Vue中,data的属性不可以和methods中的方法同名,原因如下: 命名规范:从编程规范的角度来看,同名属性或方法可能会导致混淆和难以维护的代码。data通常用于存储组件的状态或数据,而methods则包含组件的行为…...
Esxi上创建windows 11虚拟机
下载windows 11系统镜像 Download Windows 11 (microsoft.com) 虚拟机配置 正常安装部署,需要注意以下几点: 1.cpu开启虚拟化,启用CPU热添加 2.内存开启热插拔 3.磁盘类型最好选择精简置备(磁盘只使用最初所需要的数据存储空间…...
法大大亮相国家级期刊,助力数字政务有实“例”!
近日,在最新发布的国家级学术期刊《市场监督管理》中,法大大作为国内领先的电子签厂商亮相,这也是电子签行业的“第一次”。 截自《市场监督管理》2024年第12期 《市场监督管理》杂志于1953年创刊,是中国工商出版社主办的一本学术…...
【管理咨询宝藏131】麦肯锡波士顿贝恩经典战略咨询报告套装
本报告首发于公号“管理咨询宝藏”,如需阅读完整版报告内容,请查阅公号“管理咨询宝藏”。 【管理咨询宝藏131】麦肯锡波士顿贝恩经典战略咨询报告套装 【格式】PDF版本 【关键词】麦肯锡、波士顿咨询、贝恩咨询、战略咨询、战略落地、战略洞察 【强烈…...
Python | Leetcode Python题解之第160题相交链表
题目: 题解: class Solution:def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> ListNode:A, B headA, headBwhile A ! B:A A.next if A else headBB B.next if B else headAreturn A...
SSRF学习,刷题
[HNCTF 2022 WEEK2]ez_ssrf 给了一个Apache2的界面,翻译一下 就是一个默认的界面,目录扫描 可以看到flag.php,肯定是不能直接访问得到的,还有index.php,访问这个 可以看到三个参数data,host,port 还有fsockopen() 函数是 PHP 中用于打开一个…...
K-Means 算法详解
K-Means 是一种常用的无监督学习算法,广泛应用于数据聚类分析。本文将详细讲解 K-Means 算法的原理、步骤、公式以及 Python 实现,帮助你深入理解这一经典算法。 什么是 K-Means 算法? K-Means 算法是一种基于原型的聚类算法,其…...
【DIY飞控板PX4移植】BARO模块BMP388气压计的PCB硬件设计和PX4驱动配置
BARO模块BMP388气压计的PCB硬件设计和PX4驱动配置 BMP388简介硬件设计封装原理图PCB设计引脚选择问题 PX4驱动配置飞控板的配置文件夹结构default.px4board文件nuttx-config/nsh/defconfig文件nuttx-config/include/board.h文件src/board_config.h文件src/i2c.cpp文件init/rc.b…...
Flutter框架高阶——Window应用程序设置窗体窗口背景完全透明
文章目录 1.修改 main.cpp1)C 与 Win32 API2)EnableTransparency()3)中文注释 2.编写 Flutter 代码1)bitsdojo_window2)window_manager3)区别对比4)同时使用(1)设置初始化…...
HJ39判断两个IP是否属于同一子网
提示:文章 文章目录 前言一、背景二、 2.1 2.2 总结 前言 HJ39判断两个IP是否属于同一子网 一、 代码: 第一版代码没有对掩码网络号进行处理。一开始对非法字段的理解就是value大于255。然后执行示例, 254.255.0.0 85.122.52.249 10.57.…...
【根据当天日期输出明天的日期(需对闰年做判定)。】2022-5-15
缘由根据当天日期输出明天的日期(需对闰年做判定)。日期类型结构体如下: struct data{ int year; int month; int day;};-编程语言-CSDN问答 struct mdata{ int year; int month; int day; }mdata; int 天数(int year, int month) {switch (month){case 1: case 3:…...
ESP32 I2S音频总线学习笔记(四): INMP441采集音频并实时播放
简介 前面两期文章我们介绍了I2S的读取和写入,一个是通过INMP441麦克风模块采集音频,一个是通过PCM5102A模块播放音频,那如果我们将两者结合起来,将麦克风采集到的音频通过PCM5102A播放,是不是就可以做一个扩音器了呢…...
sqlserver 根据指定字符 解析拼接字符串
DECLARE LotNo NVARCHAR(50)A,B,C DECLARE xml XML ( SELECT <x> REPLACE(LotNo, ,, </x><x>) </x> ) DECLARE ErrorCode NVARCHAR(50) -- 提取 XML 中的值 SELECT value x.value(., VARCHAR(MAX))…...
TRS收益互换:跨境资本流动的金融创新工具与系统化解决方案
一、TRS收益互换的本质与业务逻辑 (一)概念解析 TRS(Total Return Swap)收益互换是一种金融衍生工具,指交易双方约定在未来一定期限内,基于特定资产或指数的表现进行现金流交换的协议。其核心特征包括&am…...
Java入门学习详细版(一)
大家好,Java 学习是一个系统学习的过程,核心原则就是“理论 实践 坚持”,并且需循序渐进,不可过于着急,本篇文章推出的这份详细入门学习资料将带大家从零基础开始,逐步掌握 Java 的核心概念和编程技能。 …...
Redis数据倾斜问题解决
Redis 数据倾斜问题解析与解决方案 什么是 Redis 数据倾斜 Redis 数据倾斜指的是在 Redis 集群中,部分节点存储的数据量或访问量远高于其他节点,导致这些节点负载过高,影响整体性能。 数据倾斜的主要表现 部分节点内存使用率远高于其他节…...
Linux --进程控制
本文从以下五个方面来初步认识进程控制: 目录 进程创建 进程终止 进程等待 进程替换 模拟实现一个微型shell 进程创建 在Linux系统中我们可以在一个进程使用系统调用fork()来创建子进程,创建出来的进程就是子进程,原来的进程为父进程。…...
在Ubuntu24上采用Wine打开SourceInsight
1. 安装wine sudo apt install wine 2. 安装32位库支持,SourceInsight是32位程序 sudo dpkg --add-architecture i386 sudo apt update sudo apt install wine32:i386 3. 验证安装 wine --version 4. 安装必要的字体和库(解决显示问题) sudo apt install fonts-wqy…...
C#学习第29天:表达式树(Expression Trees)
目录 什么是表达式树? 核心概念 1.表达式树的构建 2. 表达式树与Lambda表达式 3.解析和访问表达式树 4.动态条件查询 表达式树的优势 1.动态构建查询 2.LINQ 提供程序支持: 3.性能优化 4.元数据处理 5.代码转换和重写 适用场景 代码复杂性…...
【 java 虚拟机知识 第一篇 】
目录 1.内存模型 1.1.JVM内存模型的介绍 1.2.堆和栈的区别 1.3.栈的存储细节 1.4.堆的部分 1.5.程序计数器的作用 1.6.方法区的内容 1.7.字符串池 1.8.引用类型 1.9.内存泄漏与内存溢出 1.10.会出现内存溢出的结构 1.内存模型 1.1.JVM内存模型的介绍 内存模型主要分…...
