Rust 需要帮助重构过多的函数

Rust need help refactoring excessive functions

(我应该说我对 Rust 还很陌生)

嗨!我正在构建一个 2d 粒子模拟游戏,使用 Vec 来保存包含每个粒子信息的结构。现在,每次我想检查某个元素是否与某个 属性 接触时,我都需要编写一个单独的函数。基本上它通过计算该位置的索引在一个圆圈中搜索粒子,然后将结构 属性 与目标 属性 进行比较,如下所示:

//check around particle for corrodable particles
pub fn check_touch_corrode(screen: &mut Vec<Particle>, x_pos: usize) -> usize {
    if screen[calc::ul(x_pos)].corrode {return calc::ul(x_pos)}     //if particle can corrode return particle
    if screen[calc::u(x_pos)].corrode {return calc::u(x_pos)}
    if screen[calc::ur(x_pos)].corrode {return calc::ur(x_pos)}
    if screen[calc::l(x_pos)].corrode {return calc::l(x_pos)}
    if screen[calc::r(x_pos)].corrode {return calc::r(x_pos)}
    if screen[calc::dl(x_pos)].corrode {return calc::dl(x_pos)}
    if screen[calc::d(x_pos)].corrode {return calc::d(x_pos)}
    if screen[calc::dr(x_pos)].corrode {return calc::dr(x_pos)}
    x_pos                                                           //else return own position
}

//check around particle for flammable particle
pub fn check_touch_flammable(screen: &mut Vec<Particle>, x_pos: usize) -> usize {
    if screen[calc::ul(x_pos)].flammable {return calc::ul(x_pos)}   //if particle flammable return particle
    if screen[calc::u(x_pos)].flammable {return calc::u(x_pos)}
    if screen[calc::ur(x_pos)].flammable {return calc::ur(x_pos)}
    if screen[calc::l(x_pos)].flammable {return calc::l(x_pos)}
    if screen[calc::r(x_pos)].flammable {return calc::r(x_pos)}
    if screen[calc::dl(x_pos)].flammable {return calc::dl(x_pos)}
    if screen[calc::d(x_pos)].flammable {return calc::d(x_pos)}
    if screen[calc::dr(x_pos)].flammable {return calc::dr(x_pos)}
    x_pos                                                           //else return own position
}

考虑到我计划拥有数百个具有各种不同属性和交互的元素,这并不是真正可扩展的。我真的很想知道是否有任何方法可以将其减少到一个功能。我自己弄乱了一段时间,但一直没有取得任何进展。我 运行 遇到的问题是需要在函数中计算比较,据我所知无法传入。有没有什么办法解决这一问题?喜欢做它所以我传递了一些东西,说在计算结构索引后我想比较结构的哪个字段?

我想你想要这样的东西:

// The function returns a tuple of indices, instead of having two functions that return one index each
pub fn check_touch_corrode_flammable(screen: &mut Vec<Particle>, x_pos: usize) -> (usize, usize) {

  let mut results : (usize, usize) = (x_pos, x_pos);

  // We keep track so that we can skip checking cells  
  let mut found_corr = false;
  let mut found_flam = false;

  let ul = screen[calc::ul(x_pos)];

  if ul.corrode {
    results.0 = ul;
    found_corr = true;
  }

  if ul.flammable {
    results.1 = ul;
    found_flam = true;
  }

  let u = screen[calc::u(x_pos)];

  if !found_corr && u.corrode {
    results.0 = u;
    found_corr = true;
  }

  if !found_flam && u.flammable) {
    results.1 = u;
    found_flam = true;
  }

  let ur = screen[calc::ur(x_pos)];

  if !found_corr && ur.corrode {
    results.0 = ur;
    found_corr = true;
  }

  if !found_flam && ur.flammable) {
    results.1 = ur;
    found_flam = true;
  }

  // Continuing like this...

  // ...

  // And then at the end:

  let dr = screen[calc::dr(x_pos)];

  if !found_corr && dr.corrode {
    results.0 = dr;
    found_corr = true;
  }

  if !found_flam && dr.flammable) {
    results.1 = dr;
    found_flam = true;
  }

  results
}

这个解决方案的主要优点是它计算我们在每个方向检查的单元格的索引一次。

这或多或少是您所需要的吗?让我知道您是否需要更多帮助:)

我找到了解决办法!

pub fn check_touch(screen: &mut Vec<Particle>, x_pos: usize, criteria: impl Fn(Particle) -> bool,) -> usize {
    if criteria(screen[calc::ul(x_pos)]) {return calc::ul(x_pos)}
    //do this for every direction
}

然后像这样称呼它

check_touch(screen, x_pos, |p| p.corrode)

感谢 r/Erelde 在 reddit 上给我的建议

我会朝着这个方向前进,使用迭代器、过滤器和谓词:

fn neighbors(x_pos: usize) -> impl 'static + Iterator<Item=usize> {
    (0..8).map(move |n| match n {
        0 => calc_ul(x_pos),
        1 => calc_u(x_pos),
        // and so on for 2-7

        _ => panic!("shouldn't get hereA"),
    })
}

fn first_corrosive_neighbor(screen: &Vec<Particle>, x_pos: usize) -> Option<usize> {
    neighbors(x_pos).filter(|p| screen[*p].corrode).next()
}

fn first_flammable_neighbor(screen: &Vec<Particle>, x_pos: usize) -> Option<usize> {
    neighbors(x_pos).filter(|p| screen[*p].flammable).next()
}