首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >一种用锈写的简易迷宫发生器

一种用锈写的简易迷宫发生器
EN

Code Review用户
提问于 2017-04-01 22:09:08
回答 1查看 289关注 0票数 5

很长一段时间以来,我一直对“铁锈”感兴趣,我终于坐下来开始读“那本书”了。我还没有完成,所以可能有一些特性,我可以使用,但我不知道。

无论如何,这个程序的目的是生成一个迷宫(使用最简单的“随机DFS”算法)。

由于我的主要目标是练习语言,我试着使程序尽可能地“熟能生巧”。仍然困扰我的主要问题是有符号类型和无符号类型之间的所有类型转换。由于这是我的第一个锈蚀程序,我主要是寻找反馈,任何可能被认为是“坏代码”,或部分,可以改进的任何方式。

代码语言:javascript
复制
extern crate rand;
extern crate image;
use std::io::Write;
use rand::Rng;

#[derive(Clone, PartialEq)]
enum Cell {
    Blocked,
    Free,
}

fn make_odd(mut t: (usize, usize)) -> (usize, usize) {
    let o = |v: &mut usize| if *v%2==0{*v+=1};
    o(&mut t.0); o(&mut t.1); t
}

fn gen(s: (usize, usize)) -> Vec<Vec<Cell>> {
    let mut t = vec![vec![Cell::Blocked; s.1]; s.0];
    let mut stack = Vec::<(isize, isize)>::new();

    let c = make_odd((s.0/2, s.1/2));
    stack.push((c.0 as isize, c.1 as isize));
    t[c.0][c.1] = Cell::Free;

    let mut dirs: [(isize, isize); 4] = [(2, 0), (-2, 0), (0, 2), (0, -2)];
    let mut rng = rand::thread_rng();
    'o: while let Some(&(x, y)) = stack.last() {
        rng.shuffle(&mut dirs);
        for i in 0..4 {
            let (dx, dy) = dirs[i];
            let (nx, ny) = (x+dx, y+dy);
            if nx < 0 || ny < 0 || nx >= (s.0 as isize) || ny >= (s.1 as isize) { continue; }
            if t[nx as usize][ny as usize] != Cell::Free {
                stack.push((nx, ny));
                t[nx as usize][ny as usize] = Cell::Free;
                t[(x+dx/2) as usize][(y+dy/2) as usize] = Cell::Free;
                continue 'o;
            }
        }
        stack.pop();
    }

    t[0][1] = Cell::Free;
    t[s.0-1][s.1-2] = Cell::Free;
    t
}

fn print_usage() -> ! {
    let _ = writeln!(std::io::stderr(), "Usage: maze-gen width height [output.png]");
    std::process::exit(1);
}

fn main() {
    let args: Vec<_> = std::env::args().collect();
    if args.len() < 3 { print_usage(); }
    let mut nums = [0; 2];
    for i in 0..2 {
        match args[i+1].parse::<usize>() {
            Err(_) => { print_usage(); },
            Ok(v) => nums[i] = v,
        }
    }
    let s0 = (nums[0], nums[1]);
    let mut s = make_odd(s0);
    if s.0 < 3 { s.0 = 3; }
    if s.1 < 3 { s.1 = 3; }
    if s != s0 {
        let _ = writeln!(std::io::stderr(),
            "Warning: Adjusting sizes to {} and {}!", s.0, s.1);
    }
    let m = gen(s);
    let mut buf = image::ImageBuffer::new(s.0 as u32, s.1 as u32);
    for (x,y,px) in buf.enumerate_pixels_mut() {
        *px = image::Luma([
            match m[x as usize][y as usize] {
                Cell::Free => 255,
                Cell::Blocked => 0,
            }
        ]);
    }
    let filename = if args.len() >= 4 { args[3].as_str() } else { "out.png" };
    let ref mut file = std::fs::File::create(&std::path::Path::new(filename)).unwrap();
    image::ImageLuma8(buf).save(file, image::PNG).unwrap();
}

示例输出:(它很小,您可能需要下载并放大以正确查看它。)

(不好意思语法不好,英语不是我的母语。)

EN

回答 1

Code Review用户

回答已采纳

发布于 2017-04-02 03:10:19

  1. 学会爱铁锈
    1. 空格围绕运算符,在逗号之后-设c= make_odd((s.0/2,s.1/2));+ let c= make_odd((s.0 / 2,s.1 / 2));- for (x,y,px)在buf.enumerate_pixels_mut() {+ for (x,y,px)中buf.enumerate_pixels_mut()中
    2. 不要在一行上放置多个语句- o(&mut t.0);o(&mut t.1);t+ o(&mut t.0);+ o(&mut t.1);+t

  2. 无论(usize, usize)是什么,都要做一个类型。至少,创建一个类型别名。
  3. 实际上,有三个地方想要对元组的两个组件进行更改。将元组提升为类型并添加map方法。
  4. 如果您必须给出闭包参数类型,并且闭包没有捕获,只需创建一个内联函数即可。
  5. 没有真正的理由采取可变的引用来增加一个积分值。
  6. 定义stack,然后立即推送一个值。相反,只需使用vec!宏一次性创建它。
  7. 不需要指定dirs的类型
  8. 与其重新编码dir中的元素数量,而且可能需要再次执行边界检查,只需直接在dirs上迭代即可。
  9. 人物是自由的。使用一些较长的变量名。
  10. 如果您不需要程序名,skip it以避免记住它在那里,然后在这里和那里添加数字来补偿。
  11. 在解析迷宫大小时,没有必要使用涡轮鱼。
  12. 使用unwrap_or_else处理解析失败。
  13. 使用enumerate避免再次索引args片。
  14. 使用std::cmp::max为大小提供较低的值。
  15. 不要忽视错误。如果向stderr写入很重要,请使用expect
  16. 使用Vec::get代替检查大小,然后使用索引运算符(它再次检查大小)
  17. 您不需要构造一个Path,只需传入&str
  18. 喜欢expect而不是unwrap
  19. 您可以使用ImageBuffer::from_fn。请注意,这消除了使变量可变的需要。
  20. 不要在ref绑定中使用let。在右手边使用&更有习性。在这种情况下,&mut at call是比较惯用的。
  21. 要处理未签名/签名的转换,请记住a + (-2)a - (+2)相同。添加一个枚举来定义方向,一个方法将这些方向应用到一个点,在该方法中使用checked_add / checked_sub来处理以下/溢出,然后添加自己的“溢出”检查迷宫的大小。
代码语言:javascript
复制
extern crate rand;
extern crate image;

use rand::Rng;
use std::cmp::max;
use std::io::Write;

#[derive(Clone, PartialEq)]
enum Cell {
    Blocked,
    Free,
}

#[derive(Debug, Copy, Clone)]
enum Direction {
    Up,
    Down,
    Left,
    Right,
}

#[derive(Debug, Copy, Clone, PartialEq)]
struct Point(usize, usize);

impl Point {
    fn map<F>(self, mut f: F) -> Self
        where F: FnMut(usize) -> usize
    {
        Point(f(self.0), f(self.1))
    }

    fn move_by(self, amount: usize, dir: Direction) -> Option<Self> {
        use Direction::*;
        match dir {
            Left  => self.0.checked_sub(amount).map(|x| Point(x, self.1)),
            Right => self.0.checked_add(amount).map(|x| Point(x, self.1)),
            Up    => self.1.checked_sub(amount).map(|y| Point(self.0, y)),
            Down  => self.1.checked_add(amount).map(|y| Point(self.0, y)),
        }
    }
}

fn next_odd_number(n: usize) -> usize {
    n + if n % 2 == 0 { 1 } else { 0 }
}

fn generate_maze(size: Point) -> Vec<Vec<Cell>> {
    use Direction::*;

    let center = size.map(|v| v / 2).map(next_odd_number);
    let mut stack = vec![Point(center.0, center.1)];

    let mut maze = vec![vec![Cell::Blocked; size.1]; size.0];
    maze[center.0][center.1] = Cell::Free;

    let mut dirs = [Left, Right, Up, Down];
    let mut rng = rand::thread_rng();

    let ensure_in_bounds = |z: Point| {
        if z.0 >= size.0 || z.1 >= size.1 {
            None
        } else {
            Some(z)
        }
    };

    'next_odd_number: while let Some(&point) = stack.last() {
        rng.shuffle(&mut dirs);

        for &dir in &dirs {
            let new_point = match point.move_by(2, dir).and_then(&ensure_in_bounds) {
                Some(new_point) => new_point,
                None => continue,
            };

            if maze[new_point.0][new_point.1] != Cell::Free {
                stack.push(new_point);
                maze[new_point.0][new_point.1] = Cell::Free;
                let middle_point = point.move_by(1, dir)
                    .expect("Middle point cannot be out-of-bounds");
                maze[middle_point.0][middle_point.1] = Cell::Free;
                continue 'next_odd_number;
            }
        }
        stack.pop();
    }

    maze[0][1] = Cell::Free;
    maze[size.0 - 1][size.1 - 2] = Cell::Free;
    maze
}

fn print_usage() -> ! {
    writeln!(std::io::stderr(), "Usage: maze-gen width height [output.png]")
        .expect("Unable to write to stderr");
    std::process::exit(1);
}

fn main() {
    let args: Vec<_> = std::env::args().skip(1).collect();
    if args.len() < 2 {
        print_usage();
    }

    let mut dimensions = [0; 2];
    for (i, arg) in args.iter().enumerate() {
        dimensions[i] = arg.parse().unwrap_or_else(|_| print_usage());
    }
    let requested_size = Point(dimensions[0], dimensions[1]);
    let valid_size = requested_size.map(next_odd_number).map(|v| max(v, 3));

    if valid_size != requested_size {
        writeln!(std::io::stderr(),
                 "Warning: Adjusting sizes to {} and {}!",
                 valid_size.0,
                 valid_size.1)
            .expect("Unable to write to stderr");
    }

    let maze = generate_maze(valid_size);

    let buf = image::ImageBuffer::from_fn(valid_size.0 as u32, valid_size.1 as u32, |x, y| {
        let luma = match maze[x as usize][y as usize] {
            Cell::Free => 255,
            Cell::Blocked => 0,
        };
        image::Luma([luma])
    });
    let filename = args.get(2).map(String::as_str).unwrap_or("out.png");

    let mut file = std::fs::File::create(filename).expect("Couldn't open the file");
    image::ImageLuma8(buf).save(&mut file, image::PNG).expect("Coulding write the file");
}

接下来,我可能会研究如何为迷宫实现一个平面存储,这个存储可以由Point直接索引。这将允许这一变化:

代码语言:javascript
复制
-maze[middle_point.0][middle_point.1] = Cell::Free;
+maze[middle_point] = Cell::Free;

老实说,我非常不喜欢标签循环的用法。根据我的经验,我认为我从来不需要它们,它们是锈菌中最稀有的建筑之一。我可能会花很多时间(比我已经花的多.)试图用不同的方式重写它。

票数 2
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/159565

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档