added stair resizing

This commit is contained in:
grimsace
2026-04-28 14:09:51 -05:00
parent 51bb936263
commit bd0d64b4af
4 changed files with 322 additions and 88 deletions
+8 -8
View File
@@ -889,8 +889,8 @@ pub fn build_svg(layout: &DungeonLayout, settings: &UiSettings) -> String {
for stair in &layout.stairs { for stair in &layout.stairs {
let x = g.left() + stair.cell.0 as f32 * g.cell; let x = g.left() + stair.cell.0 as f32 * g.cell;
let y = g.top() + stair.cell.1 as f32 * g.cell; let y = g.top() + stair.cell.1 as f32 * g.cell;
let w = stair.size as f32 * g.cell; let w = stair.width as f32 * g.cell;
let h = stair.size as f32 * g.cell; let h = stair.height as f32 * g.cell;
let fill = if settings.colorblind_mode { let fill = if settings.colorblind_mode {
"rgb(180,180,180)" "rgb(180,180,180)"
@@ -901,7 +901,7 @@ pub fn build_svg(layout: &DungeonLayout, settings: &UiSettings) -> String {
s, s,
"<rect x='{x}' y='{y}' width='{w}' height='{h}' fill='{fill}' fill-opacity='0.35' stroke='rgb(150,100,30)' stroke-width='2'/>" "<rect x='{x}' y='{y}' width='{w}' height='{h}' fill='{fill}' fill-opacity='0.35' stroke='rgb(150,100,30)' stroke-width='2'/>"
); );
let steps = (stair.size as f32 * 2.0).round() as usize; let steps = (stair.height as f32 * 2.0).round() as usize;
let step_h = h / steps as f32; let step_h = h / steps as f32;
for i in 0..steps { for i in 0..steps {
let sy = y + (i as f32 * step_h) + step_h * 0.5; let sy = y + (i as f32 * step_h) + step_h * 0.5;
@@ -1155,8 +1155,8 @@ fn fill_rect_staircase(
let Some(rect) = Rect::from_xywh( let Some(rect) = Rect::from_xywh(
g.left() + staircase.cell.0 as f32 * g.cell, g.left() + staircase.cell.0 as f32 * g.cell,
g.top() + staircase.cell.1 as f32 * g.cell, g.top() + staircase.cell.1 as f32 * g.cell,
staircase.size as f32 * g.cell, staircase.width as f32 * g.cell,
staircase.size as f32 * g.cell, staircase.height as f32 * g.cell,
) else { ) else {
return; return;
}; };
@@ -1181,8 +1181,8 @@ fn draw_staircase(
let Some(rect) = Rect::from_xywh( let Some(rect) = Rect::from_xywh(
g.left() + staircase.cell.0 as f32 * g.cell, g.left() + staircase.cell.0 as f32 * g.cell,
g.top() + staircase.cell.1 as f32 * g.cell, g.top() + staircase.cell.1 as f32 * g.cell,
staircase.size as f32 * g.cell, staircase.width as f32 * g.cell,
staircase.size as f32 * g.cell, staircase.height as f32 * g.cell,
) else { ) else {
return; return;
}; };
@@ -1206,7 +1206,7 @@ fn draw_staircase(
draw_wall_segment(pixmap, x, y, x, y + h, 2.0, stroke_color); draw_wall_segment(pixmap, x, y, x, y + h, 2.0, stroke_color);
draw_wall_segment(pixmap, x + w, y, x + w, y + h, 2.0, stroke_color); draw_wall_segment(pixmap, x + w, y, x + w, y + h, 2.0, stroke_color);
let steps = (staircase.size as f32 * 2.0).round() as usize; let steps = (staircase.height as f32 * 2.0).round() as usize;
let step_h = h / steps as f32; let step_h = h / steps as f32;
for i in 0..steps { for i in 0..steps {
let sy = (y + (i as f32 * step_h) + step_h * 0.5).round(); let sy = (y + (i as f32 * step_h) + step_h * 0.5).round();
+21 -7
View File
@@ -64,7 +64,8 @@ pub struct AreaMarker {
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Staircase { pub struct Staircase {
pub cell: (usize, usize), pub cell: (usize, usize),
pub size: usize, pub width: usize,
pub height: usize,
} }
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
@@ -1384,13 +1385,26 @@ fn shuffle_rooms(rooms: &mut [Room], rng: &mut SimpleRng) {
} }
} }
fn rooms_overlap(a: &Room, b: &Room) -> bool { fn rects_overlap(
let a_right = a.x + a.width; ax: usize,
let a_bottom = a.y + a.height; ay: usize,
let b_right = b.x + b.width; aw: usize,
let b_bottom = b.y + b.height; ah: usize,
bx: usize,
by: usize,
bw: usize,
bh: usize,
) -> bool {
let a_right = ax + aw;
let a_bottom = ay + ah;
let b_right = bx + bw;
let b_bottom = by + bh;
a.x < b_right && a_right > b.x && a.y < b_bottom && a_bottom > b.y ax < b_right && a_right > bx && ay < b_bottom && a_bottom > by
}
fn rooms_overlap(a: &Room, b: &Room) -> bool {
rects_overlap(a.x, a.y, a.width, a.height, b.x, b.y, b.width, b.height)
} }
fn rooms_touch(a: &Room, b: &Room) -> bool { fn rooms_touch(a: &Room, b: &Room) -> bool {
+231 -23
View File
@@ -1194,7 +1194,8 @@ impl DungeonApp {
let stair = layout::Staircase { let stair = layout::Staircase {
cell: (origin_x, origin_y), cell: (origin_x, origin_y),
size, width: size,
height: size,
}; };
if self.settings.sync_stairs_across_levels { if self.settings.sync_stairs_across_levels {
@@ -1414,9 +1415,9 @@ impl DungeonApp {
layout.stairs.iter().position(|stair| { layout.stairs.iter().position(|stair| {
clicked.0 >= stair.cell.0 clicked.0 >= stair.cell.0
&& clicked.0 < stair.cell.0 + stair.size && clicked.0 < stair.cell.0 + stair.width
&& clicked.1 >= stair.cell.1 && clicked.1 >= stair.cell.1
&& clicked.1 < stair.cell.1 + stair.size && clicked.1 < stair.cell.1 + stair.height
}) })
} }
@@ -1488,16 +1489,16 @@ impl DungeonApp {
return; return;
} }
let stair_size = { let (stair_width, stair_height) = {
let layout = &self.levels[self.settings.active_level_index]; let layout = &self.levels[self.settings.active_level_index];
let Some(stair) = layout.stairs.get(drag.stair_idx) else { let Some(stair) = layout.stairs.get(drag.stair_idx) else {
return; return;
}; };
stair.size (stair.width, stair.height)
}; };
let max_x = self.settings.cols.saturating_sub(stair_size); let max_x = self.settings.cols.saturating_sub(stair_width);
let max_y = self.settings.rows.saturating_sub(stair_size); let max_y = self.settings.rows.saturating_sub(stair_height);
let target_x = (grid_x - drag.offset_x).floor().max(0.0) as usize; let target_x = (grid_x - drag.offset_x).floor().max(0.0) as usize;
let target_y = (grid_y - drag.offset_y).floor().max(0.0) as usize; let target_y = (grid_y - drag.offset_y).floor().max(0.0) as usize;
let new_cell = (target_x.min(max_x), target_y.min(max_y)); let new_cell = (target_x.min(max_x), target_y.min(max_y));
@@ -1565,6 +1566,14 @@ impl DungeonApp {
self.resize_state = Some(ResizeState::Room(state)); self.resize_state = Some(ResizeState::Room(state));
return true; return true;
} }
} else if let Some(stair_idx) = self.stair_at_pointer(pointer_pos, geometry) {
if let Some(state) =
self.start_staircase_resize(stair_idx, pointer_pos, geometry)
{
self.push_undo_snapshot();
self.resize_state = Some(ResizeState::Staircase(state));
return true;
}
} }
} }
} }
@@ -1579,6 +1588,7 @@ impl DungeonApp {
match state { match state {
ResizeState::Room(state) => self.resize_room(&state, cell), ResizeState::Room(state) => self.resize_room(&state, cell),
ResizeState::Text(state) => self.resize_text(&state, cell), ResizeState::Text(state) => self.resize_text(&state, cell),
ResizeState::Staircase(state) => self.resize_staircase(&state, cell),
} }
} }
} }
@@ -1671,6 +1681,38 @@ impl DungeonApp {
}) })
} }
fn start_staircase_resize(
&self,
stair_idx: usize,
pointer_pos: egui::Pos2,
geometry: &GridGeometry,
) -> Option<StaircaseResizeState> {
if self.settings.active_level_index >= self.levels.len() {
return None;
}
let layout = &self.levels[self.settings.active_level_index];
let original_stair = layout.stairs.get(stair_idx)?.clone();
let (grid_x, grid_y) = pointer_to_grid(pointer_pos, geometry)?;
let center_x = original_stair.cell.0 as f32 + original_stair.width as f32 * 0.5;
let center_y = original_stair.cell.1 as f32 + original_stair.height as f32 * 0.5;
let pointer_x = grid_x;
let pointer_y = grid_y;
let resize_corner = match (pointer_x >= center_x, pointer_y >= center_y) {
(false, false) => ResizeCorner::TopLeft,
(true, false) => ResizeCorner::TopRight,
(false, true) => ResizeCorner::BottomLeft,
(true, true) => ResizeCorner::BottomRight,
};
Some(StaircaseResizeState {
stair_idx,
original_stair,
resize_corner,
})
}
// Resize only the corner chosen at drag start while keeping the opposite corner fixed. // Resize only the corner chosen at drag start while keeping the opposite corner fixed.
fn resize_room(&mut self, state: &RoomResizeState, target_cell: (usize, usize)) { fn resize_room(&mut self, state: &RoomResizeState, target_cell: (usize, usize)) {
if self.settings.active_level_index >= self.levels.len() { if self.settings.active_level_index >= self.levels.len() {
@@ -1748,6 +1790,112 @@ impl DungeonApp {
label.font_size = new_size as u16; label.font_size = new_size as u16;
} }
fn resize_staircase(&mut self, state: &StaircaseResizeState, target_cell: (usize, usize)) {
if self.settings.active_level_index >= self.levels.len() {
return;
}
let cols = self.settings.cols.max(1);
let rows = self.settings.rows.max(1);
let tx = target_cell.0.min(cols - 1) as isize;
let ty = target_cell.1.min(rows - 1) as isize;
let original = &state.original_stair;
let left = original.cell.0 as isize;
let top = original.cell.1 as isize;
let right = (original.cell.0 + original.width - 1) as isize;
let bottom = (original.cell.1 + original.height - 1) as isize;
let (x0, x1, y0, y1) = match state.resize_corner {
ResizeCorner::TopLeft => (tx.min(right), right, ty.min(bottom), bottom),
ResizeCorner::TopRight => (left, tx.max(left), ty.min(bottom), bottom),
ResizeCorner::BottomLeft => (tx.min(right), right, top, ty.max(top)),
ResizeCorner::BottomRight => (left, tx.max(left), top, ty.max(top)),
};
let new_stair_cell = (x0 as usize, y0 as usize);
let new_stair_width = (x1 - x0 + 1) as usize;
let new_stair_height = (y1 - y0 + 1) as usize;
if new_stair_width == 0 || new_stair_height == 0 {
return;
}
// Check for collisions with other stairs or rooms on the current level.
let active_layout = &self.levels[self.settings.active_level_index];
if active_layout.rooms.iter().any(|room| {
rects_overlap(
new_stair_cell.0,
new_stair_cell.1,
new_stair_width,
new_stair_height,
room.x,
room.y,
room.width,
room.height,
)
}) {
return;
}
if active_layout.stairs.iter().enumerate().any(|(idx, stair)| {
idx != state.stair_idx
&& rects_overlap(
new_stair_cell.0,
new_stair_cell.1,
new_stair_width,
new_stair_height,
stair.cell.0,
stair.cell.1,
stair.width,
stair.height,
)
}) {
return;
}
if self.settings.sync_stairs_across_levels {
for layout in &mut self.levels {
for stair in &mut layout.stairs {
if stair.cell == state.original_stair.cell {
stair.cell = new_stair_cell;
stair.width = new_stair_width;
stair.height = new_stair_height;
}
}
}
} else {
let current_idx = self.settings.active_level_index;
let level_indices = vec![
Some(current_idx),
current_idx.checked_sub(1),
if current_idx + 1 < self.levels.len() {
Some(current_idx + 1)
} else {
None
},
];
for idx in level_indices.into_iter().flatten() {
let layout = &mut self.levels[idx];
for stair in &mut layout.stairs {
if stair.cell == state.original_stair.cell {
stair.cell = new_stair_cell;
stair.width = new_stair_width;
stair.height = new_stair_height;
}
}
}
}
// Update the resize state with the new dimensions to allow continuous resizing.
if let Some(ResizeState::Staircase(ref mut s)) = self.resize_state {
s.original_stair.cell = new_stair_cell;
s.original_stair.width = new_stair_width;
s.original_stair.height = new_stair_height;
}
}
// Draw dashed overlays for room resizing interactions. // Draw dashed overlays for room resizing interactions.
fn draw_resize_overlay(&self, painter: &egui::Painter, geometry: &GridGeometry) { fn draw_resize_overlay(&self, painter: &egui::Painter, geometry: &GridGeometry) {
if self.settings.active_level_index >= self.levels.len() { if self.settings.active_level_index >= self.levels.len() {
@@ -1760,7 +1908,7 @@ impl DungeonApp {
.as_ref() .as_ref()
.and_then(|s| match s { .and_then(|s| match s {
ResizeState::Text(state) => Some(state.text_idx), ResizeState::Text(state) => Some(state.text_idx),
ResizeState::Room(_) => None, _ => None,
}) })
.or(self.hover_text_idx) .or(self.hover_text_idx)
{ {
@@ -1781,15 +1929,12 @@ impl DungeonApp {
.as_ref() .as_ref()
.and_then(|s| match s { .and_then(|s| match s {
ResizeState::Room(state) => Some(state.room_idx), ResizeState::Room(state) => Some(state.room_idx),
ResizeState::Text(_) => None, _ => None,
}) })
.or(self.hover_room_idx); .or(self.hover_room_idx);
let Some(room_idx) = room_idx else {
return; if let Some(room_idx) = room_idx {
}; if let Some(room) = layout.rooms.get(room_idx) {
let Some(room) = layout.rooms.get(room_idx) else {
return;
};
let color = Color32::from_gray(170); let color = Color32::from_gray(170);
let stroke = Stroke::new(1.5, color); let stroke = Stroke::new(1.5, color);
@@ -1826,6 +1971,47 @@ impl DungeonApp {
draw_resize_handle(painter, br, ResizeCorner::BottomRight, color); draw_resize_handle(painter, br, ResizeCorner::BottomRight, color);
draw_resize_handle(painter, bl, ResizeCorner::BottomLeft, color); draw_resize_handle(painter, bl, ResizeCorner::BottomLeft, color);
} }
return;
}
}
let stair_idx = self
.resize_state
.as_ref()
.and_then(|s| match s {
ResizeState::Staircase(state) => Some(state.stair_idx),
_ => None,
})
.or(self.hover_stair_idx);
if let Some(stair_idx) = stair_idx {
if let Some(stair) = layout.stairs.get(stair_idx) {
let color = Color32::from_gray(170);
let stroke = Stroke::new(1.5, color);
let left = geometry.rect.left() + stair.cell.0 as f32 * geometry.cell_size;
let top = geometry.rect.top() + stair.cell.1 as f32 * geometry.cell_size;
let right = left + stair.width as f32 * geometry.cell_size;
let bottom = top + stair.height as f32 * geometry.cell_size;
let tl = egui::pos2(left, top);
let tr = egui::pos2(right, top);
let br = egui::pos2(right, bottom);
let bl = egui::pos2(left, bottom);
draw_dashed_line(painter, tl, tr, stroke, 6.0, 6.0);
draw_dashed_line(painter, tr, br, stroke, 6.0, 6.0);
draw_dashed_line(painter, br, bl, stroke, 6.0, 6.0);
draw_dashed_line(painter, bl, tl, stroke, 6.0, 6.0);
if matches!(self.resize_state, Some(ResizeState::Staircase(_))) {
draw_resize_handle(painter, tl, ResizeCorner::TopLeft, color);
draw_resize_handle(painter, tr, ResizeCorner::TopRight, color);
draw_resize_handle(painter, br, ResizeCorner::BottomRight, color);
draw_resize_handle(painter, bl, ResizeCorner::BottomLeft, color);
}
}
}
} }
// Draw a dashed overlay along the hovered corridor path. // Draw a dashed overlay along the hovered corridor path.
@@ -2248,10 +2434,18 @@ struct TextResizeState {
original_font_size: u16, original_font_size: u16,
} }
#[derive(Debug, Clone)]
struct StaircaseResizeState {
stair_idx: usize,
original_stair: layout::Staircase,
resize_corner: ResizeCorner,
}
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
enum ResizeState { enum ResizeState {
Room(RoomResizeState), Room(RoomResizeState),
Text(TextResizeState), Text(TextResizeState),
Staircase(StaircaseResizeState),
} }
struct GridGeometry { struct GridGeometry {
@@ -2635,8 +2829,8 @@ fn draw_staircase(
) { ) {
let left = geometry.rect.left() + stair.cell.0 as f32 * geometry.cell_size; let left = geometry.rect.left() + stair.cell.0 as f32 * geometry.cell_size;
let top = geometry.rect.top() + stair.cell.1 as f32 * geometry.cell_size; let top = geometry.rect.top() + stair.cell.1 as f32 * geometry.cell_size;
let right = left + stair.size as f32 * geometry.cell_size; let right = left + stair.width as f32 * geometry.cell_size;
let bottom = top + stair.size as f32 * geometry.cell_size; let bottom = top + stair.height as f32 * geometry.cell_size;
let rect = egui::Rect::from_min_max(egui::pos2(left, top), egui::pos2(right, bottom)); let rect = egui::Rect::from_min_max(egui::pos2(left, top), egui::pos2(right, bottom));
// Fill with a distinctive color for stairs. // Fill with a distinctive color for stairs.
@@ -2665,7 +2859,7 @@ fn draw_staircase(
); );
// Draw stair lines (horizontal steps). // Draw stair lines (horizontal steps).
let steps = (stair.size as f32 * 2.0).round() as usize; let steps = (stair.height as f32 * 2.0).round() as usize;
let step_height = (rect.height() / (steps as f32)).max(1.0); let step_height = (rect.height() / (steps as f32)).max(1.0);
let line_color = if colorblind_mode { let line_color = if colorblind_mode {
Color32::BLACK Color32::BLACK
@@ -2886,13 +3080,27 @@ fn room_index_at_cell(rooms: &[layout::Room], cell: (usize, usize)) -> Option<us
}) })
} }
// Check whether two rectangles overlap by area.
fn rects_overlap(
ax: usize,
ay: usize,
aw: usize,
ah: usize,
bx: usize,
by: usize,
bw: usize,
bh: usize,
) -> bool {
let a_right = ax + aw;
let a_bottom = ay + ah;
let b_right = bx + bw;
let b_bottom = by + bh;
ax < b_right && a_right > bx && ay < b_bottom && a_bottom > by
}
// Check whether two rooms overlap by area. // Check whether two rooms overlap by area.
fn rooms_overlap(a: &layout::Room, b: &layout::Room) -> bool { fn rooms_overlap(a: &layout::Room, b: &layout::Room) -> bool {
let a_right = a.x + a.width; rects_overlap(a.x, a.y, a.width, a.height, b.x, b.y, b.width, b.height)
let a_bottom = a.y + a.height;
let b_right = b.x + b.width;
let b_bottom = b.y + b.height;
a.x < b_right && a_right > b.x && a.y < b_bottom && a_bottom > b.y
} }
fn manual_door_edge_allowed( fn manual_door_edge_allowed(
+32 -20
View File
@@ -448,14 +448,15 @@ fn get_stair_count(settings: &UiSettings, gap_idx: usize) -> usize {
pub fn ensure_stair_in_room(layout: &mut DungeonLayout, stair: &layout::Staircase) -> bool { pub fn ensure_stair_in_room(layout: &mut DungeonLayout, stair: &layout::Staircase) -> bool {
let stair_x = stair.cell.0; let stair_x = stair.cell.0;
let stair_y = stair.cell.1; let stair_y = stair.cell.1;
let stair_size = stair.size; let stair_width = stair.width;
let stair_height = stair.height;
// Check if any room already contains the staircase. // Check if any room already contains the staircase.
for room in &layout.rooms { for room in &layout.rooms {
if stair_x >= room.x if stair_x >= room.x
&& stair_y >= room.y && stair_y >= room.y
&& (stair_x + stair_size) <= (room.x + room.width) && (stair_x + stair_width) <= (room.x + room.width)
&& (stair_y + stair_size) <= (room.y + room.height) && (stair_y + stair_height) <= (room.y + room.height)
{ {
return false; return false;
} }
@@ -480,8 +481,8 @@ pub fn ensure_stair_in_room(layout: &mut DungeonLayout, stair: &layout::Staircas
let room = &mut layout.rooms[idx]; let room = &mut layout.rooms[idx];
let new_x = room.x.min(stair_x); let new_x = room.x.min(stair_x);
let new_y = room.y.min(stair_y); let new_y = room.y.min(stair_y);
let new_right = (room.x + room.width).max(stair_x + stair_size); let new_right = (room.x + room.width).max(stair_x + stair_width);
let new_bottom = (room.y + room.height).max(stair_y + stair_size); let new_bottom = (room.y + room.height).max(stair_y + stair_height);
room.x = new_x; room.x = new_x;
room.y = new_y; room.y = new_y;
@@ -493,8 +494,8 @@ pub fn ensure_stair_in_room(layout: &mut DungeonLayout, stair: &layout::Staircas
layout.rooms.push(layout::Room { layout.rooms.push(layout::Room {
x: stair_x, x: stair_x,
y: stair_y, y: stair_y,
width: stair_size, width: stair_width,
height: stair_size, height: stair_height,
}); });
true true
} }
@@ -502,16 +503,16 @@ pub fn ensure_stair_in_room(layout: &mut DungeonLayout, stair: &layout::Staircas
// Compute the minimum Manhattan distance between a room and a staircase. // Compute the minimum Manhattan distance between a room and a staircase.
fn room_to_stair_min_dist(room: &layout::Room, stair: &layout::Staircase) -> usize { fn room_to_stair_min_dist(room: &layout::Room, stair: &layout::Staircase) -> usize {
let dx = if stair.cell.0 + stair.size <= room.x { let dx = if stair.cell.0 + stair.width <= room.x {
room.x - (stair.cell.0 + stair.size) room.x - (stair.cell.0 + stair.width)
} else if stair.cell.0 >= room.x + room.width { } else if stair.cell.0 >= room.x + room.width {
stair.cell.0 - (room.x + room.width) stair.cell.0 - (room.x + room.width)
} else { } else {
0 0
}; };
let dy = if stair.cell.1 + stair.size <= room.y { let dy = if stair.cell.1 + stair.height <= room.y {
room.y - (stair.cell.1 + stair.size) room.y - (stair.cell.1 + stair.height)
} else if stair.cell.1 >= room.y + room.height { } else if stair.cell.1 >= room.y + room.height {
stair.cell.1 - (room.y + room.height) stair.cell.1 - (room.y + room.height)
} else { } else {
@@ -595,15 +596,19 @@ fn pick_stair_positions(
stair_cells stair_cells
.into_iter() .into_iter()
.enumerate() .enumerate()
.map(|(i, cell)| layout::Staircase { .map(|(i, cell)| {
cell, let (width, height) = random_stair_size(
size: random_stair_size(
settings, settings,
seed::derive_seed( seed::derive_seed(
settings.seed, settings.seed,
STAIR_MARKER_STREAM_BASE + i as u64 + seed_offset * 1000, STAIR_MARKER_STREAM_BASE + i as u64 + seed_offset * 1000,
), ),
), );
layout::Staircase {
cell,
width,
height,
}
}) })
.collect() .collect()
} }
@@ -626,11 +631,18 @@ fn has_start_or_end_on_bottom_row(
} }
// Pick a random stair size within settings range. // Pick a random stair size within settings range.
fn random_stair_size(settings: &UiSettings, seed_value: u64) -> usize { fn random_stair_size(settings: &UiSettings, seed_value: u64) -> (usize, usize) {
let min_size = settings.min_stair_width.min(settings.min_stair_height); let w_span = settings
let max_size = settings.max_stair_width.max(settings.max_stair_height); .max_stair_width
let span = max_size - min_size + 1; .saturating_sub(settings.min_stair_width)
min_size + (seed_value as usize % span) + 1;
let h_span = settings
.max_stair_height
.saturating_sub(settings.min_stair_height)
+ 1;
let width = settings.min_stair_width + (seed_value as usize % w_span);
let height = settings.min_stair_height + (seed_value.rotate_left(17) as usize % h_span);
(width, height)
} }
// Shuffle a vector deterministically using a seed. // Shuffle a vector deterministically using a seed.