
Mojo 上手教程代码详解从 List 到参数化 Grid 构建 Conway 生命游戏【免费下载链接】mojoThe Modular Platform (includes MAX Mojo)项目地址: https://gitcode.com/GitHub_Trending/mo/mojo本文基于 Mojo 官方手册的Get started with Mojo教程配套代码目录 Mojo/docs/site/code/manual/get-started 展开。该目录是教程 Get started with Mojo 的官方参考实现通过逐步构建 Conway 生命游戏细胞自动机覆盖var绑定、comptime常量、lambda、参数化struct、__init__(out self)初始化、索引协议__getitem__/__setitem__、mut self、staticmethod、转移语义^等 Mojo 核心语法。读完本文你将掌握这套教程的完整代码演进路径、每步的测试验证方式tests.mojo以及配套的 Bazel 构建目标定义。一、目录结构与文件职责README.md 说明该目录存放Get started with Mojo教程的代码示例与测试完整参考方案另见 examples/life 目录部署到公开仓库的图形化版本。当前目录实际包含文件职责grid.mojo教程最终形态的Gridstruct双缓冲演化、坐标校验、随机块状生成life.mojo教程终点的动画主循环300 代演化 终端清屏tests.mojo对教程各步骤代码片段的断言测试BUILD.bazel定义可运行的tests二进制与tests_test测试目标需要说明README 中还列出了life01.mojo教程第一步的独立可运行版本及life01、life、life01_test等构建目标但对照当前仓库快照BUILD.bazel 实际只定义了testsmojo_binarytestonly True和tests_testmodular_run_binary_testsize small两个目标life01.mojo文件未出现在目录中。这说明该 README 描述略超前于当前代码快照以实际文件为准load(//bazel:api.bzl, modular_run_binary_test, mojo_binary) package(default_visibility [//oss/modular/docs:__subpackages__]) # Assertions over the tutorials code examples. mojo_binary( name tests, testonly True, srcs [ grid.mojo, tests.mojo, ], deps [ mojo//:std, ], ) modular_run_binary_test( name tests_test, size small, binary tests, )要点tests二进制同时编译grid.mojo与tests.mojo后者通过from grid import Grid复用前者的成品 struct依赖标准库mojo//:stdtests_test只是运行该二进制并断言其退出码。二、第一步用List[Int]表达游戏状态教程的第一阶段对应 tests.mojo 中test_glider_grid所固化的片段在一个 8×8 网格里摆出 glider滑翔机图案此时Grid类型尚不存在用一维列表 局部lambda完成坐标到线性索引的转换def main(): var count, num_cols 64, 8 var glider_grid: List[Int] ListInt # Convert (x, y) coordinates to a linear index in the grid var to_index lambda (x: Int, y: Int) - Int: y * num_cols x # Set up the grid for coord in [(0, 1), (1, 2), (2, 0), (2, 1), (2, 2)]: glider_grid[to_index(coord[0], coord[1])] 1 # Print the grid for index in range(count): print(X if glider_grid[index] else ., end) if index % num_cols (num_cols - 1): print()运行输出..X..... X.X..... .XX..... ........ ........ ........ ........ ........这一步确立了几条基础语法规则教程 Checkpoint 总结所有变量声明以var开头var绑定默认可变to_index是一个小型局部函数lambda把 (x, y) 坐标映射为列表索引列表表达式直接赋值var values: List[Int] [12, -7, 64]参数化类型名中方括号提供编译期参数如List[Int]、Grid[8, 8]圆括号提供运行期实参如Grid[8, 8]()、print(value)。tests.mojo用test_to_index_lambda固化了索引换算to_index(0, 1) 8、to_index(7, 7) 63即用y * num_cols x的行主序布局。三、重构成 comptime常量与转换提升到文件级教程第二步把print_grid抽成独立函数第三步进一步把常量与坐标转换提升到模块级的comptime声明comptime count 64 comptime num_cols 8 comptime num_rows count // num_cols comptime to_index lambda (x: Int, y: Int) - Int: ( y * num_cols x ) comptime to_coord lambda (i: Int) - Tuple[Int, Int]: ( (i % num_cols, i // num_cols) ) def print_grid(grid: List[Int]): for index in range(len(grid)): print(X if grid[index] else ., end) if index % num_cols (num_cols - 1): print() def main(): var glider_grid: List[Int] ListInt for coord in [(0, 1), (1, 2), (2, 0), (2, 1), (2, 2)]: glider_grid[to_index(coord[0], coord[1])] 1 print_grid(glider_grid)Checkpoint 强调常量移出main()后在整个文件可用且在编译期求值print_grid也去掉了第二个参数改用文件级num_cols。tests.mojo 中test_comptime_conversions对此做了严格验证并补充了一个值得注意的实现细节# The declared Tuple[Int, Int] return type converts i // num_cols # back to an Int, truncating toward negative infinity. var x, y to_coord(18) assert_equal(x, 2) assert_equal(y, 2)即 lambda 声明的返回类型Tuple[Int, Int]会对i // num_cols做一次转换向负无穷截断测试随后让 0~63 的每个索引在to_coord与to_index之间双向往返round-trip保证两个映射互逆。四、定义参数化Gridstruct教程第四步把网格收敛为真正的类型——参数化struct对应 grid.mojo 的最简形态struct Grid[num_cols: Int, num_rows: Int]: var cells: List[Int] var count: Int def __init__(out self): self.count Self.num_cols * Self.num_rows self.cells ListInt def main(): var glider_grid Grid[8, 8]()关键语义教程 Checkpoint tests.mojotest_grid_init/test_grid_parameters佐证Grid同时使用编译期参数num_cols、num_rows经由Self.访问与运行期字段cells、count经由self.访问__init__(out self)负责初始化新实例每个字段在初始化器返回前必须被赋值out self表示初始化器产出的是全新构造的对象测试验证了count跟踪参数Grid[4, 6]().count 24、Grid[60, 20]().count 1200且cells初始全 0若初始化器只是把参数直接写入字段可改用fieldwise_init让 Mojo 自动生成初始化器本例需要自定义初始化逻辑计算count、分配列表因此显式定义__init__()。五、给 Grid 加索引协议与坐标转换第五步向Grid内添加 comptime 转换与print_grid/__setitem__/__getitem__当前 grid.mojo 的对应实现comptime to_index lambda (x: Int, y: Int) - Int: ( y * Self.num_cols x ) comptime to_coord lambda (i: Int) - Tuple[Int, Int]: ( (i % Self.num_cols, i // Self.num_cols) ) def print_grid(self): for index in range(len(self.cells)): print(X if self.cells[index] else ., end) if index % Self.num_cols (Self.num_cols - 1): print() def __setitem__(mut self, coord: Tuple[Int, Int], value: Int): self.cells[Self.to_index(coord[0], coord[1])] value def __getitem__(self, coord: Tuple[Int, Int]) - Int: return self.cells[Self.to_index(coord[0], coord[1])]Self.to_index()访问的是类型的编译期成员因此可以直接以Grid[8, 8].to_index(2, 1)的静态方式调用test_grid_static_conversions断言其结果为 10实例方法首参为selfmut self表示方法可修改实例__setitem__必须有它__getitem__/__setitem__定义索引行为之后调用方即可写glider_grid[(0, 1)] 1签名不同的重载方法可以共存调用点预览for coord in [(0, 1), (1, 2), (2, 0), (2, 1), (2, 2)]: glider_grid[coord] 1 # Uses indexingtest_grid_indexing验证了glider_grid[(2, 1)] 1后底层cells[10] 1直接印证了行主序y * num_cols x布局2 1×8 10。第六步是模块化拆分从grid.mojo移除main()在 life.mojo 中from grid import Grid用List([...])显式构造坐标序列后再写入网格。当前life.mojo已演进到终点形态见第九节但该导入 Grid步骤被 tests.mojo 顶部的from grid import Grid覆盖头部注释说明这属于源码布局步骤无需单独断言。六、随机化seed 规则与越界保护6.1 seed() 不能放在全局作用域教程指出Mojo 不允许在全局作用域出现运行期语句因此seed()必须放在main()等可执行代码中from std.random import seed from grid import Grid def main(): seed() # ...seed 对导入的 random 模块全局生效只需播种一次。6.2 坐标校验setter 防护getter 不造数随机填格会频繁越界教程要求在 setter 中加校验当前实现见 grid.mojodef is_valid_coord(self, coord: Tuple[Int, Int]) - Bool: return not ( coord[0] 0 or coord[0] Self.num_cols or coord[1] 0 or coord[1] Self.num_rows ) def __setitem__(mut self, coord: Tuple[Int, Int], value: Int): if not self.is_valid_coord(coord): return # no op self.cells[Self.to_index(coord[0], coord[1])] value教程明确的设计取向getter 可以加检查但不应让__getitem__()悄悄返回编造的越界值——正确的做法是索引前先测试坐标。test_is_valid_coord与test_setitem_out_of_bounds_is_a_no_op固化了这一点对(-1, 0)、(0, -1)、(8, 0)、(0, 8)四个越界坐标写入后整个网格仍全为 0没有任何回卷写入。6.3random_grid静态方法与转移返回随机生成采用块状clumps策略每块以某个随机中心点为核8 邻域中以 25% 概率激活避免纯随机噪点from std.random import random_si64 # Add this import to grid.mojo staticmethod def random_grid(var clumps: Int 2) - Self: var grid Self() for _ in range(clumps 1): var idx Int(random_si64(0, Int64(grid.count) - 1)) var x, y Self.to_coord(idx) grid[(x, y)] 1 # Fill index cell for dx in range(-1, 2): # -1, 0, or 1 for dy in range(-1, 2): if not grid.is_valid_coord((x dx, y dy)): continue if random_si64(0, 3) 0: continue # 75% skip grid[(x dx, y dy)] 1 return grid^Checkpoint 逐条解释tests.mojo 中test_random_grid_single_clump/test_random_grid_default_clumps固化了不变量staticmethod属于类型而非实例通过Grid[8, 8].random_grid()调用仍需类型参数8 和 8循环clumps 1次默认参数 2 即生成 3 块_是丢弃模式discard patternrandom_si64()返回Int64需显式转换为Int末尾的^记号return grid^转移新建的Grid避免拷贝——这是 Mojo 移动语义的典型用法随机性不可逐格断言测试只验证不变量单块时存活数在 1~9 之间中心点 至多 8 邻居默认参数时在 1~27 之间。七、演化规则双缓冲与 evolve 重载Conway 规则三条活细胞有 2 或 3 个活邻居则存活死细胞恰好 3 个活邻居则诞生其余一律死亡。为避免同代互相污染更新结果写入独立的next_cells缓冲当前 grid.mojo 的完整实现def evolve(mut self, var i: Int): var is_live Bool(self.cells[i]) self.next_cells[i] 0 # Count the neighbors var ncount -1 if is_live else 0 var x, y Self.to_coord(i) for dx in range(-1, 2): for dy in range(-1, 2): var nx, ny x dx, y dy if not self.is_valid_coord((nx, ny)): continue ncount self.cells[Self.to_index(nx, ny)] # Live cell stays alive with two or three live neighbors if is_live and (ncount 2 or ncount 3): self.next_cells[i] 1 # Dead cell becomes alive with exactly three live neighbors elif not is_live and ncount 3: self.next_cells[i] 1 def evolve(mut self): for i in range(self.count): var x, y Self.to_coord(i) # Edges are excluded from evolution and will always go dead. if (x 0 or y 0 or x Self.num_cols - 1 or y Self.num_rows - 1): self.next_cells[i] 0 # Edges go dead continue self.evolve(i) # Swap the current and next cell states self.cells self.next_cells^ # Transfer self.next_cells ListInt # Fresh实现要点教程 Checkpoint 测试佐证同名重载表达不同意图evolve(i)只算单格evolve()遍历全网格这是为不同意图重载的典型示例边界不参与演化四条边缘细胞直接置 0test_evolve_edges_go_dead验证整圈边缘在一代后全灭-1 if is_live else 0技巧活细胞的邻居计数从 -1 起步加上 3×3 窗口含自身后恰好得到邻居数省去特殊分支教程同时说明 Mojo 的三元表达式是value if condition else value没有 Elvis 操作符双缓冲的转移收尾演化结束后self.cells self.next_cells^用转移替换旧状态不拷贝再把next_cells重新初始化为全 0 的新列表tests.mojo 用四种经典图案验证规则正确性2×2 方块是静物test_evolve_still_life竖直 blinker 一代后变水平、再一代变回test_evolve_blinker_oscillates孤立细胞死亡underpopulation恰好三邻居的死格诞生birth。八、运行模拟模板字符串与 ANSI 清屏教程终点是 life.mojo与当前仓库文件一致from std.random import seed from grid import Grid from std.time import sleep def main(): seed() var grid Grid[60, 20].random_grid(240) var gen 1 for _ in range(300): print(t\033[H\033J\nGeneration: {gen}) grid.evolve(); grid.print_grid() sleep(0.1); gen 1Checkpoint 说明t...是模板字符串TString直接内插{gen}print()会自动做字符串转换其他场景需显式调用String()\033[H\033J是 ANSI 转义码光标回原点 清屏实现逐代刷新建议终端至少 80×24Mojo 的分号用于分隔同一行的语句而非结束语句此处两两配对仅为紧凑排版。test_print_grid_runs在 tests.mojo 中只验证抽出的print_grid能编译并跑通教程网格输出到 stdout 不便断言头部注释同时列出了明确不测试的部分及原因print_grid的实际 stdout 输出改由同构的render()函数累计字符串后断言GLIDER_8X8常量即教程前两步的期望渲染文本、seed()的跨运行差异、以及main()动画循环ANSI、sleep、300 代循环无可断言的结果。九、与 examples/life 参考方案的关系README 指出完整参考方案在 examples/life。对照其 BUILD.bazel该目录包含lifev1gridv1.mojo lifev1.mojo与lifev2gridv2.mojo lifev2.mojo两代实现依赖pygame-ce做图形化渲染testonly True仅供构建/测试benchmark同时引用 gridv1 与 gridv2 的 benchmark.mojotest_gridv1/test_gridv2mojo_testsize smallmain指向 test/test_gridv1.mojo 与 test/test_gridv2.mojo。从源码结构看本目录get-started是纯终端输出的最小教学实现而examples/life在其基础上引入图形窗口与性能基准两者共享同一套Grid演化模型。若希望继续阅读图形化扩展或对比两代实现可直接查看Mojo/examples/life/下的相应文件。小结get-started 目录以List lambda → comptime 常量 → 参数化 struct → 索引协议 → 边界防护 → 双缓冲演化 → 终端动画的六步递进覆盖了 Mojo 值语义var、mut self、out self、^转移、编译期/运行期划分comptime参数与Self./self.访问以及 stdlib 的std.random、std.time、std.testing用法每一步的关键行为都在 tests.mojo 中固化为可断言的测试glider 渲染文本、索引往返、越界 no-op、静物/blinker/诞生/边缘死亡并通过tests_test目标接入构建系统构建与运行均以仓库内 Bazel 目标为准//Mojo/docs/site/code/manual/get-started:tests_test路径对应上述BUILD.bazel教程正文则以 get-started.mdx 为权威来源。【免费下载链接】mojoThe Modular Platform (includes MAX Mojo)项目地址: https://gitcode.com/GitHub_Trending/mo/mojo创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考