论坛的首页
勇芳的软件
教程和帮助
VisualFreeBasic编程文档
勇芳系列软件帮助说明教程
留言或交流
登录
搜索
登录
搜索
勇芳
累计撰写
330
篇文章
累计收到
0
条评论
首页
栏目
论坛的首页
勇芳的软件
教程和帮助
VisualFreeBasic编程文档
勇芳系列软件帮助说明教程
留言或交流
登录
自定义幻灯片
最新文章
2026-1-22
QQ游戏大厅多开补丁使用说明
使用方法 这只是一个补丁软件,点击打补丁 然后直接点桌面上的 可以重复点击这个图标,就能打开很多个QQ游戏大厅了。 打玩补丁,这个软件可以删除,级别上是用不到了。 新版大厅互相踢出房间问题 只发生在最新版大厅,老大厅没问题 当多开大厅后,进入同的游戏房间,就会提示 这个目前没办法解决,只能使用旧版本大厅 进入QQ群,下载群,里面下载旧大厅使用 点击链接加入群聊【勇芳软件下载群】:https://qm.qq.com/q/OO8R3owqky 旧版大厅登录问题 一直显示登录,无法登录 那需要先登录QQ,QQ任意版本,然后QQ游戏大厅上就有头像 直接点头像即可登录。 旧版大厅无游戏列表问题 解决方法,查看“大厅管理器”帮助,因为需要配合管理器才能解决。
2026年-1月-22日
417 阅读
0 评论
勇芳系列软件帮助说明教程
2026-1-22
下载和安装勇芳的软件
下载勇芳软件,需要进入勇芳官方网站下载,其它地方有风险,特别是百度找的,到处是广告,嵌入流氓软件,甚至诱骗收费等等很多名堂。 官方网站 勇芳官方网站 www.yfvb.com 软件页面 找到自己需要的软件,点击下载页面 下载选择 普通用户点击“安装文件下载”,简单方便。 不信安装的下载“解压版”,需要自己解压文件,麻烦很多 需要以前版本的,点击“旧版系列下载” 有的旧版下载,后面的英文字母,是密码 进入下载的地方,有3个可选,随便点一个即可 下载过程 谷歌的浏览器,一般就直接下载,微软的浏览器会提示很多次 提示打开,点击打开 提示保留,点击保留 提示安全警告,点击运行 提示安装,全部点击下一步 卸载软件 勇芳系列软件,都是绿色软件,不污染系统,不写注册表,卸载就直接删除软件文件即可 桌面有图标,删除 软件所在文件夹,选择不用的软件,直接删除
2026年-1月-22日
410 阅读
0 评论
勇芳系列软件帮助说明教程
2026-1-22
VisualFreeBasic进程通信_文件映射(内存映射)
文件映射(Memory-Mapped File,简称内存映射)是一种将磁盘文件的部分或全部内容直接映射到进程虚拟内存地址空间的技术。通过这种方式,进程可以像访问普通内存一样读写文件,无需使用read、write等传统 I/O 函数,大幅简化了文件操作流程,同时也能提升 I/O 效率(尤其适合大文件或频繁读写场景)。 在这里,我们并不是去文件映射,而是利用文件映射,跳过文件而直接使用内存映射,让任何进程都可以访问这个内存。实现共享内存,达到进程通信的目的。 A进程代码(主进程) 一、创建映射的内存 此内存为共享内存,用“名字”标识 "名字" 是唯一的,需要你自己取个名字,要是和其它人重名,那就会打开别人的内存,而不是自己创建的。 Dim hMapFile As HANDLE = CreateFileMapping( _ INVALID_HANDLE_VALUE, _ ' 不关联实际文件(匿名共享内存) NULL, _ ' 默认安全属性 PAGE_READWRITE, _ ' 可读可写 0, _ ' 高位文件大小(0表示由低位决定) 1060 * 1000, _ ' 共享内存大小(需要多少就创建多少) "名字") ' 全局命名(跨会话访问需加Global\前缀) if hMapFile =0 then 创建失败 二、映射共享内存到自己进程 需要把 【共享内存】映射到自己进程,返回内存地址 Dim pBuf As LPVOID = MapViewOfFile( _ hMapFile, _ ' 共享内存对象句柄 FILE_MAP_ALL_ACCESS, _ ' 读写权限 0, 0, 1060 * 1000) ' 映射全部内容 三、读写内存 有内存地址后,你可以用 Peek Poke 读写内存,也可以用指针读写。 四、关闭和释放 用完后,当然需要释放,不然内存泄露。 UnmapViewOfFile(pBuf) ' 解除映射 CloseHandle(hMapFile) ' 关闭共享内存对象 B进程代码(副进程) 副进程可以很多个,不单单是一个。 一、打开共享内存 和 A进程创建的内存,“名字” 一样就可以打开了 Dim hMapFile As HANDLE= OpenFileMapping(FILE_MAP_ALL_ACCESS, False, "名字") if hMapFile =0 then 打开失败,可能名字错了或没创建 二、映射共享内存到自己进程 这个操作和A进程相同 Dim pBuf As LPVOID = MapViewOfFile(hMapFile, FILE_MAP_ALL_ACCESS, 0, 0, 0) 三、读写内存 有内存地址后,你可以用 Peek Poke 读写内存,也可以用指针读写。 四、关闭和释放 用完后,当然需要释放,不然内存泄露。 UnmapViewOfFile(pBuf) ' 解除映射 CloseHandle(hMapFile) ' 关闭共享内存对象 AB进程同时读写 同时读写,是经常发生的,这个和 多线程读写相同内存一样,也有一个专用的互斥体,来保证同一时刻,只能有一个进程在读写,其它的排队等待。 代码暂时省。。。。可以问AI 越界读写 创建的内存大小是固定的,你要是超过大小,还读写,等待的你的就是软件崩溃。没什么大不了的,重新开软件就好。 封装类 为了简化代码操作,于是封装了一个类,方便使用,封装类进VFB的QQ群里下载,放到VFB的私库文件夹中,即可使用。 已经增加读写锁,不会因为同时读写发生混乱,也增加了边界检查,避免越界发生软件奔溃 安装好私库后,自己代码就简化为 Dim Shared a As 进程通信之内存共享类 = "名字" 默认内存大小为 1024 字节,可以自己定义大小 Dim Shared a As 进程通信之内存共享类 = Type("名字",100) 使用共享内存就这样写 a.tLong(0) = 100 Print a.tLong(0) 当然还有其它类型 Declare Property tLong(位置 As ULong) As Long '返回属性 ,“位置”就是在共享内存中的相对位置,从0开始,单位是字节。 Declare Property tLong(位置 As ULong, 值 As Long) '给属性赋值 Declare Property tULong(位置 As ULong) As ULong '返回属性 ,“位置”就是在共享内存中的相对位置,从0开始,单位是字节。 Declare Property tULong(位置 As ULong, 值 As ULong) '给属性赋值 Declare Property tSingle(位置 As ULong) As Single '返回属性 ,“位置”就是在共享内存中的相对位置,从0开始,单位是字节。 Declare Property tSingle(位置 As ULong, 值 As Single) '给属性赋值 Declare Property tDouble(位置 As ULong) As Double '返回属性 ,“位置”就是在共享内存中的相对位置,从0开始,单位是字节。 Declare Property tDouble(位置 As ULong, 值 As Double) '给属性赋值 Declare Property tLongInt(位置 As ULong) As LongInt '返回属性 ,“位置”就是在共享内存中的相对位置,从0开始,单位是字节。 Declare Property tLongInt(位置 As ULong, 值 As LongInt) '给属性赋值 Declare Property tULongInt(位置 As ULong) As ULongInt '返回属性 ,“位置”就是在共享内存中的相对位置,从0开始,单位是字节。 Declare Property tULongInt(位置 As ULong, 值 As ULongInt) '给属性赋值 Declare Property tString(位置 As ULong) As String '返回属性,超过“尺寸”字符会被截断,遇到0也截断,不支持带0 的字符,自己控制字符长度,避免覆盖后面数据 Declare Property tString(位置 As ULong, 值 As String) '给属性赋值 Declare Property tWString(位置 As ULong) As StringW '返回属性,超过“尺寸”字符会被截断,遇到0也截断,不支持带0 的字符,自己控制字符长度(1字符占2字节),避免覆盖后面数据 Declare Property tWString(位置 As ULong, 值 As Wstring) '给属性赋值 这个位置,就是字节为单位,比方 long 类型占4个字节,你要使用 2个 Long 那么 a.tLong(0) = 100 ‘第1个 a.tLong(4) = 100 ‘第2个 因此,共享内存中需要保存什么数据,需要自己计算好位置进行读写。
2026年-1月-22日
206 阅读
0 评论
VisualFreeBasic编程文档
2026-1-22
VisualFreeBasic进程通信
进程通信(IPC,Inter-Process Communication)是指操作系统中不同进程之间交换数据、传递信息或协同工作的机制。由于进程在内存中相互隔离(独立的地址空间),无法直接访问彼此的资源,因此需要通过操作系统提供的特定接口实现交互。 一、进程通信的核心目的 数据交换:进程间传递数据(如文件内容、网络数据等)。 同步协作:协调多个进程的执行顺序(如避免同时修改共享资源)。 资源共享:多个进程共享硬件或软件资源(如打印机、数据库连接)。 通知事件:一个进程向另一个进程发送事件信号(如 “数据处理完成”“错误发生”)。 二、常见进程通信方式及特点 不同操作系统(Windows、Linux、macOS 等)支持的 IPC 机制略有差异,但核心类型一致,按效率和适用场景可分为以下几类: 1. 管道(Pipe) 原理:创建一个单向的 “管道文件”(内存中的缓冲区),一个进程写入数据,另一个进程读取数据,类似 “水管”。 分类: 匿名管道:仅用于父子进程或兄弟进程(同一父进程创建),生命周期随进程结束。 命名管道(FIFO):可用于任意进程,通过唯一文件名标识,支持跨权限访问。 特点: 半双工(单向传输),双向通信需创建两个管道。 数据流式传输(无结构),需自定义协议解析。 适用于简单的短消息传递(如命令行程序交互)。 2. 消息队列(Message Queue) 原理:操作系统维护一个 “消息链表”,进程可按类型发送 / 接收消息(带结构的数据块)。 特点: 数据有结构(包含类型标识),支持按类型读取。 消息可持久化(进程退出后消息不丢失)。 避免管道的 “忙等” 问题,但效率低于共享内存。 适用场景:进程间非实时、多类型消息传递(如日志收集、任务调度)。 3. 共享内存(Shared Memory) 原理:操作系统划出一块物理内存,映射到多个进程的虚拟地址空间,进程直接读写该内存区域。 特点: 效率最高(无需内核中转,直接访问内存)。 需配合同步机制(如互斥锁、信号量)避免数据冲突。 32 位与 64 位进程共享时需注意数据类型兼容性。 适用场景:高频、大数据量交互(如游戏引擎与渲染进程、视频处理)。 4. 信号量(Semaphore) 原理:一个计数器,用于控制多个进程对共享资源的访问(如限制同时操作资源的进程数量)。 核心操作: P():计数器减 1,若为负则阻塞进程(等待资源)。 V():计数器加 1,若有阻塞进程则唤醒一个(释放资源)。 特点:本身不传递数据,仅用于同步和互斥。 适用场景:保护共享资源(如打印机、数据库连接池)。 5. 信号(Signal) 原理:操作系统向进程发送的 “事件通知”(如中断信号),进程可自定义处理函数。 常见信号: SIGINT(Ctrl+C):终止进程。 SIGTERM:请求进程终止。 SIGUSR1/SIGUSR2:用户自定义信号。 特点:异步通知,传递信息少(仅信号编号)。 适用场景:紧急事件处理(如程序异常终止、超时提醒)。 6. 套接字(Socket) 原理:通过网络协议(TCP/UDP)实现进程通信,支持同一主机或跨网络的进程。 特点: 跨平台、跨网络(最通用的 IPC 方式)。 支持可靠传输(TCP)或高效传输(UDP)。 效率低于共享内存,但灵活性最高。 适用场景:网络通信(如客户端与服务器、分布式系统)。 7. 其他方式 内存映射文件:将磁盘文件映射到内存,多进程通过读写文件实现共享(类似共享内存,但数据可持久化)。 D-Bus:Linux 桌面环境常用,基于消息的高级 IPC,支持服务注册与发现。 管道通信的扩展:如 Windows 的 “命名管道” 支持双向通信,Linux 的 “socketpair” 创建双向管道。 三、不同场景的选择策略 需求场景 推荐 IPC 方式 核心原因 父子进程简单通信 匿名管道 无需额外标识,创建简单 任意进程多类型消息 消息队列 支持按类型读取,无需轮询 高频大数据交互 共享内存 + 信号量 效率最高,配合同步避免冲突 跨网络 / 跨主机通信 套接字(TCP/UDP) 通用性最强,支持分布式系统 资源访问控制 信号量 专门用于同步,避免资源竞争 紧急事件通知 信号 异步响应,适合处理异常情况 四、核心挑战与解决 同步与互斥:多进程访问共享资源时需避免 “数据竞争”,需用信号量、互斥锁(Mutex)等机制控制访问顺序。 数据格式一致:不同进程(尤其是 32 位与 64 位)需约定统一的数据结构(如用固定长度类型int32_t),避免解析错误。 权限控制:确保通信对象有权限访问 IPC 资源(如命名管道的读写权限、共享内存的访问控制列表)。 总结 进程通信是多任务操作系统的核心功能,选择合适的 IPC 方式需权衡效率、复杂度、适用场景:简单交互用管道或信号,高频大数据用共享内存,跨网络用套接字。实际开发中,常组合多种方式(如共享内存 + 信号量)以兼顾效率与安全性。
2026年-1月-22日
222 阅读
0 评论
VisualFreeBasic编程文档
2026-1-22
VisualFreeBasic多线程编程
多线程编程是指在一个进程内创建多个线程,让它们并发执行不同任务的编程技术。线程作为进程内的执行单元,共享进程的内存空间(如全局变量、堆内存),但拥有独立的栈和程序计数器,这使得线程间通信更高效,却也带来了同步与冲突的挑战。 一、为什么需要多线程? 单线程程序在执行耗时操作(如网络请求、文件读写)时会陷入阻塞,导致整个程序 “卡壳”。多线程的核心价值在于: 提升资源利用率:当一个线程因 I/O 操作阻塞时,其他线程可继续利用 CPU 资源。 响应速度更快:例如 GUI 程序中,用后台线程处理数据,主线程保持界面流畅。 简化编程模型:相比多进程,线程共享内存,无需复杂的 IPC 机制传递数据。 二、线程的核心特性 特性 说明 共享性 线程共享进程的地址空间(全局变量、堆、文件描述符等),通信成本低。 独立性 每个线程有独立的栈(存储局部变量、函数调用)和程序计数器(执行位置)。 轻量级 线程创建 / 销毁的开销远小于进程(无需复制地址空间)。 调度由 OS 管理 操作系统的线程调度器负责分配 CPU 时间片,线程可被抢占(抢占式调度)。 三、多线程编程的核心问题 多线程的优势源于共享内存,但也因此引发以下关键问题: 1. 线程安全:数据竞争与临界区 当多个线程同时读写共享资源(如全局变量、缓存)时,可能导致数据不一致,这种现象称为数据竞争。例如:两个线程同时执行count++(实际是 “读取 - 修改 - 写入” 三步操作),可能导致最终结果少加 1。 临界区:访问共享资源的代码段,需要通过同步机制保证同一时间只有一个线程进入。 2. 同步机制:确保线程有序协作 为避免数据竞争,需用同步工具控制线程对临界区的访问,常见方式包括: 互斥锁(Mutex): 最常用的同步工具,通过 “加锁” 和 “解锁” 控制临界区: 线程进入临界区前先尝试加锁,成功则独占资源,失败则阻塞等待。 离开时解锁,唤醒等待的线程。 注意:需避免忘记解锁(导致死锁)或锁粒度过大(降低并发效率)。 条件变量(Condition Variable): 用于线程间的 “等待 - 通知” 机制。例如:线程 A 需等待线程 B 完成数据准备后再执行,可通过条件变量让 A 阻塞,B 完成后唤醒 A。 信号量(Semaphore): 与进程间的信号量类似,通过计数器控制同时访问资源的线程数量(如限制 5 个线程同时操作数据库连接)。 原子操作(Atomic Operation): 由 CPU 直接支持的不可分割的操作(如count++可通过std::atomic<int>实现原子递增),无需加锁,效率更高,适合简单的数值操作。 3. 死锁:线程互相等待资源 当线程 A 持有锁 1 并等待锁 2,线程 B 持有锁 2 并等待锁 1 时,两者会永远阻塞,这就是死锁。避免死锁的核心原则: 按顺序加锁:所有线程按固定顺序(如按锁的地址从小到大)获取多个锁。 限时等待:尝试加锁时设置超时时间,超时则释放已持有的锁并重试。 最小化锁持有时间:尽快释放锁,减少冲突概率。 四、多线程的适用场景 并非所有场景都适合多线程,以下是典型适用场景: I/O 密集型任务:如网络爬虫(大量时间等待网页响应)、文件批量处理(等待磁盘读写),多线程可在等待时切换执行其他任务。 异步事件处理:如服务器程序用线程池处理多个客户端请求,避免单线程阻塞。 后台任务:如软件中的自动保存、日志上传,用后台线程不影响主线程交互。 而CPU 密集型任务(如大规模计算)在单核 CPU 上用多线程可能因切换开销降低效率,多核环境下可通过多线程利用多核资源(需注意负载均衡)。 五、多线程编程的常见模型 线程池:预先创建一定数量的线程,任务到来时分配给空闲线程,避免频繁创建销毁线程的开销(适用于任务数量多、生命周期短的场景)。 生产者 - 消费者模型:一个 / 多个线程(生产者)生成数据,放入共享缓冲区;另一个 / 多个线程(消费者)从缓冲区取数据处理,通过条件变量协调两者节奏。 读写锁:针对 “读多写少” 场景优化,允许多个线程同时读,但写时需独占(读锁共享,写锁排他),比普通互斥锁效率更高。 六、跨语言的多线程支持 几乎所有主流编程语言都提供多线程库,例如: C/C++:pthread(跨平台)、std::thread(C++11 标准库)。 Java:java.lang.Thread、java.util.concurrent(含线程池、锁等工具)。 Python:threading模块(但受 GIL 限制,CPU 密集型任务需用多进程辅助)。 Go:通过goroutine(轻量级线程)和channel(通信机制)简化多线程编程,避免显式锁操作。 总结 多线程编程是提升程序并发能力的关键技术,其核心是在共享资源的高效利用与线程安全之间找到平衡。实际开发中,需根据任务类型(I/O 密集型 / CPU 密集型)选择合适的线程模型,熟练运用锁、条件变量等同步工具,并警惕死锁、数据竞争等陷阱。合理的多线程设计能显著提升程序的响应速度和资源利用率,但过度使用或设计不当则会导致代码复杂、难以调试。
2026年-1月-22日
240 阅读
0 评论
VisualFreeBasic编程文档
2026-1-22
VisualFreeBasic游戏趣味编程_11.4_显示绘制过程动画
为了可以显示出逐渐绘制樱花的过程,增加全局变量: Dim Shared isShowAnimation As Long = 1 '是否显示树生成的过程动画 在brunch()函数中加入代码,如果isShowAnimation等于1,每次运行brunch()后调用FlushBatchDraw()绘制,这样就可以显示逐渐绘制樱花树的过程动画了: If isShowAnimation Then '如果为1,绘制樱花树生成的过程动画 gg.Redraw Sleep 10 End If 在update()函数中增加代码,点击鼠标右键可以切换是否显示中间绘制的过程动画: If IsKeyPress(VK_RBUTTON) Then isShowAnimation = isShowAnimation=0 Sleep 300 End If 完整代码 '全局变量定义 Dim Shared offsetAngle As Single = 30 ' 左右枝干和父枝干偏离的角度 Dim Shared shortenRate As Single = 0.65 ' 左右枝干长度与父枝干长度的比例 Dim Shared isShowAnimation As Long = 1 '是否显示树生成的过程动画 '枝干生成和绘制递归函数 '输入参数:枝干起始x,y坐标,枝干角度,第几代 Sub brunch(gg As yGDI, x_start As Single, y_start As Single, length As Single, angle As Single, thickness As Single, generation As Long) ' 利用三角函数求出当前枝干的终点x,y坐标 Dim As Single x_end, y_end If angle > 360 Then angle -= 360 If angle < 0 Then angle += 360 x_end = x_start + length * Cos(angle * (3.1419265 / 180)) y_end = y_start + length * Sin(angle * (3.1419265 / 180)) gg.Brush gg.Pen thickness, HSBtoRGB_Gdi(HSB(randBetween(300, 350), 100, 50 + generation * 5, 255)) ' gg.DrawLine(x_start, y_start, x_end, y_end) ' 画出当前枝干(画线) ' 求出子枝干的代数 Dim childGeneration As Long = generation + 1 '生成子枝干的长度,逐渐变短 Dim childLength As Single = shortenRate *length Dim leftChildLength As Single = childLength *randBetween(0.9, 1.1) Dim rightChildLength As Single = childLength *randBetween(0.9, 1.1) Dim centerChildLength As Single = childLength *randBetween(0.8, 1.1) If childLength <= 2 Or childGeneration >= 9 Then gg.Pen 1, HSBtoRGB_Gdi(HSB(325, 50, 100, 255)) '设定线条颜色为粉色 gg.Brush HSBtoRGB_Gdi(HSB(325, 50, 100, 255)) '设定填充颜色为粉色 If childLength <= 4 Then gg.DrawEllipse x_end -2, y_end -2, 2 * 2, 2 * 2 '圆的半径为2(再小就看不清了) Else gg.DrawEllipse x_end - childLength / 2, y_end - childLength / 2, childLength, childLength '画一个圆,半径为子枝干长度的一半 End If End If If isShowAnimation Then '如果为1,绘制樱花树生成的过程动画 gg.Redraw Sleep 10 End If If IsKeyPress(VK_RBUTTON) Then isShowAnimation = isShowAnimation=0 Sleep 300 End If ' 当子枝干长度大于2,并且代数小于或等于10,递归调用产生子枝干 If childLength >= 2 And childGeneration <= 9 Then '生成子枝干的粗细,逐渐变细 Dim childThickness As Single = thickness * 0.8 If (childThickness < 1) Then childThickness = 1 ' 枝干绘图最细的线宽为2 If (randBetween(0, 1) < 0.95) Then ' 一定概率生成左边的子枝干 brunch(gg, x_end, y_end, leftChildLength, angle - randBetween(15, 60), childThickness, childGeneration) End If If (randBetween(0, 1) < 0.95) Then ' 一定概率生成右边的子枝干 brunch(gg, x_end, y_end, rightChildLength, angle + randBetween(15, 60), childThickness, childGeneration) End If If (randBetween(0, 1) < 0.85) Then '一定概率生成中间的子枝干 brunch(gg, x_end, y_end, centerChildLength, angle + randBetween( -15, 15), childThickness, childGeneration) End If End If End Sub '生成[min,max]之间的随机小数 Function randBetween(xiao As Single, da As Single) As Single Randomize Dim t As Single = rnd() '生成[0,1]的随机小数 '把值范围从[0,1]映射到[min,max] Dim r As Single = t * (da - xiao) + xiao Return r End Function Sub 游戏执行过程(hWndForm As hWnd) Dim gg As yGDI = hWndForm Do gg.Cls BGR(255, 255, 255) '白色背景 brunch(gg, DpiUnScaleF(Me.ScaleWidth / 2), DpiUnScaleF(Me.ScaleHeight), 0.45 *DpiUnScaleF(Me.ScaleHeight) *shortenRate, 270, 15 *shortenRate, 1) gg.Redraw Sleep 500 Do If IsKeyPress(VK_LBUTTON) Then Exit Do Sleep 100 Loop Loop End Sub
2026年-1月-22日
139 阅读
0 评论
VisualFreeBasic编程文档
2026-1-22
VisualFreeBasic游戏趣味编程_11.3_绘制樱花树
修改练习题11-1中的brunch()函数,当第10代树枝或树枝长度小于2时,绘制一个粉色填充圆,表示樱花: If childLength <= 2 Or childGeneration >= 9 Then gg.Pen 1, HSBtoRGB_Gdi(HSB(325, 50, 100, 255)) '设定线条颜色为粉色 gg.Brush HSBtoRGB_Gdi(HSB(325, 50, 100, 255)) '设定填充颜色为粉色 If childLength <= 4 Then gg.DrawEllipse x_end -2, y_end -2, 2 * 2, 2 * 2 '圆的半径为2(再小就看不清了) Else gg.DrawEllipse x_end - childLength / 2, y_end - childLength / 2, childLength, childLength '画一个圆,半径为子枝干长度的一半 End If End If 另外,为了使得生成的樱花树更加稠密,除了产生左右子枝干外,再生成一个中间的子枝干: brunch(gg, x_end, y_end, childLength, angle , childThickness, childGeneration) 下面我们引入一些随机性,首先定义函数randBetween( )生成[min,max]之间的随机小数: '生成[min,max]之间的随机小数 Function randBetween(xiao As Single, da As Single) As Single Randomize Dim t As Single = rnd() '生成[0,1]的随机小数 '把值范围从[0,1]映射到[min,max] Dim r As Single = t * (da - xiao) + da Return r End Function 左、右、中间3个子枝干的长度逐渐变短,并有一定随机性: Dim childLength As Single = shortenRate *length Dim leftChildLength As Single = childLength *randBetween(0.9, 1.1) Dim rightChildLength As Single = childLength *randBetween(0.9, 1.1) Dim centerChildLength As Single = childLength *randBetween(0.8, 1.1) 有一定概率产生左、右、中间的子枝干,并且子枝干的旋转角度也有一定的随机性: If (randBetween(0, 1) < 0.95) Then ' 一定概率生成左边的子枝干 brunch(gg, x_end, y_end, leftChildLength, angle - randBetween(15, 60), childThickness, childGeneration) End If If (randBetween(0, 1) < 0.95) Then ' 一定概率生成右边的子枝干 brunch(gg, x_end, y_end, rightChildLength, angle + randBetween(15, 60), childThickness, childGeneration) End If If (randBetween(0, 1) < 0.85) Then '一定概率生成中间的子枝干 brunch(gg, x_end, y_end, centerChildLength,angle+randBetween(-15, 15) , childThickness, childGeneration) End If 进一步,让树枝越向上、颜色越淡: gg.Pen thickness, HSBtoRGB_Gdi(HSB(325, 100, 50 + generation * 5, 255)) '设定线条颜色为黑色 樱花颜色也加上一定的随机性: gg.Pen thickness, HSBtoRGB_Gdi(HSB(randBetween(300, 350), 100, 50 + generation * 5, 255)) ' 当点击鼠标后,重新画树 Do If IsKeyPress(VK_LBUTTON) Then Exit Do Sleep 100 Loop 完整代码 '全局变量定义 Dim Shared offsetAngle As Single = 30 ' 左右枝干和父枝干偏离的角度 Dim Shared shortenRate As Single = 0.65 ' 左右枝干长度与父枝干长度的比例 '枝干生成和绘制递归函数 '输入参数:枝干起始x,y坐标,枝干角度,第几代 Sub brunch(gg As yGDI, x_start As Single, y_start As Single, length As Single, angle As Single, thickness As Single, generation As Long) ' 利用三角函数求出当前枝干的终点x,y坐标 Dim As Single x_end, y_end If angle > 360 Then angle -= 360 If angle < 0 Then angle += 360 x_end = x_start + length * Cos(angle * (3.1419265 / 180)) y_end = y_start + length * Sin(angle * (3.1419265 / 180)) gg.Brush gg.Pen thickness, HSBtoRGB_Gdi(HSB(randBetween(300, 350), 100, 50 + generation * 5, 255)) ' gg.DrawLine(x_start, y_start, x_end, y_end) ' 画出当前枝干(画线) ' 求出子枝干的代数 Dim childGeneration As Long = generation + 1 '生成子枝干的长度,逐渐变短 Dim childLength As Single = shortenRate *length Dim leftChildLength As Single = childLength *randBetween(0.9, 1.1) Dim rightChildLength As Single = childLength *randBetween(0.9, 1.1) Dim centerChildLength As Single = childLength *randBetween(0.8, 1.1) If childLength <= 2 Or childGeneration >= 9 Then gg.Pen 1, HSBtoRGB_Gdi(HSB(325, 50, 100, 255)) '设定线条颜色为粉色 gg.Brush HSBtoRGB_Gdi(HSB(325, 50, 100, 255)) '设定填充颜色为粉色 If childLength <= 4 Then gg.DrawEllipse x_end -2, y_end -2, 2 * 2, 2 * 2 '圆的半径为2(再小就看不清了) Else gg.DrawEllipse x_end - childLength / 2, y_end - childLength / 2, childLength, childLength '画一个圆,半径为子枝干长度的一半 End If End If ' 当子枝干长度大于2,并且代数小于或等于10,递归调用产生子枝干 If childLength >= 2 And childGeneration <= 9 Then '生成子枝干的粗细,逐渐变细 Dim childThickness As Single = thickness * 0.8 If (childThickness < 1) Then childThickness = 1 ' 枝干绘图最细的线宽为2 If (randBetween(0, 1) < 0.95) Then ' 一定概率生成左边的子枝干 brunch(gg, x_end, y_end, leftChildLength, angle - randBetween(15, 60), childThickness, childGeneration) End If If (randBetween(0, 1) < 0.95) Then ' 一定概率生成右边的子枝干 brunch(gg, x_end, y_end, rightChildLength, angle + randBetween(15, 60), childThickness, childGeneration) End If If (randBetween(0, 1) < 0.85) Then '一定概率生成中间的子枝干 brunch(gg, x_end, y_end, centerChildLength, angle + randBetween( -15, 15), childThickness, childGeneration) End If End If End Sub '生成[min,max]之间的随机小数 Function randBetween(xiao As Single, da As Single) As Single Randomize Dim t As Single = rnd() '生成[0,1]的随机小数 '把值范围从[0,1]映射到[min,max] Dim r As Single = t * (da - xiao) + xiao Return r End Function Sub 游戏执行过程(hWndForm As hWnd) Dim gg As yGDI = hWndForm Do gg.Cls BGR(255, 255, 255) '白色背景 brunch(gg, DpiUnScaleF(Me.ScaleWidth / 2), DpiUnScaleF(Me.ScaleHeight), 0.45 *DpiUnScaleF(Me.ScaleHeight) *shortenRate, 270, 15 *shortenRate, 1) gg.Redraw Sleep 500 Do If IsKeyPress(VK_LBUTTON) Then Exit Do Sleep 100 Loop Loop End Sub
2026年-1月-22日
124 阅读
0 评论
VisualFreeBasic编程文档
2026-1-22
VisualFreeBasic游戏趣味编程_11.2_分形与递归
雪花、果实、闪电、叶子、树枝、河道等自然界中很多对象的图形都具有以下两个特征: (1)整体上看,物体图形是处处不规则的; (2)在不同尺度上,图形的结构又有一定的相似性。 如图11-5所示。 满足这些特征的图形可以称为分形(Fractal),图11-6展示了用分形方法绘制一棵树的过程: 绘制过程可抽象为如下步骤。 (1)绘制一个树干。 (2)绘制其左边的子树干,绘制其右边的子树干。 (3)当到第n代树干时停止生成子树干。 输入并运行以下代码: '枝干生成和绘制递归函数 '输入参数:枝干起始x,y坐标,枝干角度,第几代 Sub brunch(gg As yGDI, x_start As Single, y_start As Single, angle As Single, generation As Long) ' 利用三角函数求出当前枝干的终点x,y坐标 Dim As Single x_end, y_end If angle > 360 Then angle -= 360 If angle < 0 Then angle += 360 x_end = x_start + 50 * Cos(angle * (3.1419265 / 180)) y_end = y_start + 50 * Sin(angle * (3.1419265 / 180)) gg.DrawLine(x_start, y_start, x_end, y_end) ' 画出当前枝干(画线) ' 求出子枝干的代数 Dim childGeneration As Long = generation + 1 ' 当子枝干并且代数小于或等于4,画出当前枝干,并递归调用产生子枝干 If (childGeneration <= 4) Then ' 产生左右的子枝干 brunch(gg, x_end, y_end, angle + 30, childGeneration) brunch(gg, x_end, y_end, angle - 30, childGeneration) End If End Sub Sub 游戏执行过程(hWndForm As hWnd) Dim gg As yGDI = hWndForm gg.Cls BGR(255, 255, 255) '白色背景 gg.Pen 2,BGR(0,0,0) '设定线条颜色为黑色 brunch(gg,DpiUnScaleF(Me.ScaleWidth/2),DpiUnScaleF(Me.ScaleHeight), 270,1) gg.Redraw End Sub 定义函数Sub brunch绘制起点坐标(x_start, y_start)、长度50、角度angle、代数generation的树枝。主函数中调用brunch(gg,DpiUnScaleF(Me.ScaleWidth/2),DpiUnScaleF(Me.ScaleHeight), 270,1)绘制主枝干。 brunch()函数内部,首先利用三角函数求出当前枝干的终点坐标(x_end,y_end),利用gg.DrawLine(x_start, y_start, x_end, y_end) 绘制当前枝干线条。 利用三角函数求出当前枝干的终点x,y坐标 x_end = x_start + 50 * Cos(angle * (3.1419265 / 180)) y_end = y_start + 50 * Sin(angle * (3.1419265 / 180)) 然后对子枝干的代数加1,如果代数小于或等于4,则通过递归调用绘制左、右子枝干,两个子枝干的角度在父枝干基础上偏移 30度(angle * (3.1419265 / 180) 是 角度转换弧度,因为代码中的参数是弧度) brunch(gg, x_end, y_end, angle + 30, childGeneration) brunch(gg, x_end, y_end, angle - 30, childGeneration) 实现效果如图11-7所示。 进一步改进代码,使得子枝干的长度逐渐变短,枝干画线逐渐变细: '全局变量定义 Dim Shared offsetAngle As Single = 30 ' 左右枝干和父枝干偏离的角度 Dim Shared shortenRate As Single = 0.65 ' 左右枝干长度与父枝干长度的比例 '枝干生成和绘制递归函数 '输入参数:枝干起始x,y坐标,枝干角度,第几代 Sub brunch(gg As yGDI, x_start As Single, y_start As Single, length As Single, angle As Single, thickness As Single, generation As Long) ' 利用三角函数求出当前枝干的终点x,y坐标 Dim As Single x_end, y_end If angle > 360 Then angle -= 360 If angle < 0 Then angle += 360 x_end = x_start + length * Cos(angle * (3.1419265 / 180)) y_end = y_start + length * Sin(angle * (3.1419265 / 180)) gg.Pen thickness, BGR(0, 0, 0) '设定线条颜色为黑色 gg.DrawLine(x_start, y_start, x_end, y_end) ' 画出当前枝干(画线) ' 求出子枝干的代数 Dim childGeneration As Long = generation + 1 '生成子枝干的长度,逐渐变短 Dim childLength As Single = shortenRate *length ' 当子枝干长度大于2,并且代数小于或等于10,递归调用产生子枝干 If childLength >= 2 And childGeneration <= 9 Then '生成子枝干的粗细,逐渐变细 Dim childThickness As Single = thickness * 0.8 If (childThickness < 1) Then childThickness = 1 ' 枝干绘图最细的线宽为2 '产生左右的子枝干 brunch(gg, x_end, y_end, childLength, angle + offsetAngle, childThickness, childGeneration) brunch(gg, x_end, y_end, childLength, angle - offsetAngle, childThickness, childGeneration) End If End Sub Sub 游戏执行过程(hWndForm As hWnd) Dim gg As yGDI = hWndForm gg.Cls BGR(255, 255, 255) '白色背景 brunch(gg, DpiUnScaleF(Me.ScaleWidth / 2), DpiUnScaleF(Me.ScaleHeight), 0.45 *DpiUnScaleF(Me.ScaleHeight) *shortenRate, 270, 15 *shortenRate, 1) gg.Redraw End Sub 程序运行后输出如图11-8所示。 练习题11-1:尝试将鼠标位置的x坐标用于调整子枝干和父枝干之间偏离角度,鼠标位置的y坐标用于调整树枝的高度。当用户移动鼠标时,可以绘制出不同高低形态的分形树,如图11-9所示。
2026年-1月-22日
109 阅读
0 评论
VisualFreeBasic编程文档
2026-1-22
VisualFreeBasic游戏趣味编程_11.1_递归
函数在定义时可以调用其他的函数。输入并运行以下代码: Sub fun1() Print "a" End Sub Sub fun2() fun1() Print "a" End Sub Sub 游戏执行过程(hWndForm As hWnd) fun2() Print "c" End Sub 程序运行后输出: a b c 程序运行流程如图11-2所示。 (1)代码首先从主函数开始运行,调用fun2()函数。 (2)进入fun2()函数内部。 (3)在fun2()内,首先调用fun1()函数。 (4)进入fun1()函数内部。 (5)在fun1()内,首先输出“a”。 (6)fun1()运行结束,返回到fun2()函数内部。 (7)在fun2()内部继续运行,输出“b”。 (8)fun2()运行结束,返回到main()函数内部。 (9)游戏执行过程()函数内部继续运行,输出“c”,End Sub 程序结束。 一个函数直接或间接地调用自身的形式被称为递归调用,比如,求一个整数n的阶乘n !=n(n-1)(n-2) 可以转换为递归调用的形式: 当n大于1时,n的阶乘等于n乘以n-1的阶乘;当n=1时,n的阶乘等于1。定义求阶乘函数fac()如下: Function fac(n As Long) As Long Dim f As Long If n = 1 Then f = 1 Else f = n *fac(n -1) End If Function = f End Function Sub 游戏执行过程(hWndForm As hWnd) Dim num As Long = fac(5) Print "5!=" & num End Sub 主函数中调用fac(5),程序运行后输出:5!=120 程序运行流程如图11-3所示。 (1)从主函数中调用fac(5)进入fac()函数内部,n=5,是大于1的,因此fac(5)=5*fac(4)。 (2)调用fac(4)进入fac()函数内部,n=4,是大于1的,因此fac(4)=4*fac(3)。 (3)调用fac(3)进入fac()函数内部,n=3,是大于1的,因此fac(3)=3*fac2)。 (4)调用fac(2)进入fac()函数内部,n=2,是大于1的,因此fac(2)=2*fac(1)。 (5)调用fac(1)进入fac()函数内部,n=1使得fac(1)=1,fac(1)运行结束。 (6)返回fac(2),即fac(2)=2fac(1)=21=2,fac(2)运行结束。 (7)返回fac(3),即fac(3)=3fac(2)=32=6,fac(3)运行结束。 (8)返回fac(4),即fac(4)=4fac(3)=46=24,fac(4)运行结束。 (9)返回fac(5),即fac(5)=5fac(4)=524=120,fac(5)运行结束。 (10)返回 游戏执行过程() 函数,最终输出120。 提示 要使用函数递归调用,首先需能写成递归调用的形式,比如,求n的阶乘可以转换为求n-1阶乘。另外需要有结束递归的条件,比如,n=1时结束求阶乘递归调用,否则程序会一直重复运行。 汉诺塔问题:有3根针A、B、C。A针上有n个盘子。盘子大小不等,大的在下,小的在上。要求将n个盘子从A针移到C针,可以借助B针,每次只许移动1个盘子,3根针上始终保持大盘在下小盘在上。 汉诺塔问题是可用递归求解的一个经典问题,将n个盘子从A针移到C针可分解为3个步骤。 (1)将A上n-1个盘子借助C针移到B针。 (2)将A针上剩下的一个盘子移到C针。 (3)将B针上的n-1个盘子借助A移到C针。 其中,1、3的操作是相同的,只是针的名称不同,因此3个步骤可分成两类操作: (1)将n-1个盘子从一根针移到另一根针上(n>1); (2)将一个盘子从一根针移到另一根针上。 分别用两个函数实现以上两个操作: (1)hanoi(n,one,two,three)表示将n个盘子从one针借助two 移到three针; (2)move(getone,putone) 表示将一个盘子从getone针移到putone针。 one、two、three、getone、putone都代表A、B、C之一,根据各次不同情况取A、B、C代入。 Sub move(x As String, y As String) Print "move " & x & " To " & y End Sub Sub hanoi(n As Long, a As String, b As String, c As String) If n = 1 Then move(a, c) Else hanoi(n -1, a, c, b) move(a, c) hanoi(n -1, b, a, c) End If End Sub Sub 游戏执行过程(hWndForm As hWnd) Dim n As Long =3 Print "3个盘子演示" hanoi(n,"A","B","C") End Sub 输入3,程序运行后输出3个盘子的汉诺塔问题操作步骤:
2026年-1月-22日
65 阅读
0 评论
VisualFreeBasic编程文档
2026-1-22
VisualFreeBasic游戏趣味编程_第11章_樱花树
在本章我们将探讨如何绘制一些漂亮的樱花树,如图11-1所示。通过鼠标交互设定樱花树的高度和分散程度,鼠标右键点击设置是否显示过程动画,鼠标左键点击开始绘制。 本章首先介绍了递归的概念,以及如何实现汉诺塔问题的求解;然后介绍了分形的概念,以及如何利用递归调用绘制一棵分形树;最后讲解了如何修改分形树的生成与绘制参数,实现随机樱花树的绘制。
2026年-1月-22日
56 阅读
0 评论
VisualFreeBasic编程文档
6
7
8
9
10