use mumu::parser::interpreter::Interpreter;
use mumu::parser::types::Value;
use indexmap::IndexMap;
use fastrand;
pub fn astar_boulders_bridge(
_interp: &mut Interpreter,
args: Vec<Value>,
) -> Result<Value, String> {
if args.len() != 1 {
return Err("astar:boulders ⇒ expects a single keyed array argument".into());
}
let map = match &args[0] {
Value::KeyedArray(m) => m,
_ => return Err("astar:boulders ⇒ first argument must be keyed array".into()),
};
let seed = get_i64(map, "seed")? as u64;
let width = get_i32(map, "width")?;
let height = get_i32(map, "height")?;
let count = get_i32(map, "count")?;
let (sx, sy) = match map.get("start") {
Some(Value::IntArray(xs)) if xs.len() == 2 => (xs[0], xs[1]),
_ => (0, 0),
};
let (ex, ey) = match map.get("end") {
Some(Value::IntArray(xs)) if xs.len() == 2 => (xs[0], xs[1]),
_ => (width - 1, height - 1),
};
if width < 3 || height < 3 {
return Err("astar:boulders ⇒ width and height must be >= 3".into());
}
if sx < 0 || sx >= width || sy < 0 || sy >= height {
return Err("astar:boulders ⇒ (start) must be within the grid".into());
}
if ex < 0 || ex >= width || ey < 0 || ey >= height {
return Err("astar:boulders ⇒ (end) must be within the grid".into());
}
fastrand::seed(seed);
let mut obstacle_set = std::collections::HashSet::<(i32, i32)>::new();
for _ in 0..count {
let cx = fastrand::i32(2..width-2);
let cy = fastrand::i32(2..height-2);
let r_major = fastrand::i32(2..=(width/8).max(2));
let r_minor = fastrand::i32(2..=(height/8).max(2));
for dy in -r_minor..=r_minor {
for dx in -r_major..=r_major {
let fx = dx as f32 / r_major as f32;
let fy = dy as f32 / r_minor as f32;
if fx * fx + fy * fy <= 1.0 {
let x = cx + dx;
let y = cy + dy;
if x >= 0 && x < width && y >= 0 && y < height {
obstacle_set.insert((x, y));
}
}
}
}
}
obstacle_set.remove(&(sx, sy));
obstacle_set.remove(&(ex, ey));
let mut obstacles: Vec<Vec<i32>> = obstacle_set
.into_iter()
.map(|(x, y)| vec![x, y])
.collect();
obstacles.sort();
let mut out = IndexMap::new();
out.insert("dimension".into(), Value::IntArray(vec![width, height]));
out.insert("start".into(), Value::IntArray(vec![sx, sy]));
out.insert("end".into(), Value::IntArray(vec![ex, ey]));
out.insert("obj".into(), Value::Int2DArray(obstacles));
Ok(Value::KeyedArray(out))
}
fn get_i32(map: &IndexMap<String, Value>, key: &str) -> Result<i32, String> {
match map.get(key) {
Some(Value::Int(i)) => Ok(*i),
Some(Value::Long(l)) => Ok(*l as i32),
Some(v) => Err(format!("boulders ⇒ field '{}' must be int/long, got {:?}", key, v)),
None => Err(format!("boulders ⇒ field '{}' is required", key)),
}
}
fn get_i64(map: &IndexMap<String, Value>, key: &str) -> Result<i64, String> {
match map.get(key) {
Some(Value::Int(i)) => Ok(*i as i64),
Some(Value::Long(l)) => Ok(*l),
Some(v) => Err(format!("boulders ⇒ field '{}' must be int/long, got {:?}", key, v)),
None => Err(format!("boulders ⇒ field '{}' is required", key)),
}
}