ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

基于ESP32的康威生命游戏实现:嵌入式开发与算法优化实战

基于ESP32的康威生命游戏实现:嵌入式开发与算法优化实战 1. 项目概述在掌上设备中复活生命游戏最近在整理我的电子积木抽屉翻出了几块吃灰的M5StickC Plus2开发板。这玩意儿巴掌大小却集成了彩色屏幕、按键、电池和ESP32芯片总让人觉得不拿来做点有趣的东西就浪费了。恰好我一直在琢磨如何用最直观的方式向朋友解释“元胞自动机”和“涌现”这些听起来有点玄乎的概念。于是一个想法冒了出来为什么不把经典的康威生命游戏Conway‘s Game of Life搬到这块小屏幕上让它成为一个可以随身携带、随时把玩的“数字生命沙盒”呢这个项目我称之为“AUTOMATA VITAE”生命自动机。康威生命游戏是一个零玩家游戏它的规则极其简单在一个二维网格上每个细胞格子根据其周围八个邻居的状态遵循“出生”、“存活”或“死亡”的规则演化。然而就是这几条简单的规则却能演化出滑翔机、飞船、振荡器等复杂且迷人的模式完美诠释了“整体大于部分之和”的涌现现象。将这样一个充满哲学和数学美感的系统塞进一个可以握在手中的ESP32设备里看着像素点构成的“生命”在方寸屏幕间生生不息本身就是一件极具吸引力的事情。这个项目非常适合有一定Arduino或ESP32开发基础的爱好者尤其是那些对算法可视化、嵌入式图形或复杂系统感兴趣的朋友。通过它你不仅能深入理解生命游戏的算法实现还能掌握在资源受限的嵌入式设备上进行图形渲染、性能优化和交互设计的实战技巧。整个过程就像在微型的数字宇宙中扮演造物主看着自己设定的初始条件如何引发出乎意料的复杂图景。2. 硬件选型与开发环境搭建2.1 为什么是M5StickC Plus2在众多ESP32开发板中选择M5StickC Plus2作为本项目载体是经过一番考量的。首先它是一款高度集成的“即插即用”型设备。核心是一颗ESP32-PICO-D4芯片双核240MHz主频、520KB SRAM、4MB Flash处理一个80x160分辨率的生命游戏网格绰绰有余。其自带的1.14英寸IPS彩色屏幕135x240像素提供了良好的视觉输出基础而内置的锂聚合物电池和充电管理电路则让项目彻底摆脱线缆束缚成为一个真正的便携式装置。其次它的物理形态非常适合交互。板载的两个物理按键电源键和M5按键和一个红外发射器为控制游戏如暂停、清屏、切换模式提供了天然的输入接口。你不需要额外焊接任何元件开箱即可进入开发。最后M5Stack成熟的生态系统和丰富的库支持能极大降低开发门槛让我们可以更专注于游戏逻辑本身而不是底层驱动调试。当然你也可以用其他ESP32开发板搭配OLED屏幕来实现但M5StickC Plus2这种“All-in-One”的设计在完成度、便携性和美观度上有着无可比拟的优势。它让项目从一个“实验原型”快速升级为一个“可展示的产品”。2.2 开发环境PlatformIO VS Code 实战配置虽然Arduino IDE简单易用但对于稍复杂的项目其工程管理、库依赖和代码跳转功能就显得力不从心了。因此我强烈推荐使用PlatformIO核心 VS Code编辑器的组合。这不仅是当前ESP32开发社区的“事实标准”也能为你未来的项目打下更专业的基础。首先在你的电脑上安装VS Code。然后在VS Code的扩展商店中搜索并安装“PlatformIO IDE”。安装完成后侧边栏会出现一个蚂蚁头图标这就是PlatformIO。创建一个新项目点击PIO主页的“New Project”。在“Board”输入框中搜索“M5Stick-C Plus”选择正确的型号。框架Framework选择“Arduino”。选择好项目存放路径点击“Finish”。PlatformIO会自动为你生成一个标准的项目结构包括src源代码、include头文件、lib库目录和最重要的platformio.ini项目配置文件。初始化的过程可能会因为网络问题而缓慢这是因为它正在后台下载对应的工具链和框架。一个加速技巧是可以事先配置使用国内镜像源具体方法是在用户目录下的.platformio文件夹中修改platformio.ini的配置或者使用环境变量。接下来是关键一步为项目添加必要的库。M5StickC Plus2需要M5StickCPlus2这个专用库来驱动其硬件。你不需要手动下载PlatformIO的库依赖管理非常方便。打开platformio.ini文件在[env]部分添加以下行lib_deps m5stack/M5StickCPlus2 ^1.1.1保存文件后PlatformIO会自动解析并安装该库。有时库的版本号需要你到PlatformIO的库注册中心去查一下最新版。至此一个专为M5StickC Plus2优化的开发环境就搭建好了。相比于纯Arduino IDE你现在拥有了强大的代码补全、语法检查、一键上传和串口监控功能开发效率会提升好几个量级。注意如果你遇到VS Code无法跳转到M5库头文件的问题这通常是IntelliSense配置缓存未更新的缘故。尝试点击VS Code右下角的“正在加载”图标选择“重新扫描工作空间”或者执行CtrlShiftP打开命令面板输入“C/C: 编辑配置(UI)”来手动添加包含路径。更彻底的方法是关闭VS Code删除项目根目录下的.vscode文件夹和build文件夹然后重新打开项目让PlatformIO重新生成配置。3. 核心算法设计与数据结构3.1 生命游戏规则的精炼与代码化康威生命游戏的规则可以用四句话概括任何“活细胞”如果拥有少于两个活邻居则因“孤独”而死亡。任何“活细胞”如果拥有两到三个活邻居则存活到下一代。任何“活细胞”如果拥有超过三个活邻居则因“拥挤”而死亡。任何“死细胞”如果恰好拥有三个活邻居则“复活”为活细胞。在代码中实现这些规则核心是遍历整个网格为每个细胞计算其周围的活邻居数量然后根据当前状态和邻居数决定其下一代的状态。这里有一个至关重要的技巧不能边计算边更新当前网格。因为下一代细胞的状态完全依赖于当前这一代所有细胞的“快照”。如果你在计算过程中直接修改了当前网格那么后续细胞计算邻居时参考的就已经是部分更新的、不正确的状态了。因此标准的做法是使用“双缓冲区”Double Buffering。我们定义两个完全相同的二维数组currentGrid和nextGrid。在每一代的计算中我们只读取currentGrid的数据将所有下一代的状态计算结果写入nextGrid。当一整代计算完毕后再将nextGrid的内容整体复制到currentGrid中或者更高效地交换两个缓冲区的指针。最后根据新的currentGrid来更新屏幕显示。3.2 网格表示与内存优化策略M5StickC Plus2的屏幕是135x240像素。如果我们让一个细胞对应一个像素那么网格就是135行、240列。用一个二维布尔数组bool grid[135][240]来表示似乎很直观但这会占用135 * 240 32400个字节约31.6KB。ESP32-PICO-D4的内部SRAM总共约520KB这看起来似乎没问题。但我们需要考虑双缓冲区内存占用会翻倍到63KB。同时程序代码、M5库、堆栈以及其他变量也需要内存。在嵌入式开发中内存是需要精打细算的资源。一个更节省内存的方法是使用一维数组或者使用位bit来存储细胞状态。一个细胞只有生/死两种状态完全可以用一个比特0或1表示。在C/C中我们可以使用uint8_t数组并手动进行位操作。例如定义一个uint8_t grid[135][30]的数组因为240列 / 8比特每字节 30字节。这样总内存占用为135 * 30 4050字节双缓冲区也才8KB左右节省了超过85%的内存这为程序运行和未来添加更多功能如模式存储留下了充足的空间。当然位操作会增加代码的复杂度。你需要编写函数来设置、获取特定位置的状态。例如获取第i行第j列细胞状态的伪代码bool getCellState(uint8_t grid[135][30], int i, int j) { int byteIndex j / 8; int bitIndex j % 8; return (grid[i][byteIndex] bitIndex) 0x01; }虽然多写几行代码但在嵌入式环境下这种用复杂度换取空间的做法是非常值得的。它直接决定了你的游戏能否运行得更流畅能否支持更大的网格或更长的演化时间。3.3 邻居计算与边界条件处理计算一个细胞的活邻居数量是算法中最频繁的操作。对于一个非边界的细胞它有8个邻居。最朴素的方法是写8个if判断语句。但我们可以用一个更优雅的方式使用一个预定义的邻居坐标偏移数组。const int dx[8] {-1, 0, 1, -1, 1, -1, 0, 1}; const int dy[8] {-1, -1, -1, 0, 0, 1, 1, 1};这样计算邻居数量的循环就非常清晰int count 0; for (int k 0; k 8; k) { int ni i dy[k]; int nj j dx[k]; // 检查边界并累加状态 if (ni 0 ni HEIGHT nj 0 nj WIDTH) { count getCellState(currentGrid, ni, nj); } }这里引出了另一个关键问题边界条件。网格的边界细胞没有完整的8个邻居。常见的处理方式有三种固定边界认为边界外永远是“死”细胞。这是最简单的方式实现如上代码所示只累加网格内的邻居。周期边界将网格视为一个环面Torus上下边界相连左右边界相连。这意味着从最左边移出会从最右边进入。这需要在对邻居坐标ni,nj做取模运算。复杂边界自定义边界行为。对于掌上生命游戏这个项目我推荐使用固定边界。因为它计算最快最符合直觉生命无法在虚空边缘生存并且能产生许多有趣的模式比如“宇宙尘埃”会逐渐被边界吸收。周期边界虽然在某些研究中很有用但计算稍慢且对于小屏幕的视觉体验提升不大。4. 图形渲染与性能优化实战4.1 利用M5.Display高效绘制像素M5StickC Plus2的M5.Display对象是对TFT_eSPI库的封装提供了丰富的绘图API。最直接的绘制方式是使用drawPixel(x, y, color)函数在循环中遍历所有细胞活细胞画白色死细胞画黑色。for (int y 0; y SCREEN_HEIGHT; y) { for (int x 0; x SCREEN_WIDTH; x) { if (getCellState(currentGrid, y, x)) { M5.Lcd.drawPixel(x, y, WHITE); } else { M5.Lcd.drawPixel(x, y, BLACK); } } }然而这种方法效率极低。drawPixel是一个相对耗时的调用每帧进行数万次135*24032400次会严重拖慢演化速度导致动画卡顿。优化策略一局部更新。生命游戏的特点是每一代中状态发生变化的细胞通常只占全体的一小部分。我们可以记录哪些细胞发生了变化只重绘这些细胞。这需要维护一个“脏矩形”列表或一个变化状态缓冲区。虽然逻辑变复杂了但在演化趋于稳定大部分细胞不再变化时能极大提升帧率。优化策略二使用drawFastHLine或块绘制。如果连续多个水平像素状态相同比如一片死区我们可以绘制一条水平线而不是逐个画点。我们可以扫描每一行找到状态相同的连续段然后用drawFastHLine一次性绘制。这能显著减少API调用次数。优化策略三推荐使用帧缓冲区直接操作。M5.Display底层有一个对应于屏幕内存的帧缓冲区Frame Buffer。我们可以直接访问和修改这个缓冲区。TFT_eSPI库提供了pushColor函数族但更高效的是使用startWrite()和endWrite()包裹一组绘制命令以及直接操作_img如果使用Sprite的话。对于生命游戏我们可以创建一个与屏幕同大的Sprite在Sprite的缓冲区上进行细胞状态的绘制这本质上是一个内存中的位图操作然后一次性将整个Sprite推送到屏幕。这种方法将数万次绘制调用减少到一次是性能提升的关键。4.2 帧率控制与演化速度调节生命游戏的魅力在于观察演化过程。演化太快就像快进看不清模式太慢又令人失去耐心。因此可控的演化速度是必须的。一个简单而有效的方法是使用millis()函数进行非阻塞延时。在loop()函数中unsigned long currentMillis millis(); if (currentMillis - previousMillis evolutionInterval) { previousMillis currentMillis; // 执行一代计算与绘制 computeNextGeneration(); renderGrid(); }这里evolutionInterval变量就是每一代之间的毫秒间隔。你可以将这个间隔与一个旋钮通过ADC读取电位器、一个按键短按加速、长按减速或者触摸手势绑定从而实现实时的速度调节。例如将M5StickC Plus2的M5按键设置为速度调节键单击暂停/继续双击增加速度长按减少速度。实操心得在调节速度时要注意计算一代所需的时间。如果evolutionInterval设置得小于计算一代所需的时间那么程序就会永远“追赶”实际帧率会低于设定值。最好在串口监视器中打印出computeNextGeneration()和renderGrid()的执行时间确保你的最小间隔大于这个时间。对于80x160的网格优化后的计算加渲染时间控制在十几毫秒到几十毫秒是完全可以做到的这意味着你可以实现每秒几十帧的流畅动画。4.3 色彩与视觉增强方案虽然经典的生命游戏是黑白两色但在彩色屏幕上我们可以玩出更多花样来增强视觉表现力同时传递更多信息。年龄着色不要只区分生死。可以为活细胞赋予“年龄”属性。新生的细胞用一种颜色如亮绿色存活了一代的细胞用另一种颜色如青色存活了很久的细胞用第三种颜色如蓝色。这样屏幕上会呈现出斑斓的、有历史层次感的图案你能一眼看出哪些是稳定的结构哪些是新生的活跃区域。渐变与平滑在渲染时不使用纯色块而是根据邻居数量或细胞年龄计算一个渐变的颜色值。这能让画面看起来更柔和、更具科技感。高亮变化在“局部更新”的基础上可以将刚刚死亡的细胞用红色高亮一闪将刚刚诞生的细胞用绿色高亮一闪。这种视觉反馈让演化的过程更加生动和清晰。背景网格线在初始化时用很深的灰色绘制一个浅浅的网格线可以帮助观察者更好地定位和识别复杂的模式比如滑翔机的移动轨迹。实现这些效果只需要在renderGrid函数中根据每个细胞的额外状态年龄、是否刚变化等来选择合适的颜色值而不是简单的黑白。这几乎不增加计算负担却能极大提升观赏性。5. 交互功能与模式预设实现5.1 硬件按键与手势控制设计M5StickC Plus2的物理资源有限如何用两个按键电源键通常不建议复用主要用M5键实现丰富的控制需要巧思。我设计了一套单击、双击、长按的组合逻辑M5键单击暂停/继续。这是最常用的功能让你可以随时定格有趣的瞬间仔细观察模式结构。M5键双击单步执行。在暂停状态下双击一次游戏向前演化一代。这是分析模式演化细节的利器。M5键长按1秒进入菜单/模式选择。长按后屏幕边缘出现菜单图标通过再次单击或结合六轴传感器的手势进行选择。电源键谨慎使用复位/清屏。由于电源键直接关联硬件通常保留其原有功能开关屏幕或深度睡眠。如果需要复用可以通过检测其按下时间短按来触发清屏或重置为随机初始状态但一定要做好防误触处理比如长按2秒才生效。除了按键M5StickC Plus2内置的六轴加速度计MPU6886可以用来实现手势控制。例如向左/向右倾斜调节演化速度。向上/向下倾斜滚动浏览预设模式列表。快速晃动一下随机生成新的初始图案。 这种体感交互让整个设备玩起来更像一个充满魔力的水晶球体验非常棒。实现上你需要读取M5.Imu.getAccelData()获取加速度值并设计一个简单的状态机来识别特定的动作模式。5.2 经典模式库的嵌入与调用让用户每次都从随机散点开始玩容易无聊。内置一个经典模式库可以让用户一键加载诸如“滑翔机机枪”、“脉冲星”、“繁殖者”等著名图案瞬间感受生命游戏的复杂性魅力。如何存储这些模式在资源紧张的MCU上我们不能存储一张张图片。最有效的方式是使用RLE编码或简单的坐标列表。例如一个“滑翔机”可以用它的相对坐标表示const int glider[5][2] { {1, 0}, {2, 1}, {0, 2}, {1, 2}, {2, 2} };这表示在某个锚点(0,0)的基础上这些位置是活细胞。加载时用户选择一个模式程序将这个模式“绘制”到网格的中央。我们可以定义一个结构体数组作为模式库struct Pattern { const char* name; int numCells; const int (*cells)[2]; // 指向坐标数组的指针 }; Pattern patternLibrary[] { {Glider, 5, glider}, {Lightweight Spaceship, 9, lwss}, // ... 更多模式 };通过按键或手势在菜单中浏览这些name选中后将对应的cells坐标应用到当前网格的中央区域。注意在应用前要先清空网格或提供一个确认选项。5.3 随机生成与初始状态创作除了预设模式随机生成初始状态是一个核心乐趣来源。最简单的就是遍历网格给每个细胞一个随机的生死概率比如50%。for (int i 0; i HEIGHT; i) { for (int j 0; j WIDTH; j) { setCellState(currentGrid, i, j, (random(100) 50)); // 50%概率为活 } }但完全随机的“白噪声”往往演化不出有趣的结构很快会陷入停滞或简单的振荡。更高级的生成算法可以提升“有趣图案”的产出率密度可控随机允许用户设置初始活细胞密度。对称生成生成具有水平、垂直或旋转对称性的图案这类图案更容易演化出有序结构。“种子”生成在屏幕中央放置一个小的高密度随机块让它向外生长和演化。从预设模式变形随机选取一个预设模式然后对其施加随机的旋转、翻转或添加/删除少量细胞。提供一个“摇一摇随机生成”或“定时自动重新随机化”的功能能让这个掌上设备变成一个永不重复的电子动态壁画常看常新。6. 电源管理与高级功能拓展6.1 低功耗策略与电池续航优化作为便携设备续航很重要。ESP32的功耗不低持续全速运行并点亮屏幕可能几个小时就会耗光电池。我们需要引入睡眠模式。一个简单的策略是无操作自动休眠。利用millis()检测用户最后一次操作按键或手势的时间。如果超过一段时间例如5分钟无操作则关闭屏幕背光M5.Lcd.setBrightness(0)或M5.Axp.ScreenBreath(0)。将ESP32置于轻睡眠模式。在轻睡眠下CPU暂停RAM数据保留功耗可以降到mA级别。可以通过定时器或外部中断比如按键唤醒。唤醒后恢复背光并从睡眠前的状态继续演化。更进一步的优化涉及计算频率。当设备检测到演化已进入一个稳定或循环状态连续多代网格无变化时可以自动降低演化帧率甚至暂停计算直到用户再次交互。注意事项进入深度睡眠Deep Sleep会丢失RAM中的所有数据这意味着你的游戏状态会完全丢失。除非你设计了一个在深度睡眠前将当前网格状态保存到RTC内存或EEPROM中的机制并在唤醒后读取恢复否则轻睡眠是更合适的选择。M5StickC Plus2的AXP192电源管理芯片提供了精细的电源控制能力值得深入研究其库函数以实现最佳能效。6.2 状态保存与加载功能想象一下你培养出了一个非常复杂有趣的生态系统设备却突然没电了。如果有状态保存功能就能避免这种遗憾。我们可以利用ESP32的Preferences库类似于非易失性存储NVS来保存关键数据。需要保存的信息可能包括当前网格状态压缩后存储例如用RLE编码。当前演化速度。当前选中的显示模式如颜色方案。由于NVS空间有限通常几百KB直接存储整个位图网格可能压力较大。因此存储时可以采用压缩算法。对于生命游戏网格由于大部分区域是死细胞使用游程编码RLE压缩率会非常高。保存时将网格状态编码成一个字符串或字节数组加载时再解码恢复网格。实现一个“保存快照”和“加载快照”的菜单项。你甚至可以扩展为多个存档位让用户可以保存多个不同的“世界”。6.3 无线通信与社交分享构想虽然本项目未直接实现但ESP32强大的Wi-Fi和蓝牙功能为未来扩展打开了大门。你可以思考以下有趣的方向Web配置界面让设备启动一个Wi-Fi热点用户手机连接后通过浏览器访问一个网页。在网页上可以上传自定义的RLE格式图案文件、调整游戏参数网格大小、规则变异、甚至远程控制设备的演化和显示。这利用了ESP32的异步网络服务器能力。蓝牙遥控器开发一个简单的手机App通过蓝牙向M5设备发送控制命令开始、停止、切换模式、发送新图案让手机成为设备的遥控器和图案编辑器。生命游戏“网络对战”概念性让多个M5设备连接到同一个Wi-Fi通过MQTT或UDP协议同步彼此的网格状态。可以尝试让两个不同的初始图案在“宇宙”中扩散、碰撞、相互作用看看会发生什么。这些高级功能将项目从一个独立的玩具升级为一个可交互、可扩展的复杂系统实验平台。7. 常见问题与调试心得实录7.1 编译与上传问题排查在开发过程中你肯定会遇到各种编译和上传错误。这里记录几个典型问题及其解决方法问题编译时报错“fatal error: M5StickCPlus2.h: No such file or directory”原因PlatformIO没有正确安装或找到M5StickCPlus2库。解决首先检查platformio.ini中的lib_deps是否正确。然后尝试在PIO主页的“Libraries”中搜索并安装。如果不行可以尝试删除项目下的.pio文件夹和platformio.ini中的lib_deps行重新添加让PIO强制刷新依赖。问题上传代码时一直卡在“Connecting...”阶段原因USB驱动问题、端口被占用或板子未进入下载模式。解决确保安装了正确的CP210x或CH340 USB转串口驱动。在设备管理器中确认COM端口号并在platformio.ini中通过upload_port COMxWindows或upload_port /dev/ttyUSBxLinux/Mac指定。对于M5StickC Plus2上传前需要按住主板正面的“电源键”非侧面的红色按键不放然后短按一下侧面的“复位键”RST待屏幕熄灭后再松开电源键此时板子会进入下载模式。这是最关键的一步。关闭可能占用串口的其他软件如串口监视器、Arduino IDE。问题程序运行不稳定偶尔重启看门狗复位原因loop()函数中某次循环执行时间过长触发了硬件看门狗定时器WDT。解决优化你的computeNextGeneration()和renderGrid()函数。确保它们不会陷入死循环。可以在循环中适当加入yield()或delay(0)让看门狗喂狗。使用millis()进行非阻塞延时避免使用delay()函数长时间阻塞程序。7.2 运行时性能分析与优化点如果你的动画感觉卡顿首先需要定位瓶颈。使用Serial.println(millis())在关键函数前后打印时间戳。计算瓶颈如果computeNextGeneration()耗时过长。优化检查邻居计算循环。确保边界检查在最内层循环高效。考虑使用查表法对于生命游戏一个细胞的下一状态只取决于当前状态和活邻居数0-8总共只有2*918种可能。可以预先计算好这个状态转移表nextState[2][9]这样计算下一代时只需要查表无需进行多个if判断这是极大的优化。代码示例// 预计算状态转移表nextState[当前状态][活邻居数] const bool nextState[2][9] { // 死细胞 (当前状态0) {0, 0, 0, 1, 0, 0, 0, 0, 0}, // 邻居数0-8只有3个邻居时复活 // 活细胞 (当前状态1) {0, 0, 1, 1, 0, 0, 0, 0, 0} // 邻居数0-82或3个邻居时存活 }; // 在计算细胞新状态时 bool current getCellState(currentGrid, i, j); int liveNeighbors countLiveNeighbors(currentGrid, i, j); bool newState nextState[current][liveNeighbors]; setCellState(nextGrid, i, j, newState);渲染瓶颈如果renderGrid()耗时过长。优化如前所述从drawPixel切换到Sprite缓冲绘制是最大的性能飞跃。确保使用startWrite()和endWrite()包裹批量绘制操作。如果实现了局部更新确保脏细胞检测的逻辑开销没有超过重绘全部细胞的成本。7.3 显示异常与内存不足排查问题屏幕显示花屏、错位或只有部分更新原因通常是帧缓冲区操作错误、内存越界或屏幕驱动初始化问题。解决检查网格坐标到屏幕坐标的映射是否正确确保没有x和y弄反或者索引超出屏幕范围。如果使用Sprite确保Sprite的创建大小与屏幕一致并且pushSprite的坐标参数正确。在setup()中确保M5.begin()被正确调用并且屏幕初始化如M5.Lcd.setRotation()无误。问题程序运行一段时间后崩溃串口输出“Guru Meditation Error”或内存相关错误原因堆内存碎片化导致分配失败或者栈溢出。解决避免在循环中动态分配内存如new,malloc或创建String对象。尽量使用全局或静态数组。如果使用了位图网格确保数组大小计算正确没有访问越界。使用ESP.getFreeHeap()、ESP.getMaxAllocHeap()等函数在串口监视器中监控内存使用情况观察是否在持续下降内存泄漏。如果使用了递归函数确保递归深度不会导致栈溢出。生命游戏的逻辑通常不需要递归。这个项目从构思到实现就像在微观世界里搭建了一个遵循简单物理定律的宇宙。最让我着迷的时刻不是代码成功运行的那一刻而是当我放入一个“滑翔机”种子然后静静看着它穿越由随机点构成的“星云”在屏幕上划出一道道优雅的轨迹甚至与其他结构碰撞产生出人意料的复杂结果时。它提醒我极简的规则与庞大的规模相结合便能孕育出近乎无限的复杂性。调试过程中优化算法和内存使用的每一个技巧都让我对嵌入式编程有了更深的理解。最后当你把这块装载着“数字生命”的小设备放在桌上看着它自顾自地演化你会觉得这不仅仅是一个程序更像是一个被赋予了基础规则后便自行生长的数字生态缸。
返回列表