fixed a bug with start and end marker placement
This commit is contained in:
+15
@@ -186,6 +186,10 @@ impl eframe::App for DungeonApp {
|
|||||||
} else {
|
} else {
|
||||||
self.levels
|
self.levels
|
||||||
.push(generate_level(&self.settings, self.levels.len()));
|
.push(generate_level(&self.settings, self.levels.len()));
|
||||||
|
// Stairs first so markers can be placed away from level transitions.
|
||||||
|
let stair_results = layout::populate_stairs(self.levels.clone(), &self.settings);
|
||||||
|
self.levels = stair_results.into_iter().map(|(l, _)| l).collect();
|
||||||
|
layout::assign_start_end_markers(&mut self.levels, &self.settings);
|
||||||
self.refresh_stairs();
|
self.refresh_stairs();
|
||||||
}
|
}
|
||||||
self.settings.active_level_index = self.levels.len() - 1;
|
self.settings.active_level_index = self.levels.len() - 1;
|
||||||
@@ -410,6 +414,11 @@ pub fn generate_all_levels(settings: &UiSettings) -> Vec<DungeonLayout> {
|
|||||||
for i in 0..level_count {
|
for i in 0..level_count {
|
||||||
levels.push(generate_level(settings, i));
|
levels.push(generate_level(settings, i));
|
||||||
}
|
}
|
||||||
|
// Populate stairs first so assign_start_end_markers can place start/end
|
||||||
|
// markers as far from the stairs (level transitions) as possible.
|
||||||
|
let stair_results = layout::populate_stairs(levels, settings);
|
||||||
|
let mut levels: Vec<_> = stair_results.into_iter().map(|(l, _)| l).collect();
|
||||||
|
layout::assign_start_end_markers(&mut levels, settings);
|
||||||
levels
|
levels
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -604,6 +613,12 @@ impl DungeonApp {
|
|||||||
self.settings.overlay_level_index = Some(overlay_idx - 1);
|
self.settings.overlay_level_index = Some(overlay_idx - 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if !self.settings.composition_mode {
|
||||||
|
// Stairs first so markers can be placed away from level transitions.
|
||||||
|
let stair_results = layout::populate_stairs(self.levels.clone(), &self.settings);
|
||||||
|
self.levels = stair_results.into_iter().map(|(l, _)| l).collect();
|
||||||
|
layout::assign_start_end_markers(&mut self.levels, &self.settings);
|
||||||
|
}
|
||||||
self.reset_transient_state();
|
self.reset_transient_state();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,8 @@ use crate::seed;
|
|||||||
use crate::ui::UiSettings;
|
use crate::ui::UiSettings;
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
|
|
||||||
// Populates random markers.
|
// Populates per-level markers (traps and monsters only).
|
||||||
|
// Start/end markers are assigned across levels by assign_start_end_markers in app.rs.
|
||||||
pub fn populate_random_markers(mut layout: DungeonLayout, settings: &UiSettings) -> DungeonLayout {
|
pub fn populate_random_markers(mut layout: DungeonLayout, settings: &UiSettings) -> DungeonLayout {
|
||||||
layout.start_markers.clear();
|
layout.start_markers.clear();
|
||||||
layout.end_markers.clear();
|
layout.end_markers.clear();
|
||||||
@@ -21,6 +22,33 @@ pub fn populate_random_markers(mut layout: DungeonLayout, settings: &UiSettings)
|
|||||||
return layout;
|
return layout;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
layout = populate_random_traps(layout, settings);
|
||||||
|
layout = populate_random_monsters(layout, settings);
|
||||||
|
layout
|
||||||
|
}
|
||||||
|
|
||||||
|
// Distributes start/end markers across all dungeon levels.
|
||||||
|
//
|
||||||
|
// Start markers are placed on the first level, as far as possible from that
|
||||||
|
// level's stairs (so the player has to traverse the level to reach them).
|
||||||
|
// End markers are placed on the last level, as far as possible from that
|
||||||
|
// level's stairs. Extra markers beyond the first pair spread to adjacent
|
||||||
|
// levels using the same logic.
|
||||||
|
//
|
||||||
|
// When there is only one level the start is placed at the room farthest from
|
||||||
|
// the end (using the original farthest_room_pair logic).
|
||||||
|
pub fn assign_start_end_markers(layouts: &mut Vec<DungeonLayout>, settings: &UiSettings) {
|
||||||
|
let num_levels = layouts.len();
|
||||||
|
if num_levels == 0 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear any stale markers on all levels first.
|
||||||
|
for layout in layouts.iter_mut() {
|
||||||
|
layout.start_markers.clear();
|
||||||
|
layout.end_markers.clear();
|
||||||
|
}
|
||||||
|
|
||||||
let start_count = random_range_inclusive(
|
let start_count = random_range_inclusive(
|
||||||
settings.min_start_marker_count.max(1),
|
settings.min_start_marker_count.max(1),
|
||||||
settings.max_start_marker_count.max(1),
|
settings.max_start_marker_count.max(1),
|
||||||
@@ -31,36 +59,39 @@ pub fn populate_random_markers(mut layout: DungeonLayout, settings: &UiSettings)
|
|||||||
settings.max_end_marker_count.max(1),
|
settings.max_end_marker_count.max(1),
|
||||||
seed::derive_seed(settings.seed, 10_002),
|
seed::derive_seed(settings.seed, 10_002),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if num_levels == 1 {
|
||||||
|
// Single level: place start and end as far apart as possible within it.
|
||||||
|
let layout = &mut layouts[0];
|
||||||
|
if layout.rooms.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let all_rooms: Vec<usize> = (0..layout.rooms.len()).collect();
|
||||||
|
let mut available_start = all_rooms.clone();
|
||||||
|
let mut available_end = all_rooms.clone();
|
||||||
|
|
||||||
let pair_count = start_count.min(end_count);
|
let pair_count = start_count.min(end_count);
|
||||||
|
|
||||||
let mut available_start_rooms: Vec<usize> = (0..layout.rooms.len()).collect();
|
|
||||||
let mut available_end_rooms: Vec<usize> = (0..layout.rooms.len()).collect();
|
|
||||||
|
|
||||||
for pair_idx in 0..pair_count {
|
for pair_idx in 0..pair_count {
|
||||||
let (start_room_idx, end_room_idx) =
|
let (si, ei) = farthest_room_pair(&layout.rooms, &available_start, &available_end);
|
||||||
farthest_room_pair(&layout.rooms, &available_start_rooms, &available_end_rooms);
|
|
||||||
let start_room = &layout.rooms[start_room_idx];
|
|
||||||
let end_room = &layout.rooms[end_room_idx];
|
|
||||||
layout.start_markers.push(marker_in_room(
|
layout.start_markers.push(marker_in_room(
|
||||||
start_room,
|
&layout.rooms[si],
|
||||||
settings.min_start_marker_size,
|
settings.min_start_marker_size,
|
||||||
settings.max_start_marker_size,
|
settings.max_start_marker_size,
|
||||||
seed::derive_seed(settings.seed, 11_000 + pair_idx as u64),
|
seed::derive_seed(settings.seed, 11_000 + pair_idx as u64),
|
||||||
));
|
));
|
||||||
layout.end_markers.push(marker_in_room(
|
layout.end_markers.push(marker_in_room(
|
||||||
end_room,
|
&layout.rooms[ei],
|
||||||
settings.min_end_marker_size,
|
settings.min_end_marker_size,
|
||||||
settings.max_end_marker_size,
|
settings.max_end_marker_size,
|
||||||
seed::derive_seed(settings.seed, 12_000 + pair_idx as u64),
|
seed::derive_seed(settings.seed, 12_000 + pair_idx as u64),
|
||||||
));
|
));
|
||||||
consume_room(&mut available_start_rooms, start_room_idx);
|
consume_room(&mut available_start, si);
|
||||||
consume_room(&mut available_end_rooms, end_room_idx);
|
consume_room(&mut available_end, ei);
|
||||||
}
|
}
|
||||||
|
|
||||||
assign_extra_markers(
|
assign_extra_markers(
|
||||||
&mut layout.start_markers,
|
&mut layout.start_markers,
|
||||||
&layout.rooms,
|
&layout.rooms,
|
||||||
&mut available_start_rooms,
|
&mut available_start,
|
||||||
pair_count,
|
pair_count,
|
||||||
start_count,
|
start_count,
|
||||||
settings.min_start_marker_size,
|
settings.min_start_marker_size,
|
||||||
@@ -72,7 +103,7 @@ pub fn populate_random_markers(mut layout: DungeonLayout, settings: &UiSettings)
|
|||||||
assign_extra_markers(
|
assign_extra_markers(
|
||||||
&mut layout.end_markers,
|
&mut layout.end_markers,
|
||||||
&layout.rooms,
|
&layout.rooms,
|
||||||
&mut available_end_rooms,
|
&mut available_end,
|
||||||
pair_count,
|
pair_count,
|
||||||
end_count,
|
end_count,
|
||||||
settings.min_end_marker_size,
|
settings.min_end_marker_size,
|
||||||
@@ -81,10 +112,115 @@ pub fn populate_random_markers(mut layout: DungeonLayout, settings: &UiSettings)
|
|||||||
15_000,
|
15_000,
|
||||||
16_000,
|
16_000,
|
||||||
);
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
layout = populate_random_traps(layout, settings);
|
// Multi-level: starts go on the first level far from its stairs,
|
||||||
layout = populate_random_monsters(layout, settings);
|
// ends go on the last level far from its stairs.
|
||||||
layout
|
// Extra markers spill to adjacent levels using the same rule.
|
||||||
|
for idx in 0..start_count {
|
||||||
|
let level_idx = idx.min(num_levels - 1);
|
||||||
|
let layout = &mut layouts[level_idx];
|
||||||
|
if layout.rooms.is_empty() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// Build a list of rooms sorted by distance from stairs on this level,
|
||||||
|
// farthest first. Rooms with no stair proximity get the whole list.
|
||||||
|
let mut available = rooms_farthest_from_stairs(layout);
|
||||||
|
// Remove rooms already claimed by previously placed start markers.
|
||||||
|
for placed_idx in 0..layout.start_markers.len() {
|
||||||
|
let placed_cell = layout.start_markers[placed_idx].cell;
|
||||||
|
if let Some(pos) = available.iter().position(|&r| {
|
||||||
|
let room = &layout.rooms[r];
|
||||||
|
placed_cell.0 >= room.x && placed_cell.0 < room.x + room.width
|
||||||
|
&& placed_cell.1 >= room.y && placed_cell.1 < room.y + room.height
|
||||||
|
}) {
|
||||||
|
available.remove(pos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let room_idx = if available.is_empty() {
|
||||||
|
pick_room_index(&(0..layout.rooms.len()).collect::<Vec<_>>(),
|
||||||
|
layout.rooms.len(), seed::derive_seed(settings.seed, 11_000 + idx as u64))
|
||||||
|
} else {
|
||||||
|
available[0]
|
||||||
|
};
|
||||||
|
let marker = marker_in_room(
|
||||||
|
&layout.rooms[room_idx],
|
||||||
|
settings.min_start_marker_size,
|
||||||
|
settings.max_start_marker_size,
|
||||||
|
seed::derive_seed(settings.seed, 11_500 + idx as u64),
|
||||||
|
);
|
||||||
|
layout.start_markers.push(marker);
|
||||||
|
}
|
||||||
|
|
||||||
|
for idx in 0..end_count {
|
||||||
|
let level_idx = num_levels.saturating_sub(1 + idx.min(num_levels - 1));
|
||||||
|
let layout = &mut layouts[level_idx];
|
||||||
|
if layout.rooms.is_empty() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let mut available = rooms_farthest_from_stairs(layout);
|
||||||
|
for placed_idx in 0..layout.end_markers.len() {
|
||||||
|
let placed_cell = layout.end_markers[placed_idx].cell;
|
||||||
|
if let Some(pos) = available.iter().position(|&r| {
|
||||||
|
let room = &layout.rooms[r];
|
||||||
|
placed_cell.0 >= room.x && placed_cell.0 < room.x + room.width
|
||||||
|
&& placed_cell.1 >= room.y && placed_cell.1 < room.y + room.height
|
||||||
|
}) {
|
||||||
|
available.remove(pos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let room_idx = if available.is_empty() {
|
||||||
|
pick_room_index(&(0..layout.rooms.len()).collect::<Vec<_>>(),
|
||||||
|
layout.rooms.len(), seed::derive_seed(settings.seed, 12_000 + idx as u64))
|
||||||
|
} else {
|
||||||
|
available[0]
|
||||||
|
};
|
||||||
|
let marker = marker_in_room(
|
||||||
|
&layout.rooms[room_idx],
|
||||||
|
settings.min_end_marker_size,
|
||||||
|
settings.max_end_marker_size,
|
||||||
|
seed::derive_seed(settings.seed, 12_500 + idx as u64),
|
||||||
|
);
|
||||||
|
layout.end_markers.push(marker);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns room indices sorted by their minimum distance from any staircase on
|
||||||
|
// this level, farthest first. If the level has no stairs all rooms are
|
||||||
|
// returned in their natural order (the caller falls back to room-pair logic).
|
||||||
|
fn rooms_farthest_from_stairs(layout: &DungeonLayout) -> Vec<usize> {
|
||||||
|
if layout.stairs.is_empty() {
|
||||||
|
return (0..layout.rooms.len()).collect();
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut distances: Vec<(usize, usize)> = layout
|
||||||
|
.rooms
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(idx, room)| {
|
||||||
|
// Minimum squared distance from this room's center to any stair's center.
|
||||||
|
let min_dist = layout
|
||||||
|
.stairs
|
||||||
|
.iter()
|
||||||
|
.map(|stair| {
|
||||||
|
let stair_cx = stair.cell.0 * 2 + stair.width;
|
||||||
|
let stair_cy = stair.cell.1 * 2 + stair.height;
|
||||||
|
let room_cx = room.x * 2 + room.width;
|
||||||
|
let room_cy = room.y * 2 + room.height;
|
||||||
|
let dx = stair_cx.abs_diff(room_cx);
|
||||||
|
let dy = stair_cy.abs_diff(room_cy);
|
||||||
|
dx * dx + dy * dy
|
||||||
|
})
|
||||||
|
.min()
|
||||||
|
.unwrap_or(0);
|
||||||
|
(idx, min_dist)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
// Sort farthest first.
|
||||||
|
distances.sort_by(|a, b| b.1.cmp(&a.1));
|
||||||
|
distances.into_iter().map(|(idx, _)| idx).collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generates range inclusive.
|
// Generates range inclusive.
|
||||||
|
|||||||
Reference in New Issue
Block a user