Skip to content

Commit

Permalink
Add momentum and introduce Result returns
Browse files Browse the repository at this point in the history
  • Loading branch information
iMilchshake committed Apr 20, 2024
1 parent dfca940 commit 4e4c8bc
Show file tree
Hide file tree
Showing 6 changed files with 102 additions and 48 deletions.
4 changes: 4 additions & 0 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ pub struct GenerationConfig {
/// (min, max) distance between platforms
pub platform_distance_bounds: (usize, usize),

/// probability for doing the last shift direction again
pub momentum_prob: f32,

// ------- TODO: these should go somewhere else -----
pub waypoints: Vec<Position>,
}
Expand Down Expand Up @@ -99,6 +102,7 @@ impl Default for GenerationConfig {
],
step_weights: vec![20, 11, 10, 9],
platform_distance_bounds: (500, 750),
momentum_prob: 0.01,
}
}
}
8 changes: 8 additions & 0 deletions src/editor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,14 @@ impl Editor {
true,
);

field_edit_widget(
ui,
&mut self.config.momentum_prob,
edit_f32,
"momentum prob",
true,
);

// only show these in setup mode
ui.add_visible_ui(self.is_setup(), |ui| {
vec_edit_widget(
Expand Down
26 changes: 16 additions & 10 deletions src/generator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,26 +37,26 @@ impl Generator {

if !self.walker.finished {
// randomly mutate kernel
self.walker.mutate_kernel(config, &mut self.rnd);
self.walker.mutate_kernel(&config, &mut self.rnd);

// perform one step
self.walker
.probabilistic_step(&mut self.map, &mut self.rnd)?;
.probabilistic_step(&mut self.map, config, &mut self.rnd)?;

// handle platforms
self.walker.check_platform(
&mut self.map,
config.platform_distance_bounds.0,
config.platform_distance_bounds.1,
);
)?;
}

Ok(())
}

/// Post processing step to fix all existing edge-bugs, as certain inner/outer kernel
/// configurations do not ensure a min. 1-block freeze padding consistently.
fn fix_edge_bugs(&mut self) -> Array2<bool> {
fn fix_edge_bugs(&mut self) -> Result<Array2<bool>, &'static str> {
let mut edge_bug = Array2::from_elem((self.map.width, self.map.height), false);
let width = self.map.width;
let height = self.map.height;
Expand All @@ -71,8 +71,12 @@ impl Generator {
continue;
}

let neighbor_x = x + dx - 1; // TODO: deal with overflow?
let neighbor_y = y + dy - 1;
let neighbor_x = (x + dx)
.checked_sub(1)
.ok_or("fix edge bug out of bounds")?;
let neighbor_y = (y + dy)
.checked_sub(1)
.ok_or("fix edge bug out of bounds")?;
if neighbor_x < width && neighbor_y < height {
let neighbor_value = &self.map.grid[[neighbor_x, neighbor_y]];
if *neighbor_value == BlockType::Hookable {
Expand All @@ -90,15 +94,17 @@ impl Generator {
}
}

edge_bug
Ok(edge_bug)
}

pub fn post_processing(&mut self) {
self.fix_edge_bugs();
self.fix_edge_bugs().expect("fix edge bugs failed");
self.map
.generate_room(&self.map.spawn.clone(), 4, Some(&BlockType::Start));
.generate_room(&self.map.spawn.clone(), 2, Some(&BlockType::Start))
.expect("start room generation failed");
self.map
.generate_room(&self.walker.pos.clone(), 4, Some(&BlockType::Finish));
.generate_room(&self.walker.pos.clone(), 2, Some(&BlockType::Finish))
.expect("start finish room generation");
}

/// Generates an entire map with a single function call. This function is used by the CLI.
Expand Down
63 changes: 41 additions & 22 deletions src/map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -130,45 +130,59 @@ impl Map {
Ok(())
}

pub fn generate_room(&mut self, pos: &Position, margin: usize, zone_type: Option<&BlockType>) {
// TODO: ensure valid position?
pub fn generate_room(
&mut self,
pos: &Position,
margin: usize,
zone_type: Option<&BlockType>,
) -> Result<(), &'static str> {
// TODO: return an error?
if pos.x < (margin + 1)
|| pos.y < (margin + 1)
|| pos.x > self.width - (margin + 1)
|| pos.y > self.height - (margin + 1)
{
return Err("generate room out of bounds");
}

let margin: i32 = margin.to_i32().unwrap();

// carve room
self.set_area(
&pos.shifted_by(-margin, -margin),
&pos.shifted_by(margin, margin),
&pos.shifted_by(-margin, -margin)?,
&pos.shifted_by(margin, margin)?,
&BlockType::Empty,
true,
);

// set platform
self.set_area(
&pos.shifted_by(-(margin - 2), 1),
&pos.shifted_by(margin - 2, 1),
&pos.shifted_by(-(margin - 2), 1)?,
&pos.shifted_by(margin - 2, 1)?,
&BlockType::Platform,
true,
);

// set spawns
if zone_type == Some(&BlockType::Start) {
self.set_area(
&pos.shifted_by(-(margin - 2), 0),
&pos.shifted_by(margin - 2, 0),
&pos.shifted_by(-(margin - 2), 0)?,
&pos.shifted_by(margin - 2, 0)?,
&BlockType::Spawn,
true,
);
}
// set start/finish line
if let Some(zone_type) = zone_type {
self.set_area_border(
&pos.shifted_by(-margin - 1, -margin - 1),
&pos.shifted_by(margin + 1, margin + 1),
&pos.shifted_by(-margin - 1, -margin - 1)?,
&pos.shifted_by(margin + 1, margin + 1)?,
zone_type,
false,
);
}

Ok(())
}

fn pos_to_chunk_pos(&self, pos: Position) -> Position {
Expand Down Expand Up @@ -211,27 +225,32 @@ impl Map {
top_left: &Position,
bot_right: &Position,
value: &BlockType,
) -> bool {
let area = self.grid.slice(s![
top_left.x..=bot_right.x + 1,
top_left.y..=bot_right.y + 1
]);
) -> Result<bool, &'static str> {
if !self.pos_in_bounds(&top_left) || !self.pos_in_bounds(&bot_right) {
return Err("checking area out of bounds");
}

let area = self
.grid
.slice(s![top_left.x..=bot_right.x, top_left.y..=bot_right.y]);

area.iter().any(|block| block == value)
Ok(area.iter().any(|block| block == value))
}

pub fn check_area_all(
&self,
top_left: &Position,
bot_right: &Position,
value: &BlockType,
) -> bool {
let area = self.grid.slice(s![
top_left.x..=bot_right.x + 1,
top_left.y..=bot_right.y + 1
]);
) -> Result<bool, &'static str> {
if !self.pos_in_bounds(&top_left) || !self.pos_in_bounds(&bot_right) {
return Err("checking area out of bounds");
}
let area = self
.grid
.slice(s![top_left.x..=bot_right.x, top_left.y..=bot_right.y]);

area.iter().all(|block| block == value)
Ok(area.iter().all(|block| block == value))
}

// TODO: right now override is hardcoded to overide empty AND freeze. i might need some
Expand Down
8 changes: 4 additions & 4 deletions src/position.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,24 +32,24 @@ impl Position {
}

/// returns a new position shifted by some x and y value
pub fn shifted_by(&self, x_shift: i32, y_shift: i32) -> Position {
pub fn shifted_by(&self, x_shift: i32, y_shift: i32) -> Result<Position, &'static str> {
let new_x = match x_shift >= 0 {
true => self.x + (x_shift as usize),
false => self
.x
.checked_sub((-x_shift) as usize)
.expect("shift is out of bounds"),
.ok_or("invalid shift")?,
};

let new_y = match y_shift >= 0 {
true => self.y + y_shift as usize,
false => self
.y
.checked_sub((-y_shift) as usize)
.expect("shift is out of bounds"),
.ok_or("invalid shift")?,
};

Position::new(new_x, new_y)
Ok(Position::new(new_x, new_y))
}

pub fn shift_in_direction(
Expand Down
41 changes: 29 additions & 12 deletions src/walker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use crate::{
config::GenerationConfig,
kernel::Kernel,
map::{BlockType, KernelType, Map},
position::Position,
position::{Position, ShiftDirection},
random::Random,
};

Expand All @@ -21,6 +21,8 @@ pub struct CuteWalker {
pub finished: bool,

pub steps_since_platform: usize,

pub last_direction: Option<ShiftDirection>,
}

impl CuteWalker {
Expand All @@ -40,6 +42,7 @@ impl CuteWalker {
waypoints: config.waypoints.clone(),
finished: false,
steps_since_platform: 0,
last_direction: None,
}
}

Expand All @@ -59,44 +62,52 @@ impl CuteWalker {

/// will try to place a platform at the walkers position.
/// If force is true it will enforce a platform.
pub fn check_platform(&mut self, map: &mut Map, min_distance: usize, max_distance: usize) {
pub fn check_platform(
&mut self,
map: &mut Map,
min_distance: usize,
max_distance: usize,
) -> Result<(), &'static str> {
self.steps_since_platform += 1;

// Case 1: min distance is not reached -> skip
if self.steps_since_platform < min_distance {
return;
return Ok(());
}

let walker_pos = self.pos.clone();

// Case 2: max distance has been exceeded -> force platform using a room
if self.steps_since_platform > max_distance {
// TODO: for now this is hardcoded so that platform is shifted down by 7 blocks.
map.generate_room(&walker_pos.shifted_by(0, 7), 4, None);
// map.generate_room(&walker_pos.shifted_by(0, 7)?, 4, None)?;
self.steps_since_platform = 0;
return;
return Ok(());
}

// Case 3: min distance has been exceeded -> Try to place platform, but only if possible
let area_empty = map.check_area_all(
&walker_pos.shifted_by(-2, -3),
&walker_pos.shifted_by(2, 1),
&walker_pos.shifted_by(-2, -3)?,
&walker_pos.shifted_by(2, 1)?,
&BlockType::Empty,
);
)?;
if area_empty {
map.set_area(
&walker_pos.shifted_by(-1, 0),
&walker_pos.shifted_by(1, 0),
&walker_pos.shifted_by(-1, 0)?,
&walker_pos.shifted_by(1, 0)?,
&BlockType::Platform,
true,
);
self.steps_since_platform = 0;
}

Ok(())
}

pub fn probabilistic_step(
&mut self,
map: &mut Map,
config: &GenerationConfig,
rnd: &mut Random,
) -> Result<(), &'static str> {
if self.finished {
Expand All @@ -105,11 +116,17 @@ impl CuteWalker {

let goal = self.goal.as_ref().ok_or("Error: Goal is None")?;
let shifts = self.pos.get_rated_shifts(goal, map);
let sampled_shift = rnd.sample_move(&shifts);
let mut sampled_shift = &rnd.sample_move(&shifts);

// with a certain probabiliy re-use last direction instead
if rnd.with_probability(config.momentum_prob) && !self.last_direction.is_none() {
sampled_shift = self.last_direction.as_ref().unwrap();
}

// apply that shift
self.pos.shift_in_direction(&sampled_shift, map)?;
self.pos.shift_in_direction(sampled_shift, map)?;
self.steps += 1;
self.last_direction = Some(sampled_shift.clone());

// remove blocks using a kernel at current position
map.update(self, KernelType::Outer)?;
Expand Down

0 comments on commit 4e4c8bc

Please sign in to comment.