# game-2048 **Repository Path**: slinoone/game-2048 ## Basic Information - **Project Name**: game-2048 - **Description**: rust 实现一个 命令行版本的 2048 游戏 - **Primary Language**: Rust - **License**: MulanPSL-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2025-12-26 - **Last Updated**: 2025-12-26 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 从零开始用Rust实现2048游戏:一次完整的Rust学习之旅 > 本文将通过实现一个完整的2048游戏,带你深入了解Rust语言的核心特性。适合有一定编程基础但想学习Rust的开发者。 ## 前言 Rust作为一门现代系统编程语言,以其**内存安全**、**零成本抽象**和**并发安全**等特性,在开发者社区中越来越受欢迎。但Rust的学习曲线相对陡峭,特别是**所有权系统**和**生命周期**等概念。 今天,我们将通过实现一个经典的2048游戏,在实践中学习Rust的核心概念。这个项目涵盖了: - 结构体和实现(Struct & Impl) - 所有权和借用(Ownership & Borrowing) - 模式匹配(Pattern Matching) - 迭代器(Iterator) - 类型系统 ### 游戏效果预览 让我们先看看最终实现的效果: ![2048游戏运行效果](image.png) 如上图所示,游戏实现了完整的2048功能,包括: - 清晰的ASCII艺术风格游戏界面 - 实时分数显示 - 游戏状态检测(胜利/失败) - 流畅的用户交互体验 ## 项目准备 首先,创建一个新的Rust项目: ```bash cargo new game-2048 cd game-2048 ``` 在 `Cargo.toml` 中添加依赖: ```toml [dependencies] rand = "0.8" ``` ## 核心代码实现 ### 1. 基础类型定义 ```rust use std::io::{self, Write}; // 游戏板的大小(4x4) const BOARD_SIZE: usize = 4; // 游戏板类型:二维数组,每个位置存储一个数字(0表示空格) type Board = [[u32; BOARD_SIZE]; BOARD_SIZE]; ``` **Rust知识点:** - `const`:定义编译时常量,必须指定类型 - `usize`:Rust中用于索引和长度的无符号整数类型,在不同平台上大小不同(32位或64位) - `type`:类型别名,让代码更易读 - `[[u32; BOARD_SIZE]; BOARD_SIZE]`:固定大小的二维数组,Rust中数组大小必须在编译时确定 ### 2. 游戏结构体 ```rust // 游戏结构体:包含游戏板和分数 struct Game { board: Board, score: u32, } ``` **Rust知识点:** - `struct`:定义自定义数据类型 - 结构体字段默认是私有的,可以通过 `pub` 关键字公开 ### 3. 实现游戏逻辑 #### 3.1 构造函数 ```rust impl Game { // 创建新游戏 fn new() -> Self { let mut game = Game { board: [[0; BOARD_SIZE]; BOARD_SIZE], score: 0, }; // 在游戏开始时添加两个随机数字 game.add_random_tile(); game.add_random_tile(); game } } ``` **Rust知识点:** - `impl`:为类型实现方法 - `Self`:当前类型的简写,等同于 `Game` - `mut`:可变性关键字,Rust中变量默认**不可变**,这是Rust安全性的重要保证 - 函数最后一个表达式(没有分号)作为返回值 #### 3.2 添加随机数字 ```rust // 在空白位置随机添加一个数字(2或4,90%概率是2) fn add_random_tile(&mut self) { let empty_cells: Vec<(usize, usize)> = (0..BOARD_SIZE) .flat_map(|i| (0..BOARD_SIZE).map(move |j| (i, j))) .filter(|&(i, j)| self.board[i][j] == 0) .collect(); if empty_cells.is_empty() { return; } // 随机选择一个空白位置 use rand::Rng; let mut rng = rand::thread_rng(); let (i, j) = empty_cells[rng.gen_range(0..empty_cells.len())]; // 90%概率生成2,10%概率生成4 self.board[i][j] = if rng.gen_bool(0.9) { 2 } else { 4 }; } ``` **Rust知识点详解:** 1. **借用(Borrowing)**:`&mut self` 表示可变借用 - `&self`:不可变借用,只能读取 - `&mut self`:可变借用,可以修改 - 这是Rust所有权系统的核心:**同一时间只能有一个可变借用或多个不可变借用** 2. **迭代器链式调用**: ```rust (0..BOARD_SIZE) // 范围迭代器:0, 1, 2, 3 .flat_map(|i| ...) // 扁平化映射 .filter(|&(i, j)| ...) // 过滤条件 .collect() // 收集为Vec ``` - `0..BOARD_SIZE`:范围语法,左闭右开 - `flat_map`:将嵌套迭代器扁平化 - `filter`:过滤满足条件的元素 - `collect`:将迭代器收集为集合类型 3. **闭包(Closure)**:`|i| (0..BOARD_SIZE).map(move |j| (i, j))` - `move` 关键字:强制闭包获取变量的所有权 - 闭包是Rust中函数式编程的重要工具 4. **元组解构**:`let (i, j) = empty_cells[...]` - Rust支持元组解构,方便同时获取多个值 #### 3.3 核心移动逻辑 这是游戏最核心的部分,我们通过实现向左移动,然后通过旋转实现其他方向: ```rust // 向左移动一行(这是核心逻辑,其他方向会复用这个函数) fn move_row_left(row: &mut [u32; BOARD_SIZE]) -> bool { let mut moved = false; let mut write_pos = 0; // 第一步:移除所有0,将非0数字向左压缩 for read_pos in 0..BOARD_SIZE { if row[read_pos] != 0 { if read_pos != write_pos { row[write_pos] = row[read_pos]; row[read_pos] = 0; moved = true; } write_pos += 1; } } // 第二步:合并相同的相邻数字 for i in 0..(BOARD_SIZE - 1) { if row[i] != 0 && row[i] == row[i + 1] { row[i] *= 2; // 合并:数字翻倍 row[i + 1] = 0; // 清空合并后的位置 moved = true; } } // 第三步:再次压缩(因为合并后可能产生新的0) write_pos = 0; for read_pos in 0..BOARD_SIZE { if row[read_pos] != 0 { if read_pos != write_pos { row[write_pos] = row[read_pos]; row[read_pos] = 0; } write_pos += 1; } } moved } ``` **算法思路:** 1. **压缩阶段**:将所有非零数字向左移动,移除中间的零 2. **合并阶段**:相邻的相同数字合并,值翻倍 3. **再次压缩**:合并后可能产生新的零,需要再次压缩 **Rust知识点:** - `&mut [u32; BOARD_SIZE]`:可变数组切片引用 - 返回 `bool` 表示是否发生了移动 #### 3.4 通过旋转实现其他方向 为了代码复用,我们通过旋转游戏板来实现其他方向的移动: ```rust // 旋转游戏板90度(用于实现其他方向的移动) fn rotate_board(board: &mut Board) { let mut rotated = [[0; BOARD_SIZE]; BOARD_SIZE]; for i in 0..BOARD_SIZE { for j in 0..BOARD_SIZE { // 顺时针旋转90度:新位置[j][BOARD_SIZE-1-i] = 原位置[i][j] rotated[j][BOARD_SIZE - 1 - i] = board[i][j]; } } *board = rotated; // 解引用赋值 } // 向右移动(通过旋转实现) fn move_right(&mut self) -> bool { // 旋转180度 Self::rotate_board(&mut self.board); Self::rotate_board(&mut self.board); let moved = self.move_left(); // 旋转回来 Self::rotate_board(&mut self.board); Self::rotate_board(&mut self.board); moved } ``` **设计模式:** - 通过旋转将其他方向的移动转换为向左移动 - 减少代码重复,提高可维护性 #### 3.5 模式匹配处理用户输入 ```rust // 执行移动 fn make_move(&mut self, direction: char) -> bool { let moved = match direction { 'w' | 'W' | '↑' => self.move_up(), 's' | 'S' | '↓' => self.move_down(), 'a' | 'A' | '←' => self.move_left(), 'd' | 'D' | '→' => self.move_right(), _ => false, // 默认情况,匹配所有其他值 }; if moved { self.update_score(); self.add_random_tile(); } moved } ``` **Rust知识点:** - `match`:强大的模式匹配表达式 - `|`:在模式匹配中表示"或" - `_`:通配符,匹配所有其他情况 - `match` 必须**穷尽所有可能**,这是Rust安全性的体现 ### 4. 主函数 ```rust fn main() { println!("欢迎来到 2048 游戏!"); println!("按 Enter 键开始..."); let mut input = String::new(); io::stdin().read_line(&mut input).unwrap(); let mut game = Game::new(); let mut won = false; loop { game.display(); // 检查是否获胜 if !won && game.is_win() { println!("🎉 恭喜!你达到了 2048!"); // ... 处理获胜逻辑 } // 检查游戏是否结束 if game.is_game_over() { println!("游戏结束!最终分数: {}", game.score); break; } // 获取用户输入并执行移动 // ... } } ``` **Rust知识点:** - `String::new()`:创建新的字符串 - `read_line(&mut input)`:读取一行输入,需要可变引用 - `unwrap()`:如果Result是Err则panic,生产代码中应使用 `?` 或 `match` 处理错误 - `loop`:无限循环,必须通过 `break` 退出 ## Rust核心特性总结 通过这个项目,我们实践了Rust的以下核心特性: ### 1. 所有权系统(Ownership) - **每个值只有一个所有者** - 当所有者离开作用域,值会被自动释放 - 通过借用(`&` 和 `&mut`)实现多引用 ### 2. 可变性(Mutability) - 变量默认不可变,需要 `mut` 关键字才能修改 - 这是Rust防止意外修改的重要机制 ### 3. 类型系统 - 静态类型,编译时检查 - 类型推断,减少冗余代码 - 零成本抽象,性能与C/C++相当 ### 4. 模式匹配 - `match` 表达式必须穷尽所有情况 - 强大的解构能力 - 编译时保证安全性 ### 5. 迭代器 - 惰性求值,高效 - 链式调用,代码简洁 - 函数式编程风格 ## 运行游戏 ```bash cargo run ``` 使用 WASD 或方向键控制移动,输入 'q' 退出游戏。 ### 游戏运行效果 运行游戏后,你会看到类似下面的界面: ![2048游戏运行效果](image.png) 游戏界面特点: - **清晰的网格布局**:使用ASCII字符绘制4x4游戏板 - **实时分数显示**:顶部显示当前累计分数 - **操作提示**:显示可用的控制方式 - **胜利检测**:达到2048时会显示祝贺信息 - **游戏结束检测**:无法移动时自动结束游戏 ## 扩展建议 如果你想进一步学习,可以尝试: 1. **添加颜色支持**:使用 `colored` crate 让游戏更美观 2. **保存/加载游戏**:学习Rust的文件I/O和序列化(`serde`) 3. **添加AI玩家**:实现自动求解算法 4. **Web版本**:使用 `wasm-bindgen` 编译到WebAssembly 5. **GUI版本**:使用 `egui` 或 `iced` 创建图形界面 ## 总结 通过实现2048游戏,我们学习了: - ✅ Rust的基础语法和类型系统 - ✅ 所有权和借用机制 - ✅ 结构体和方法实现 - ✅ 迭代器和函数式编程 - ✅ 模式匹配 - ✅ 错误处理基础 Rust虽然学习曲线陡峭,但其**内存安全**、**并发安全**和**零成本抽象**的特性,使其成为系统编程的绝佳选择。希望这个项目能帮助你更好地理解Rust的核心概念! --- **完整代码已上传到Gitee,欢迎Star和Fork!** 如果你在学习过程中遇到问题,欢迎在评论区讨论。也欢迎关注我,我会持续分享Rust学习心得和实战经验。