“文字地牢”小游戏通关 Rust 入门-地图生成与迭代器
·
地图生成与迭代器
上文我们大致了解了"文字地牢"小游戏的行为抽象,那么我们本章的目标是用 rand 实现简单随机生成;通过迭代器链处理地图数据,掌握闭包与适配器方法。
基本概念
随机数生成
Rust通过 rand crate提供随机数生成功能,可以生成各种类型的随机数,包括整数、浮点数和布尔值。
随机数生成器类型:
- ThreadRng:线程本地的随机数生成器,适合大多数情况
- StdRng:标准的随机数生成器,可配置种子
- SmallRng:快速但密码学不安全的生成器
随机数分布:
- 均匀分布:
gen_range()生成指定范围内的随机数 - 布尔分布:
gen_bool()生成布尔值 - 选择分布:
choose()从集合中随机选择
迭代器
迭代器是Rust中一种强大的抽象,允许我们以声明式的方式处理序列数据。它们是惰性求值的,只有在需要时才计算下一个值。
迭代器的特性:
- 惰性求值:适配器方法不会立即执行
- 零成本抽象:编译器优化后与手写循环性能相当
- 组合性:可以链式调用多种适配器方法
- 安全性:编译时保证内存安全
迭代器类型:
- Iterator trait:定义迭代器行为的核心trait
- IntoIterator trait:允许类型被用于
for循环 - Consuming adapters:消耗迭代器并产生结果
- Iterator adapters:转换迭代器但不消耗
闭包
闭包是匿名函数,可以捕获其环境中的变量。在迭代器链中经常使用闭包来转换或过滤数据。
闭包的特点:
- 匿名性:不需要显式命名
- 捕获环境:可以访问定义时作用域中的变量
- 灵活的语法:参数和返回类型可以自动推断
- 多种捕获模式:可以不可变借用、可变借用或获取所有权
如何使用
随机数生成详解
use rand::{Rng, thread_rng, SeedableRng};
use rand::rngs::StdRng;
// 使用线程本地生成器
let mut rng = thread_rng();
let random_bool = rng.gen_bool(0.5);
let random_int = rng.gen_range(1..=100);
// 使用种子生成器(可重现)
let mut rng = StdRng::seed_from_u64(42);
let reproducible_value = rng.gen::<f64>();
// 不同分布的随机数
let uniform = rng.gen_range(1..=10);
let normal = rand_distr::Normal::new(0.0, 1.0).unwrap();
let sample = rng.sample(normal);
迭代器适配器详解
// 基本适配器
let numbers = vec![1, 2, 3, 4, 5];
let doubled: Vec<i32> = numbers.iter().map(|x| x * 2).collect();
let evens: Vec<&i32> = numbers.iter().filter(|&x| x % 2 == 0).collect();
// 复杂适配器链
let result: Vec<String> = (1..10)
.filter(|x| x % 2 == 0)
.map(|x| x * x)
.take(3)
.map(|x| x.to_string())
.collect();
// 扁平化适配器
let nested = vec![vec![1, 2], vec![3, 4, 5]];
let flat: Vec<i32> = nested.into_iter().flatten().collect();
// 分组和窗口
let data = vec![1, 2, 3, 4, 5];
let windows: Vec<&[i32]> = data.windows(2).collect();
let chunks: Vec<&[i32]> = data.chunks(2).collect();
闭包捕获模式
let x = 42;
// 不可变借用(默认)
let closure1 = || println!("x is {}", x);
// 可变借用
let mut y = 10;
let mut closure2 = || {
y += 1;
println!("y is {}", y);
};
// 获取所有权(move)
let z = String::from("hello");
let closure3 = move || println!("z is {}", z);
随机生成示例
use rand::{Rng, thread_rng};
let mut rng = thread_rng();
for y in 1..height-1 {
for x in 1..width-1 {
if rng.gen_bool(0.15) { tiles[index(width, x, y)] = Tile::Floor; }
}
}
迭代器与闭包
- 统计地板数量:
let floor_count = (0..height).flat_map(|y| (0..width).map(move |x| (x, y)))
.map(|(x,y)| map.get_tile(x,y))
.filter(|t| matches!(t, Tile::Floor))
.count();
map/filter/count组合简洁高效;注意闭包中捕获的所有权与借用。
注意事项
- 迭代器是惰性求值的,必须使用消费适配器(如
count、collect)才会执行。 - 注意迭代器链中所有权的转移,避免不必要的克隆。
- 随机数生成器应正确初始化,以确保随机性。
- 在使用闭包时,注意变量捕获的方式(借用或移动)。
- 对于性能敏感的代码,迭代器链通常比手动循环更高效。
- 使用
move闭包时要确保变量的所有权正确转移。 - 在迭代器链中处理错误时,考虑使用
filter_map或try_fold等方法。 - 对于大型数据集,考虑使用
rayoncrate进行并行迭代。
本项目中的使用
在我们的项目中,我们使用随机数生成来创建地图,并使用迭代器来处理地图数据:
use rand::{Rng, thread_rng};
let mut rng = thread_rng();
for y in 1..height-1 {
for x in 1..width-1 {
if rng.gen_bool(0.15) { tiles[index(width, x, y)] = Tile::Floor; }
}
}
我们还使用迭代器来统计地图中的地板数量:
let floor_count = (0..height).flat_map(|y| (0..width).map(move |x| (x, y)))
.map(|(x,y)| map.get_tile(x,y))
.filter(|t| matches!(t, Tile::Floor))
.count();
地图生成的改进方案
在地牢探险游戏中,我们可以使用更复杂的地图生成算法:
// 房间生成算法
fn generate_rooms(map: &mut Map, rng: &mut impl Rng) {
let room_count = rng.gen_range(3..=8);
for _ in 0..room_count {
let width = rng.gen_range(3..=8);
let height = rng.gen_range(3..=8);
let x = rng.gen_range(1..map.width - width - 1);
let y = rng.gen_range(1..map.height - height - 1);
// 挖掘房间
for ry in y..y+height {
for rx in x..x+width {
map.set_tile(rx, ry, Tile::Floor);
}
}
}
}
// 连接房间的隧道算法
fn connect_rooms(map: &mut Map, rooms: &[Room]) {
for window in rooms.windows(2) {
let (room1, room2) = (&window[0], &window[1]);
// 连接算法实现
}
}
迭代器在游戏中的其他应用
- 视野计算:
fn calculate_visible_tiles(player_pos: Position, range: usize) -> Vec<Position> {
(-range as i32..=range as i32)
.flat_map(|dx| (-range as i32..=range as i32).map(move |dy| (dx, dy)))
.filter(|(dx, dy)| (dx * dx + dy * dy) as usize <= range * range)
.map(|(dx, dy)| Position {
x: (player_pos.x as i32 + dx) as usize,
y: (player_pos.y as i32 + dy) as usize,
})
.filter(|pos| is_valid_position(pos))
.collect()
}
- 敌人AI决策:
fn find_nearest_player(enemies: &[Enemy], player: &Player) -> Option<&Enemy> {
enemies
.iter()
.min_by_key(|enemy| {
let dx = enemy.position.x as i32 - player.position.x as i32;
let dy = enemy.position.y as i32 - player.position.y as i32;
dx * dx + dy * dy
})
}
高级迭代器模式
自定义迭代器
struct Fibonacci {
current: u64,
next: u64,
}
impl Fibonacci {
fn new() -> Fibonacci {
Fibonacci { current: 0, next: 1 }
}
}
impl Iterator for Fibonacci {
type Item = u64;
fn next(&mut self) -> Option<u64> {
let current = self.current;
let next = self.next;
self.current = next;
self.next = current + next;
Some(current)
}
}
迭代器组合模式
// 链式处理数据
let result = data
.iter()
.filter(|x| x.is_valid())
.map(|x| x.process())
.take(100)
.collect::<Vec<_>>();
性能优化建议
- 避免不必要的collect:只在需要时才收集结果
- 使用合适的迭代器适配器:如
filter_map比filter+map更高效 - 考虑并行迭代:对于独立操作,使用
rayon进行并行处理 - 使用
fold进行累积操作:比先collect再处理更高效
练习:
- 将随机生成封装为
map::gen::dig_tunnels(&mut Map)。 - 在 HUD 中显示
floor_count与墙比例。
概念补充
- 迭代器所有权:
into_iter消费集合、iter借用不可变元素、iter_mut借用可变元素;链式调用需留意谁拥有数据。 - 惰性求值:迭代器适配器(
map/filter/flat_map)是惰性的,只有在 “消费端”(如count、collect)才执行。 collect的目标类型:使用"涡轮鱼"注解指定::<Vec<_>>或::<HashSet<_>>,或通过变量类型推断。- 闭包捕获:按需捕获(借用/移动);在多线程或
'static约束下可能需要move闭包。 - 性能:尽量使用迭代器链避免中间临时分配;对热路径可配合
rayon并行迭代(后续扩展)。 - 随机性与可重复:通过固定种子(
StdRng::seed_from_u64)获得可重现关卡;生产中可使用时间种子。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)