- Update landgen to use the latest rand crate
authorunC0Rr
Tue, 28 Jan 2025 15:49:45 +0100
changeset 16073 5d302b12d837
parent 16072 a4cbc6926439
child 16074 fff7c8e36f7b
- Update landgen to use the latest rand crate - Change Size width and height from usize to u32 for portability - Implement backtracking in wfc generator
rust/integral-geometry/src/lib.rs
rust/land2d/src/lib.rs
rust/landgen/Cargo.toml
rust/landgen/src/maze.rs
rust/landgen/src/outline_template_based/outline.rs
rust/landgen/src/outline_template_based/template_based.rs
rust/landgen/src/wavefront_collapse/generator.rs
rust/landgen/src/wavefront_collapse/wavefront_collapse.rs
rust/lib-hwengine-future/src/lib.rs
rust/mapgen/src/lib.rs
rust/mapgen/src/template/maze.rs
rust/mapgen/src/template/outline.rs
rust/mapgen/src/template/wavefront_collapse.rs
rust/mapgen/src/theme.rs
rust/vec2d/src/lib.rs
--- a/rust/integral-geometry/src/lib.rs	Tue Jan 28 10:37:46 2025 +0100
+++ b/rust/integral-geometry/src/lib.rs	Tue Jan 28 15:49:45 2025 +0100
@@ -113,20 +113,20 @@
 
 #[derive(PartialEq, Eq, Clone, Copy, Debug)]
 pub struct Size {
-    pub width: usize,
-    pub height: usize,
+    pub width: u32,
+    pub height: u32,
 }
 
 impl Size {
     pub const EMPTY: Self = Self::square(0);
 
     #[inline]
-    pub const fn new(width: usize, height: usize) -> Self {
+    pub const fn new(width: u32, height: u32) -> Self {
         Self { width, height }
     }
 
     #[inline]
-    pub const fn square(size: usize) -> Self {
+    pub const fn square(size: u32) -> Self {
         Self {
             width: size,
             height: size,
@@ -134,12 +134,12 @@
     }
 
     #[inline]
-    pub const fn area(&self) -> usize {
+    pub const fn area(&self) -> u32 {
         self.width * self.height
     }
 
     #[inline]
-    pub const fn linear_index(&self, x: usize, y: usize) -> usize {
+    pub const fn linear_index(&self, x: u32, y: u32) -> u32 {
         y * self.width + x
     }
 
@@ -194,7 +194,7 @@
 
 impl PotSize {
     #[inline]
-    const fn new_impl(width: usize, height: usize) -> Self {
+    const fn new_impl(width: u32, height: u32) -> Self {
         debug_assert!(width.is_power_of_two() && height.is_power_of_two());
         Self {
             size: Size::new(width, height),
@@ -202,7 +202,7 @@
     }
 
     #[inline]
-    pub const fn new(width: usize, height: usize) -> Option<Self> {
+    pub const fn new(width: u32, height: u32) -> Option<Self> {
         if width.is_power_of_two() && height.is_power_of_two() {
             Some(Self::new_impl(width, height))
         } else {
@@ -214,16 +214,16 @@
         self.size
     }
 
-    pub const fn width(&self) -> usize {
+    pub const fn width(&self) -> u32 {
         self.size.width
     }
 
-    pub const fn height(&self) -> usize {
+    pub const fn height(&self) -> u32 {
         self.size.height
     }
 
     #[inline]
-    pub const fn square(size: usize) -> Option<Self> {
+    pub const fn square(size: u32) -> Option<Self> {
         if size.is_power_of_two() {
             Some(Self::new_impl(size, size))
         } else {
@@ -232,12 +232,12 @@
     }
 
     #[inline]
-    pub const fn area(&self) -> usize {
+    pub const fn area(&self) -> u32 {
         self.size.area()
     }
 
     #[inline]
-    pub const fn linear_index(&self, x: usize, y: usize) -> usize {
+    pub const fn linear_index(&self, x: u32, y: u32) -> u32 {
         self.size.linear_index(x, y)
     }
 
@@ -289,18 +289,18 @@
     }
 
     #[inline]
-    pub fn contains_x<T: Into<usize>>(&self, x: T) -> bool {
+    pub fn contains_x<T: Into<u32>>(&self, x: T) -> bool {
         (self.size.width & x.into()) == 0
     }
 
     #[inline]
-    pub fn contains_y<T: Into<usize>>(&self, y: T) -> bool {
+    pub fn contains_y<T: Into<u32>>(&self, y: T) -> bool {
         (self.size.height & y.into()) == 0
     }
 
     #[inline]
     pub fn contains(&self, point: Point) -> bool {
-        self.contains_x(point.x as usize) && self.contains_y(point.y as usize)
+        self.contains_x(point.x as u32) && self.contains_y(point.y as u32)
     }
 
     #[inline]
@@ -439,7 +439,7 @@
         )
     }
 
-    pub fn from_size_coords(x: i32, y: i32, width: usize, height: usize) -> Self {
+    pub fn from_size_coords(x: i32, y: i32, width: u32, height: u32) -> Self {
         Self::from_size(Point::new(x, y), Size::new(width, height))
     }
 
@@ -448,13 +448,13 @@
     }
 
     #[inline]
-    pub const fn width(&self) -> usize {
-        (self.right() - self.left() + 1) as usize
+    pub const fn width(&self) -> u32 {
+        (self.right() - self.left() + 1) as u32
     }
 
     #[inline]
-    pub const fn height(&self) -> usize {
-        (self.bottom() - self.top() + 1) as usize
+    pub const fn height(&self) -> u32 {
+        (self.bottom() - self.top() + 1) as u32
     }
 
     #[inline]
@@ -463,7 +463,7 @@
     }
 
     #[inline]
-    pub const fn area(&self) -> usize {
+    pub const fn area(&self) -> u32 {
         self.size().area()
     }
 
@@ -504,7 +504,10 @@
 
     #[inline]
     pub fn with_margin(&self, margin: i32) -> Self {
-        let offset = Point::new(min(margin, self.width() as i32 / 2), min(margin, self.height() as i32 / 2));
+        let offset = Point::new(
+            min(margin, self.width() as i32 / 2),
+            min(margin, self.height() as i32 / 2),
+        );
         Self::new(self.top_left() + offset, self.bottom_right() - offset)
     }
 
@@ -566,7 +569,7 @@
     }
 
     #[inline]
-    pub fn quotient(self, x: usize, y: usize) -> Point {
+    pub fn quotient(self, x: u32, y: u32) -> Point {
         self.top_left() + Point::new((x % self.width()) as i32, (y % self.height()) as i32)
     }
 }
--- a/rust/land2d/src/lib.rs	Tue Jan 28 10:37:46 2025 +0100
+++ b/rust/land2d/src/lib.rs	Tue Jan 28 15:49:45 2025 +0100
@@ -48,12 +48,12 @@
     }
 
     #[inline]
-    pub fn play_width(&self) -> usize {
+    pub fn play_width(&self) -> u32 {
         self.play_box.width()
     }
 
     #[inline]
-    pub fn play_height(&self) -> usize {
+    pub fn play_height(&self) -> u32 {
         self.play_box.height()
     }
 
@@ -69,12 +69,12 @@
 
     #[inline]
     pub fn is_valid_x(&self, x: i32) -> bool {
-        self.mask.contains_x(x as usize)
+        self.mask.contains_x(x as u32)
     }
 
     #[inline]
     pub fn is_valid_y(&self, y: i32) -> bool {
-        self.mask.contains_y(y as usize)
+        self.mask.contains_y(y as u32)
     }
 
     #[inline]
@@ -150,7 +150,7 @@
             dir: isize,
         ) {
             let yd = y as isize + dir;
-            if mask.contains_y(yd as usize) {
+            if mask.contains_y(yd as u32) {
                 stack.push((xl, xr, yd as usize, dir));
             }
         }
@@ -331,7 +331,7 @@
 
     #[test]
     fn basics() {
-        let l: Land2D<u8> = Land2D::new(Size::new(30, 50), 0);
+        let l: Land2D<u8> = Land2D::new(&Size::new(30, 50), 0);
 
         assert_eq!(l.play_width(), 30);
         assert_eq!(l.play_height(), 50);
@@ -348,7 +348,7 @@
 
     #[test]
     fn fill() {
-        let mut l: Land2D<u8> = Land2D::new(Size::square(128), 0);
+        let mut l: Land2D<u8> = Land2D::new(&Size::square(128), 0);
 
         l.draw_line(Line::new(Point::new(0, 0), Point::new(32, 96)), 1);
         l.draw_line(Line::new(Point::new(32, 96), Point::new(64, 32)), 1);
--- a/rust/landgen/Cargo.toml	Tue Jan 28 10:37:46 2025 +0100
+++ b/rust/landgen/Cargo.toml	Tue Jan 28 15:49:45 2025 +0100
@@ -9,7 +9,7 @@
 land2d = { path = "../land2d" }
 vec2d = { path = "../vec2d" }
 png = "0.17"
-rand = "0.8"
+rand = "0.9"
 
 [dev-dependencies]
 criterion = "0.5"
--- a/rust/landgen/src/maze.rs	Tue Jan 28 10:37:46 2025 +0100
+++ b/rust/landgen/src/maze.rs	Tue Jan 28 15:49:45 2025 +0100
@@ -3,12 +3,13 @@
 use integral_geometry::{Point, Polygon, Rect, Size};
 use land2d::Land2D;
 use rand::Rng;
+use vec2d::Vec2D;
 
 #[derive(Clone)]
 pub struct MazeTemplate {
-    pub width: usize,
-    pub height: usize,
-    pub cell_size: usize,
+    pub width: u32,
+    pub height: u32,
+    pub cell_size: u32,
     pub inverted: bool,
     pub distortion_limiting_factor: u32,
     pub braidness: u32,
@@ -21,10 +22,10 @@
     num_cells: Size,
     num_edges: Size,
     seen_cells: Size,
-    cell_size: usize,
-    seen_list: Vec<Vec<Option<usize>>>,
-    walls: Vec<Vec<Vec<bool>>>,
-    edge_list: Vec<Vec<Vec<bool>>>,
+    cell_size: u32,
+    seen_list: Vec2D<Option<usize>>,
+    walls: Vec<Vec2D<bool>>,
+    edge_list: Vec<Vec2D<bool>>,
     last_cell: Vec<Point>,
     came_from: Vec<Vec<Point>>,
     came_from_pos: Vec<i32>,
@@ -80,7 +81,7 @@
 impl Maze {
     pub fn new(
         size: &Size,
-        cell_size: usize,
+        cell_size: u32,
         num_steps: usize,
         inverted: bool,
         braidness: u32,
@@ -96,17 +97,17 @@
 
         let mut last_cell = vec![Point::diag(0); num_steps];
         let came_from_pos = vec![0; num_steps];
-        let came_from = vec![vec![Point::diag(0); num_steps]; num_cells.area()];
+        let came_from = vec![vec![Point::diag(0); num_steps]; num_cells.area() as usize];
 
-        let seen_list = vec![vec![None as Option<usize>; seen_cells.width]; seen_cells.height];
-        let walls = vec![vec![vec![true; seen_cells.width]; seen_cells.height]; 2];
-        let edge_list = vec![vec![vec![false; num_cells.width]; num_cells.height]; 2];
+        let seen_list = Vec2D::new(&seen_cells, None);
+        let walls = vec![Vec2D::new(&seen_cells, true); 2];
+        let edge_list = vec![Vec2D::new(&num_cells, false); 2];
 
         for current_step in 0..num_steps {
-            let x = random_numbers.gen_range(0..seen_cells.width - 1) / num_steps;
+            let x = random_numbers.random_range(0..seen_cells.width as i32 - 1) / num_steps as i32;
             last_cell[current_step] = Point::new(
-                (x + current_step * seen_cells.width / num_steps) as i32,
-                random_numbers.gen_range(0..seen_cells.height) as i32,
+                x + current_step as i32 * seen_cells.width as i32 / num_steps as i32,
+                random_numbers.random_range(..seen_cells.height) as i32,
             );
         }
 
@@ -141,7 +142,7 @@
             let p = self.last_cell[current_step];
             self.seen_list[p.y as usize][p.x as usize] = Some(current_step);
 
-            let next_dir_clockwise = random_numbers.gen();
+            let next_dir_clockwise = random_numbers.random();
 
             for _ in 0..5 {
                 let sp = p + dir.0;
@@ -158,7 +159,7 @@
                 match when_seen {
                     Some(a) if a == current_step => {
                         // try another direction
-                        if !self.inverted && random_numbers.gen_range(0..self.braidness) == 0 {
+                        if !self.inverted && random_numbers.random_range(..self.braidness) == 0 {
                             if dir.0.x == -1 && p.x > 0 {
                                 self.walls[dir.orientation()][p.y as usize][p.x as usize - 1] =
                                     false;
@@ -261,46 +262,46 @@
     pub fn to_islands(mut self) -> (Vec<Polygon>, Vec<Point>) {
         let mut islands: Vec<Polygon> = vec![];
         let mut polygon: Vec<Point> = vec![];
-        let mut maze = vec![vec![false; self.num_cells.width]; self.num_cells.height];
+        let mut maze = Vec2D::new(&self.num_cells, false);
 
-        for x in 0..self.seen_cells.width {
-            for y in 0..self.seen_cells.height {
+        for x in 0..self.seen_cells.width as usize {
+            for y in 0..self.seen_cells.height as usize {
                 if self.seen_list[y][x].is_some() {
                     maze[y * 2 + 1][x * 2 + 1] = true;
                 }
             }
 
-            for y in 0..self.seen_cells.height - 1 {
+            for y in 0..self.seen_cells.height as usize - 1 {
                 if !self.walls[0][y][x] {
                     maze[y * 2 + 2][x * 2 + 1] = true;
                 }
             }
         }
 
-        for x in 0..self.seen_cells.width - 1 {
-            for y in 0..self.seen_cells.height {
+        for x in 0..self.seen_cells.width as usize - 1 {
+            for y in 0..self.seen_cells.height as usize {
                 if !self.walls[1][y][x] {
                     maze[y * 2 + 1][x * 2 + 2] = true;
                 }
             }
         }
 
-        for x in 0..self.num_edges.width {
-            for y in 0..self.num_cells.height {
+        for x in 0..self.num_edges.width as usize {
+            for y in 0..self.num_cells.height as usize {
                 self.edge_list[0][y][x] = maze[y][x] != maze[y][x + 1];
             }
         }
 
-        for x in 0..self.num_cells.width {
-            for y in 0..self.num_edges.height {
+        for x in 0..self.num_cells.width as usize {
+            for y in 0..self.num_edges.height as usize {
                 self.edge_list[1][y][x] = maze[y][x] != maze[y + 1][x];
             }
         }
 
         let mut fill_points = vec![];
 
-        for x in 0..self.num_edges.width {
-            for y in 0..self.num_cells.height {
+        for x in 0..self.num_edges.width as usize {
+            for y in 0..self.num_cells.height as usize {
                 if self.edge_list[0][y][x] {
                     self.edge_list[0][y][x] = false;
                     self.add_vertex(Point::new(x as i32 + 1, y as i32 + 1), &mut polygon);
@@ -328,13 +329,13 @@
             }
         }
 
-        for x in 0..self.num_cells.width {
-            for y in 0..self.num_cells.height {
+        for x in 0..self.num_cells.width as usize {
+            for y in 0..self.num_cells.height as usize {
                 if maze[y][x] {
                     let half_cell = self.cell_size / 2;
                     let fill_point = Point::new(
-                        (x * self.cell_size + half_cell) as i32 + self.off.x,
-                        (y * self.cell_size + half_cell) as i32 + self.off.y,
+                        (x as u32 * self.cell_size + half_cell) as i32 + self.off.x,
+                        (y as u32 * self.cell_size + half_cell) as i32 + self.off.y,
                     );
                     islands.push(Polygon::new(&[fill_point]));
                     fill_points.push(fill_point);
@@ -348,13 +349,13 @@
                             if x > 0 {
                                 points.push((x - 1, y));
                             }
-                            if x < self.num_cells.width - 1 {
+                            if x < self.num_cells.width as usize - 1 {
                                 points.push((x + 1, y));
                             }
                             if y > 0 {
                                 points.push((x, y - 1));
                             }
-                            if y < self.num_cells.height - 1 {
+                            if y < self.num_cells.height as usize - 1 {
                                 points.push((x, y + 1));
                             }
                         }
@@ -371,7 +372,7 @@
     maze_template: MazeTemplate,
 }
 
-fn prev_odd(num: usize) -> usize {
+fn prev_odd(num: u32) -> u32 {
     if num & 1 == 0 {
         num - 1
     } else {
@@ -409,7 +410,7 @@
 
             for current_step in 0..num_steps {
                 if !step_done[current_step] {
-                    let dir = Direction::new(random_numbers.gen());
+                    let dir = Direction::new(random_numbers.random_range(..4));
                     step_done[current_step] = maze.see_cell(current_step, dir, random_numbers);
                     done = false;
                 }
--- a/rust/landgen/src/outline_template_based/outline.rs	Tue Jan 28 10:37:46 2025 +0100
+++ b/rust/landgen/src/outline_template_based/outline.rs	Tue Jan 28 15:49:45 2025 +0100
@@ -27,7 +27,7 @@
                 .map(|i| {
                     i.iter()
                         .map(|rect| {
-                            let (rnd_a, rnd_b) = random_numbers.gen();
+                            let (rnd_a, rnd_b) = random_numbers.random();
                             play_box.top_left() + rect.quotient(rnd_a, rnd_b)
                         })
                         .collect::<Vec<_>>()
@@ -241,8 +241,9 @@
             Some(mid_point)
         } else {
             // select distance within [-dist_right; dist_left], keeping min_distance in mind
-            let d = random_numbers
-                .gen_range(-(dist_right as i32) + min_distance..=dist_left as i32 - min_distance);
+            let d = random_numbers.random_range(
+                -(dist_right as i32) + min_distance..=dist_left as i32 - min_distance,
+            );
 
             Some(mid_point + normal * d / normal_len as i32)
         }
--- a/rust/landgen/src/outline_template_based/template_based.rs	Tue Jan 28 10:37:46 2025 +0100
+++ b/rust/landgen/src/outline_template_based/template_based.rs	Tue Jan 28 15:49:45 2025 +0100
@@ -20,7 +20,7 @@
         random_numbers: &mut impl Rng,
     ) -> Land2D<T> {
         let do_invert = self.outline_template.is_negative
-            && (!self.outline_template.can_invert || random_numbers.gen());
+            && (!self.outline_template.can_invert || random_numbers.random());
         let (basic, zero) = if do_invert {
             (parameters.zero, parameters.basic)
         } else {
@@ -37,17 +37,17 @@
         );
 
         // mirror
-        if self.outline_template.can_mirror && random_numbers.gen() {
+        if self.outline_template.can_mirror && random_numbers.random() {
             points.mirror();
         }
 
         // flip
-        if self.outline_template.can_flip && random_numbers.gen() {
+        if self.outline_template.can_flip && random_numbers.random() {
             points.flip();
         }
 
         if !parameters.skip_distort {
-            let distortion_limiting_factor = 100 + random_numbers.gen_range(0..8) * 10;
+            let distortion_limiting_factor = 100 + random_numbers.random_range(0..8) * 10;
 
             points.distort(
                 parameters.distance_divisor,
--- a/rust/landgen/src/wavefront_collapse/generator.rs	Tue Jan 28 10:37:46 2025 +0100
+++ b/rust/landgen/src/wavefront_collapse/generator.rs	Tue Jan 28 15:49:45 2025 +0100
@@ -91,10 +91,8 @@
         let mut reader = decoder.read_info()?;
 
         let info = reader.info();
-        let mut tiles_image = vec2d::Vec2D::new(
-            &Size::new(info.width as usize, info.height as usize),
-            parameters.zero,
-        );
+        let mut tiles_image =
+            vec2d::Vec2D::new(&Size::new(info.width, info.height), parameters.zero);
 
         let mut buf = vec![0; reader.output_buffer_size()];
         let info = reader.next_frame(&mut buf)?;
@@ -325,19 +323,20 @@
 
         // render tiles into resulting land array
         let mut result = land2d::Land2D::new(&self.template.size, parameters.zero);
-        let offset_y = result.height() - result.play_height();
-        let offset_x = (result.width() - result.play_width()) / 2;
+        let offset_y = result.height() - result.play_height() as usize;
+        let offset_x = (result.width() - result.play_width() as usize) / 2;
 
-        for row in 0..wfc_size.height {
-            for column in 0..wfc_size.width {
+        for row in 0..wfc_size.height as usize {
+            for column in 0..wfc_size.width as usize {
                 if let Some(Tile::Numbered(tile_index)) = wfc.grid().get(row, column) {
                     let tile = &tiles[*tile_index];
 
-                    for tile_row in 0..tile.size().height {
-                        for tile_column in 0..tile.size().width {
+                    for tile_row in 0..tile.size().height as usize {
+                        for tile_column in 0..tile.size().width as usize {
                             result.map(
-                                (row * tile.size().height + tile_row + offset_y) as i32,
-                                (column * tile.size().width + tile_column + offset_x) as i32,
+                                (row * tile.size().height as usize + tile_row + offset_y) as i32,
+                                (column * tile.size().width as usize + tile_column + offset_x)
+                                    as i32,
                                 |p| {
                                     *p =
                                         *tile.get(tile_row, tile_column).unwrap_or(&parameters.zero)
--- a/rust/landgen/src/wavefront_collapse/wavefront_collapse.rs	Tue Jan 28 10:37:46 2025 +0100
+++ b/rust/landgen/src/wavefront_collapse/wavefront_collapse.rs	Tue Jan 28 15:49:45 2025 +0100
@@ -1,6 +1,6 @@
 use integral_geometry::Size;
-use rand::distributions::Distribution;
-use rand::distributions::WeightedIndex;
+use rand::distr::{weighted::WeightedIndex, Distribution};
+use rand::prelude::IndexedRandom;
 use rand::Rng;
 use std::collections::HashSet;
 use vec2d::Vec2D;
@@ -59,7 +59,19 @@
 
         seed_fn(&mut self.grid);
 
-        while self.collapse_step(random_numbers) {}
+        let mut backtracks = 0usize;
+        while let Some(b) = self.collapse_step(random_numbers) {
+            backtracks += b;
+
+            if backtracks >= 1000 {
+                println!("[WFC] Too much backtracking, stopping generation!");
+                break;
+            }
+        }
+
+        if backtracks > 0 {
+            println!("[WFC] Had to backtrack {} times...", backtracks);
+        }
     }
 
     pub fn set_rules(&mut self, rules: Vec<CollapseRule>) {
@@ -109,7 +121,7 @@
         })
     }
 
-    fn collapse_step(&mut self, random_numbers: &mut impl Rng) -> bool {
+    fn collapse_step(&mut self, random_numbers: &mut impl Rng) -> Option<usize> {
         let mut tiles_to_collapse = (usize::MAX, Vec::new());
 
         // Iterate through the tiles in the land
@@ -118,11 +130,16 @@
                 let current_tile = self.get_tile(y, x);
 
                 if let Tile::Empty = current_tile {
+                    let neighbors = [
+                        (y, x.wrapping_add(1)),
+                        (y.wrapping_add(1), x),
+                        (y, x.wrapping_sub(1)),
+                        (y.wrapping_sub(1), x),
+                    ];
+
                     // calc entropy
-                    let right_tile = self.get_tile(y, x.wrapping_add(1));
-                    let bottom_tile = self.get_tile(y.wrapping_add(1), x);
-                    let left_tile = self.get_tile(y, x.wrapping_sub(1));
-                    let top_tile = self.get_tile(y.wrapping_sub(1), x);
+                    let [right_tile, bottom_tile, left_tile, top_tile] =
+                        neighbors.map(|(y, x)| self.get_tile(y, x));
 
                     let possibilities: Vec<(u32, Tile)> = self
                         .rules
@@ -146,7 +163,8 @@
                             let weights = possibilities.iter().map(|(weight, _)| *weight);
                             let distribution = WeightedIndex::new(weights).unwrap();
 
-                            let entry = (y, x, possibilities[distribution.sample(random_numbers)]);
+                            let entry =
+                                (y, x, possibilities[distribution.sample(random_numbers)].1);
 
                             if entropy < tiles_to_collapse.0 {
                                 tiles_to_collapse = (entropy, vec![entry])
@@ -155,12 +173,26 @@
                             }
                         }
                     } else {
-                        /*println!("We're here: {}, {}", x, y);
+                        /*
+                        println!("We're here: {}, {}", x, y);
                         println!(
                             "Neighbour tiles are: {:?} {:?} {:?} {:?}",
                             right_tile, bottom_tile, left_tile, top_tile
                         );
-                        println!("Rules are: {:?}", self.rules);*/
+                        println!("Rules are: {:?}", self.rules);
+                        */
+
+                        let entries = neighbors
+                            .iter()
+                            .filter(|(y, x)| self.grid.get(*y, *x).is_some())
+                            .map(|(y, x)| (*y, *x, Tile::Empty))
+                            .collect::<Vec<_>>();
+
+                        if entropy < tiles_to_collapse.0 {
+                            tiles_to_collapse = (entropy, entries);
+                        } else {
+                            tiles_to_collapse.1.extend(entries);
+                        }
 
                         //todo!("no collapse possible - what to do?")
                     }
@@ -168,23 +200,28 @@
             }
         }
 
-        let tiles_to_collapse = tiles_to_collapse.1;
-        let possibilities_number = tiles_to_collapse.len();
+        if tiles_to_collapse.0 == 0 {
+            // cannot collapse, we're clearing some tiles
 
-        if possibilities_number > 0 {
-            let weights = tiles_to_collapse.iter().map(|(_, _, (weight, _))| *weight);
-            let distribution = WeightedIndex::new(weights).unwrap();
-
-            let (y, x, (_, tile)) = tiles_to_collapse[distribution.sample(random_numbers)];
+            for (y, x, tile) in tiles_to_collapse.1 {
+                *self
+                    .grid
+                    .get_mut(y, x)
+                    .expect("correct iteration over grid") = tile;
+            }
 
-            *self
-                .grid
-                .get_mut(y, x)
-                .expect("correct iteration over grid") = tile;
+            Some(1)
+        } else {
+            if let Some(&(y, x, tile)) = tiles_to_collapse.1.as_slice().choose(random_numbers) {
+                *self
+                    .grid
+                    .get_mut(y, x)
+                    .expect("correct iteration over grid") = tile;
 
-            true
-        } else {
-            false
+                Some(0)
+            } else {
+                None
+            }
         }
     }
 
--- a/rust/lib-hwengine-future/src/lib.rs	Tue Jan 28 10:37:46 2025 +0100
+++ b/rust/lib-hwengine-future/src/lib.rs	Tue Jan 28 15:49:45 2025 +0100
@@ -39,8 +39,8 @@
 #[no_mangle]
 pub extern "C" fn create_empty_game_field(width: u32, height: u32) -> *mut GameField {
     let game_field = Box::new(GameField {
-        collision: land2d::Land2D::new(&Size::new(width as usize, height as usize), 0),
-        pixels: land2d::Land2D::new(&Size::new(width as usize, height as usize), 0),
+        collision: land2d::Land2D::new(&Size::new(width, height), 0),
+        pixels: land2d::Land2D::new(&Size::new(width, height), 0),
         landgen_parameters: None,
     });
 
--- a/rust/mapgen/src/lib.rs	Tue Jan 28 10:37:46 2025 +0100
+++ b/rust/mapgen/src/lib.rs	Tue Jan 28 15:49:45 2025 +0100
@@ -19,10 +19,10 @@
     },
     LandGenerationParameters, LandGenerator,
 };
-use rand::{seq::SliceRandom, Rng};
+use rand::Rng;
 
+use rand::prelude::IndexedRandom;
 use std::{borrow::Borrow, collections::hash_map::HashMap};
-use rand::prelude::IndexedRandom;
 use vec2d::Vec2D;
 
 #[derive(PartialEq, Eq, Hash, Clone, Debug)]
@@ -68,7 +68,7 @@
         if let Some(land_sprite) = theme.land_texture() {
             for (row_index, (land_row, tex_row)) in land.rows().zip(texture.rows_mut()).enumerate()
             {
-                let sprite_row = land_sprite.get_row(row_index % land_sprite.height());
+                let sprite_row = land_sprite.get_row(row_index % land_sprite.height() as usize);
                 let mut x_offset = 0;
                 while sprite_row.len() < land.width() - x_offset {
                     let copy_range = x_offset..x_offset + sprite_row.len();
@@ -79,7 +79,7 @@
                         sprite_row,
                     );
 
-                    x_offset += land_sprite.width()
+                    x_offset += land_sprite.width() as usize
                 }
 
                 if x_offset < land.width() {
@@ -107,8 +107,10 @@
                 &mut offsets,
                 border_width,
                 |x, y| {
-                    border_sprite
-                        .get_pixel(x % border_sprite.width(), border_sprite.height() - 1 - y)
+                    border_sprite.get_pixel(
+                        x % border_sprite.width() as usize,
+                        border_sprite.height() as usize - 1 - y,
+                    )
                 },
             );
 
@@ -119,7 +121,7 @@
                 land.rows().zip(texture.rows_mut()),
                 &mut offsets,
                 border_width,
-                |x, y| border_sprite.get_pixel(x % border_sprite.width(), y),
+                |x, y| border_sprite.get_pixel(x % border_sprite.width() as usize, y),
             );
         }
 
@@ -169,7 +171,10 @@
             .map(|(size, indices)| {
                 (
                     TemplateType(size),
-                    indices.iter().map(|i| templates[*i].to_template(&desc.tiles, &desc.edges)).collect(),
+                    indices
+                        .iter()
+                        .map(|i| templates[*i].to_template(&desc.tiles, &desc.edges))
+                        .collect(),
                 )
             })
             .collect();
--- a/rust/mapgen/src/template/maze.rs	Tue Jan 28 10:37:46 2025 +0100
+++ b/rust/mapgen/src/template/maze.rs	Tue Jan 28 15:49:45 2025 +0100
@@ -4,10 +4,10 @@
 use std::collections::hash_map::HashMap;
 #[derive(Deserialize)]
 pub struct TemplateDesc {
-    width: usize,
-    height: usize,
+    width: u32,
+    height: u32,
     max_hedgehogs: u8,
-    cell_size: usize,
+    cell_size: u32,
     distortion_limiting_factor: u32,
     braidness: u32,
     invert: bool,
--- a/rust/mapgen/src/template/outline.rs	Tue Jan 28 10:37:46 2025 +0100
+++ b/rust/mapgen/src/template/outline.rs	Tue Jan 28 15:49:45 2025 +0100
@@ -21,8 +21,8 @@
 
 #[derive(Deserialize, Clone)]
 pub struct TemplateDesc {
-    width: usize,
-    height: usize,
+    width: u32,
+    height: u32,
     can_flip: bool,
     can_invert: bool,
     can_mirror: bool,
@@ -55,10 +55,7 @@
                 .map(|v| {
                     v.iter()
                         .map(|r| {
-                            Rect::from_size(
-                                Point::new(r.x as i32, r.y as i32),
-                                Size::new(r.w as usize, r.h as usize),
-                            )
+                            Rect::from_size(Point::new(r.x as i32, r.y as i32), Size::new(r.w, r.h))
                         })
                         .collect()
                 })
@@ -70,10 +67,7 @@
                 .map(|v| {
                     v.iter()
                         .map(|r| {
-                            Rect::from_size(
-                                Point::new(r.x as i32, r.y as i32),
-                                Size::new(r.w as usize, r.h as usize),
-                            )
+                            Rect::from_size(Point::new(r.x as i32, r.y as i32), Size::new(r.w, r.h))
                         })
                         .collect()
                 })
--- a/rust/mapgen/src/template/wavefront_collapse.rs	Tue Jan 28 10:37:46 2025 +0100
+++ b/rust/mapgen/src/template/wavefront_collapse.rs	Tue Jan 28 15:49:45 2025 +0100
@@ -33,8 +33,8 @@
 
 #[derive(Debug, Deserialize)]
 pub struct TemplateDesc {
-    pub width: usize,
-    pub height: usize,
+    pub width: u32,
+    pub height: u32,
     pub can_invert: Option<bool>,
     pub is_negative: Option<bool>,
     pub put_girders: Option<bool>,
@@ -53,7 +53,11 @@
 }
 
 impl TemplateDesc {
-    pub fn to_template(&self, tiles: &HashMap<String, Vec<TileDesc>>, edges: &HashMap<String, NonStrictComplexEdgesDesc>) -> TemplateDescription {
+    pub fn to_template(
+        &self,
+        tiles: &HashMap<String, Vec<TileDesc>>,
+        edges: &HashMap<String, NonStrictComplexEdgesDesc>,
+    ) -> TemplateDescription {
         let [top, right, bottom, left]: [Option<ComplexEdgeDescription>; 4] =
             if let Some(edges_name) = &self.edges {
                 let edges = edges.get(edges_name).expect("missing template edges");
@@ -63,7 +67,11 @@
                 [None, None, None, None]
             };
 
-        let tiles = self.tiles.iter().flat_map(|t| tiles.get(t).expect("missing template tiles")).collect::<Vec<_>>();
+        let tiles = self
+            .tiles
+            .iter()
+            .flat_map(|t| tiles.get(t).expect("missing template tiles"))
+            .collect::<Vec<_>>();
 
         TemplateDescription {
             size: Size::new(self.width, self.height),
--- a/rust/mapgen/src/theme.rs	Tue Jan 28 10:37:46 2025 +0100
+++ b/rust/mapgen/src/theme.rs	Tue Jan 28 15:49:45 2025 +0100
@@ -22,12 +22,12 @@
     }
 
     #[inline]
-    pub fn width(&self) -> usize {
+    pub fn width(&self) -> u32 {
         self.size().width
     }
 
     #[inline]
-    pub fn height(&self) -> usize {
+    pub fn height(&self) -> u32 {
         self.size().height
     }
 
@@ -61,7 +61,7 @@
         let size = self.size();
         assert!(size.is_power_of_two());
         let tile_width_shift = size.width.trailing_zeros() as usize + 2;
-        let mut pixels = vec![0u32; size.area()];
+        let mut pixels = vec![0u32; size.area() as usize];
 
         for (y, row) in self.pixels.rows().enumerate() {
             for (x, v) in row.iter().enumerate() {
@@ -95,12 +95,12 @@
     }
 
     #[inline]
-    pub fn width(&self) -> usize {
+    pub fn width(&self) -> u32 {
         self.size().width
     }
 
     #[inline]
-    pub fn height(&self) -> usize {
+    pub fn height(&self) -> u32 {
         self.size().height
     }
 
@@ -237,7 +237,7 @@
             info.color_type
         )));
     }
-    let size = Size::new(info.width as usize, info.height as usize);
+    let size = Size::new(info.width, info.height);
 
     let mut pixels: Vec2D<u32> = Vec2D::new(&size, 0);
     reader.next_frame(slice_u32_to_u8_mut(pixels.as_mut_slice()))?;
--- a/rust/vec2d/src/lib.rs	Tue Jan 28 10:37:46 2025 +0100
+++ b/rust/vec2d/src/lib.rs	Tue Jan 28 15:49:45 2025 +0100
@@ -4,7 +4,7 @@
     slice::SliceIndex,
 };
 
-#[derive(Debug)]
+#[derive(Debug, Clone)]
 pub struct Vec2D<T> {
     data: Vec<T>,
     size: Size,
@@ -30,19 +30,19 @@
 
         let pos = row * self.width();
 
-        &mut self.data[pos..pos + self.size.width]
+        &mut self.data[pos..pos + self.size.width as usize]
     }
 }
 
 impl<T> Vec2D<T> {
     #[inline]
     pub fn width(&self) -> usize {
-        self.size.width
+        self.size.width as usize
     }
 
     #[inline]
     pub fn height(&self) -> usize {
-        self.size.height
+        self.size.height as usize
     }
 
     #[inline]
@@ -55,7 +55,7 @@
     pub fn new(size: &Size, value: T) -> Self {
         Self {
             size: *size,
-            data: vec![value; size.area()],
+            data: vec![value; size.area() as usize],
         }
     }
 
@@ -85,7 +85,10 @@
         column: usize,
     ) -> Option<&mut <usize as SliceIndex<[T]>>::Output> {
         if row < self.height() && column < self.width() {
-            Some(unsafe { self.data.get_unchecked_mut(row * self.size.width + column) })
+            Some(unsafe {
+                self.data
+                    .get_unchecked_mut(row * self.size.width as usize + column)
+            })
         } else {
             None
         }
@@ -106,7 +109,8 @@
         row: usize,
         column: usize,
     ) -> &mut <usize as SliceIndex<[T]>>::Output {
-        self.data.get_unchecked_mut(row * self.size.width + column)
+        self.data
+            .get_unchecked_mut(row * self.size.width as usize + column)
     }
 
     #[inline]
@@ -146,7 +150,7 @@
 impl<T: Clone> Vec2D<T> {
     pub fn from_iter<I: IntoIterator<Item = T>>(iter: I, size: &Size) -> Option<Vec2D<T>> {
         let data: Vec<T> = iter.into_iter().collect();
-        if size.width * size.height == data.len() {
+        if size.width as usize * size.height as usize == data.len() {
             Some(Vec2D { data, size: *size })
         } else {
             None
@@ -160,7 +164,7 @@
 
     #[test]
     fn basics() {
-        let mut v: Vec2D<u8> = Vec2D::new(Size::new(2, 3), 0xff);
+        let mut v: Vec2D<u8> = Vec2D::new(&Size::new(2, 3), 0xff);
 
         assert_eq!(v.width(), 2);
         assert_eq!(v.height(), 3);
@@ -173,7 +177,7 @@
         assert_eq!(v[2][0], 0xff);
         assert_eq!(v[2][1], 0);
 
-        v.get_mut(2, 1).map(|v| *v = 1);
+        v.get_mut(2, 1).into_iter().for_each(|v| *v = 1);
         assert_eq!(v[2][1], 1);
 
         assert_eq!(v.get_mut(2, 2), None);