From 9e1f51b3e133ea72c5a7330c9db9b1fb55c8aa34 Mon Sep 17 00:00:00 2001 From: Bal7hazar Date: Tue, 10 Sep 2024 12:04:13 -0400 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20Add=20maze=20generation=20feature?= =?UTF-8?q?=20(#90)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/map/src/helpers/asserter.cairo | 82 +++++++ crates/map/src/helpers/bitmap.cairo | 200 ++++++++++++++++ crates/map/src/helpers/caver.cairo | 332 ++++++++++++++++++++++++++ crates/map/src/helpers/digger.cairo | 242 +++++++++++++++++++ crates/map/src/helpers/mazer.cairo | 176 ++++++++++++++ crates/map/src/helpers/power.cairo | 299 +++++++++++++++++++++++ crates/map/src/helpers/seeder.cairo | 37 +++ crates/map/src/helpers/spreader.cairo | 128 ++++++++++ crates/map/src/helpers/walker.cairo | 140 +++++++++++ crates/map/src/{hex => }/hex.cairo | 92 +++---- crates/map/src/hex/types.cairo | 28 --- crates/map/src/lib.cairo | 21 +- crates/map/src/room.cairo | 288 ++++++++++++++++++++++ crates/map/src/types/direction.cairo | 111 +++++++++ 14 files changed, 2103 insertions(+), 73 deletions(-) create mode 100644 crates/map/src/helpers/asserter.cairo create mode 100644 crates/map/src/helpers/bitmap.cairo create mode 100644 crates/map/src/helpers/caver.cairo create mode 100644 crates/map/src/helpers/digger.cairo create mode 100644 crates/map/src/helpers/mazer.cairo create mode 100644 crates/map/src/helpers/power.cairo create mode 100644 crates/map/src/helpers/seeder.cairo create mode 100644 crates/map/src/helpers/spreader.cairo create mode 100644 crates/map/src/helpers/walker.cairo rename crates/map/src/{hex => }/hex.cairo (64%) delete mode 100644 crates/map/src/hex/types.cairo create mode 100644 crates/map/src/room.cairo create mode 100644 crates/map/src/types/direction.cairo diff --git a/crates/map/src/helpers/asserter.cairo b/crates/map/src/helpers/asserter.cairo new file mode 100644 index 00000000..20ceacc6 --- /dev/null +++ b/crates/map/src/helpers/asserter.cairo @@ -0,0 +1,82 @@ +//! Map assert helper functions. + +// Constants +pub const MAX_SIZE: u8 = 252; + +/// Errors module. +pub mod errors { + pub const ASSERTER_INVALID_DIMENSION: felt252 = 'Asserter: invalid dimension'; + pub const ASSERTER_POSITION_IS_CORNER: felt252 = 'Asserter: position is a corner'; + pub const ASSERTER_POSITION_NOT_EDGE: felt252 = 'Asserter: position not an edge'; +} + +#[generate_trait] +pub impl Asserter of AssertTrait { + /// Check if the position is on the edge of the map. + /// # Arguments + /// * `width` - The width of the map + /// * `height` - The height of the map + /// * `x` - The x coordinate of the position + /// * `y` - The y coordinate of the position + /// # Returns + /// * `true` if the position is on the edge of the map, `false` otherwise + #[inline] + fn is_edge(width: u8, height: u8, x: u8, y: u8) -> bool { + x == 0 || y == 0 || x == width - 1 || y == height - 1 + } + + /// Check if the position is a corner of the map. + /// # Arguments + /// * `width` - The width of the map + /// * `height` - The height of the map + /// * `x` - The x coordinate of the position + /// * `y` - The y coordinate of the position + /// # Returns + /// * `true` if the position is a corner of the map, `false` otherwise + #[inline] + fn is_corner(width: u8, height: u8, x: u8, y: u8) -> bool { + (x == 0 && y == 0) + || (x == width - 1 && y == 0) + || (x == 0 && y == height - 1) + || (x == width - 1 && y == height - 1) + } + + /// Assert that the dimensions are valid. + /// # Arguments + /// * `width` - The width of the map + /// * `height` - The height of the map + /// # Panics + /// * If the dimensions are invalid + #[inline] + fn assert_valid_dimension(width: u8, height: u8) { + assert(width > 2, errors::ASSERTER_INVALID_DIMENSION); + assert(height > 2, errors::ASSERTER_INVALID_DIMENSION); + assert(width * height <= MAX_SIZE, errors::ASSERTER_INVALID_DIMENSION); + } + + /// Assert that the position is on the edge of the map. + /// # Arguments + /// * `width` - The width of the map + /// * `height` - The height of the map + /// * `position` - The position to check + /// # Panics + /// * If the position is not on the edge of the map + #[inline] + fn assert_on_edge(width: u8, height: u8, position: u8) { + let (x, y) = (position % width, position / width); + assert(Self::is_edge(width, height, x, y), errors::ASSERTER_POSITION_NOT_EDGE); + } + + /// Assert that the position is not a corner of the map. + /// # Arguments + /// * `width` - The width of the map + /// * `height` - The height of the map + /// * `position` - The position to check + /// # Panics + /// * If the position is a corner of the map + #[inline] + fn assert_not_corner(width: u8, height: u8, position: u8) { + let (x, y) = (position % width, position / width); + assert(!Self::is_corner(width, height, x, y), errors::ASSERTER_POSITION_IS_CORNER); + } +} diff --git a/crates/map/src/helpers/bitmap.cairo b/crates/map/src/helpers/bitmap.cairo new file mode 100644 index 00000000..85d6ae7d --- /dev/null +++ b/crates/map/src/helpers/bitmap.cairo @@ -0,0 +1,200 @@ +// Internal imports + +use origami_map::helpers::power::{TwoPower, TwoPowerTrait}; + +#[generate_trait] +pub impl Bitmap of BitmapTrait { + /// Count the number of bits set to 1 in the number + /// # Arguments + /// * `x` - The value for which to count the number of bits set to 1 + /// # Returns + /// * The number of bits set to 1 + #[inline] + fn popcount(x: felt252) -> u8 { + let mut x: u256 = x.into(); + let mut count: u8 = 0; + while (x > 0) { + count += PrivateTrait::_popcount((x % 0x100000000).try_into().unwrap()); + x /= 0x100000000; + }; + count + } + + /// Get the bit at the specified index + /// # Arguments + /// * `x` - The bitmap + /// * `index` - The index of the bit to get + /// # Returns + /// * The value of the bit at the specified index + #[inline] + fn get(x: felt252, index: u8) -> u8 { + let x: u256 = x.into(); + let offset: u256 = TwoPower::pow(index); + (x / offset % 2).try_into().unwrap() + } + + /// Set the bit at the specified index + /// # Arguments + /// * `x` - The bitmap + /// * `index` - The index of the bit to set + /// # Returns + /// * The bitmap with the bit at the specified index set to 1 + #[inline] + fn set(x: felt252, index: u8) -> felt252 { + let x: u256 = x.into(); + let offset: u256 = TwoPower::pow(index); + let bit = x / offset % 2; + let offset: u256 = offset * (1 - bit); + (x + offset).try_into().unwrap() + } + + /// Unset the bit at the specified index + /// # Arguments + /// * `x` - The bitmap + /// * `index` - The index of the bit to unset + /// # Returns + /// * The bitmap with the bit at the specified index set to 0 + #[inline] + fn unset(x: felt252, index: u8) -> felt252 { + let x: u256 = x.into(); + let offset: u256 = TwoPower::pow(index); + let bit = x / offset % 2; + let offset: u256 = offset * bit; + (x - offset).try_into().unwrap() + } + + /// The index of the least significant bit of the number, + /// where the least significant bit is at index 0 and the most significant bit is at index 255 + /// # Arguments + /// * `x` - The value for which to compute the least significant bit, must be greater than 0. + /// # Returns + /// * The index of the least significant bit, if 0 returns the index 0 + #[inline] + fn least_significant_bit(x: felt252) -> u8 { + let mut x: u256 = x.into(); + if x == 0 { + return 0; + } + let mut r: u8 = 255; + + if (x & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF) > 0 { + r -= 128; + } else { + x /= 0x100000000000000000000000000000000; + } + if (x & 0xFFFFFFFFFFFFFFFF) > 0 { + r -= 64; + } else { + x /= 0x10000000000000000; + } + if (x & 0xFFFFFFFF) > 0 { + r -= 32; + } else { + x /= 0x100000000; + } + if (x & 0xFFFF) > 0 { + r -= 16; + } else { + x /= 0x10000; + } + if (x & 0xFF) > 0 { + r -= 8; + } else { + x /= 0x100; + } + if (x & 0xF) > 0 { + r -= 4; + } else { + x /= 0x10; + } + if (x & 0x3) > 0 { + r -= 2; + } else { + x /= 0x4; + } + if (x & 0x1) > 0 { + r -= 1; + } + r + } +} + +#[generate_trait] +impl Private of PrivateTrait { + /// Count the number of bits set to 1 in the number for a u32 + /// # Arguments + /// * `x` - The value for which to count the number of bits set to 1 + /// # Returns + /// * The number of bits set to 1 + #[inline] + fn _popcount(mut x: u32) -> u8 { + x -= ((x / 2) & 0x55555555); + x = (x & 0x33333333) + ((x / 4) & 0x33333333); + x = (x + (x / 16)) & 0x0f0f0f0f; + x += (x / 256); + x += (x / 65536); + return (x % 64).try_into().unwrap(); + } +} + +#[cfg(test)] +mod tests { + // Local imports + + use super::Bitmap; + + #[test] + fn test_bitmap_popcount_large() { + let count = Bitmap::popcount(0x4003FBB391C53CCB8E99752EB665586B695BB2D026BEC9071FF30002); + assert_eq!(count, 109); + } + + #[test] + fn test_bitmap_popcount_small() { + let count = Bitmap::popcount(0b101); + assert_eq!(count, 2); + } + + #[test] + fn test_bitmap_get() { + let bit = Bitmap::get(0b1001011, 0); + assert_eq!(bit, 1); + } + + #[test] + fn test_bitmap_set() { + let bit = Bitmap::set(0b1001010, 0); + assert_eq!(bit, 0b1001011); + } + + #[test] + fn test_bitmap_set_unchanged() { + let bit = Bitmap::set(0b1001011, 0); + assert_eq!(bit, 0b1001011); + } + + #[test] + fn test_bitmap_unset() { + let bit = Bitmap::unset(0b1001011, 0); + assert_eq!(bit, 0b1001010); + } + + #[test] + fn test_bitmap_unset_unchanged() { + let bit = Bitmap::unset(0b1001010, 0); + assert_eq!(bit, 0b1001010); + } + + #[test] + fn test_bitmap_least_significant_bit_null() { + let msb = Bitmap::least_significant_bit(0); + assert!(msb == 0, "Bitmap: least significant bit"); + } + + #[test] + fn test_bitmap_least_significant_bit() { + let bitmap: felt252 = 1234; // 10011010010 + let msb = Bitmap::least_significant_bit(bitmap); + assert!(msb == 1, "Bitmap: least significant bit"); + } +} diff --git a/crates/map/src/helpers/caver.cairo b/crates/map/src/helpers/caver.cairo new file mode 100644 index 00000000..ea2818b4 --- /dev/null +++ b/crates/map/src/helpers/caver.cairo @@ -0,0 +1,332 @@ +//! Brogue's algorithm customized to generate Cave. +//! See also https://www.rockpapershotgun.com/how-do-roguelikes-generate-levels + +// Internal imports + +use origami_map::helpers::power::TwoPower; +use origami_map::helpers::bitmap::Bitmap; +use origami_map::helpers::seeder::Seeder; +use origami_map::helpers::asserter::Asserter; + +// Constants + +const WALL_TO_FLOOR_THRESHOLD: u8 = 4; +const FLOOR_TO_WALL_THRESHOLD: u8 = 3; + +/// Implementation of the `CaverTrait` trait. +#[generate_trait] +pub impl Caver of CaverTrait { + /// Generate a cave. + /// # Arguments + /// * `width` - The width of the cave + /// * `height` - The height of the cave + /// * `order` - The order of the cave, which is the number of smoothing iterations + /// * `seed` - The seed to generate the cave + /// # Returns + /// * The generated cave + #[inline] + fn generate(width: u8, height: u8, order: u8, seed: felt252) -> felt252 { + // [Check] Valid dimensions + Asserter::assert_valid_dimension(width, height); + // [Effect] Remove leading bits + let size = width * height; + let default: u256 = seed.into() / TwoPower::pow(252 - size); + let mut grid: felt252 = default.try_into().unwrap(); + Self::iter(width, height, size, order.into(), ref grid); + // [Return] Cave + grid + } + + /// Recursive function to generate the cave. + /// # Arguments + /// * `width` - The width of the cave + /// * `height` - The height of the cave + /// * `size` - The size of the cave + /// * `order` - The order of the cave + /// * `grid` - The grid of the cave to update + #[inline] + fn iter(width: u8, height: u8, size: u8, order: u16, ref grid: felt252) { + // [Check] Stop if the loop count is zero + let mut index: u16 = size.into() * order - 1; + while index != 0 { + Self::assess(width, height, (index % size.into()).try_into().unwrap(), size, ref grid); + index -= 1; + }; + } + + /// Assess the grid at the specified index. + /// # Arguments + /// * `width` - The width of the cave + /// * `height` - The height of the cave + /// * `index` - The index of the grid to assess + /// * `size` - The size of the cave + /// * `grid` - The grid of the cave to update + #[inline] + fn assess(width: u8, height: u8, index: u8, size: u8, ref grid: felt252) { + let is_wall = Bitmap::get(grid, index) == 0; + let (x, y) = (index % width, index / width); + let is_edge = Asserter::is_edge(width, height, x, y) && index < size; + let floor_count = Self::count_direct_floor(width, height, x, y, grid) + + Self::count_indirect_floor(width, height, x, y, grid); + if is_wall && floor_count > WALL_TO_FLOOR_THRESHOLD && !is_edge { + // [Effect] Convert wall into floor if surrounded by more than X floors + grid = Bitmap::set(grid, index); + } else if !is_wall && (floor_count < FLOOR_TO_WALL_THRESHOLD || is_edge) { + // [Effect] Convert floor into wall if surrounded by less than Y floors + grid = Bitmap::unset(grid, index); + } + } + + /// Count the number of direct floor neighbors (adjacent). + /// # Arguments + /// * `width` - The width of the cave + /// * `height` - The height of the cave + /// * `x` - The x-coordinate of the grid + /// * `y` - The y-coordinate of the grid + /// * `grid` - The grid of the cave + /// # Returns + /// * The number of direct floor neighbors + #[inline] + fn count_direct_floor(width: u8, height: u8, x: u8, y: u8, grid: felt252) -> u8 { + // [Compute] Neighbors + let mut floor_count: u8 = 0; + // [Compute] North + if y < height - 1 { + let index = (y + 1) * width + x; + floor_count += Bitmap::get(grid, index); + }; + // [Compute] East + if x < width - 1 { + let index = y * width + x + 1; + floor_count += Bitmap::get(grid, index); + }; + // [Compute] South + if y > 0 { + let index = (y - 1) * width + x; + floor_count += Bitmap::get(grid, index); + }; + // [Compute] West + if x > 0 { + let index = y * width + x - 1; + floor_count += Bitmap::get(grid, index); + }; + floor_count + } + + /// Count the number of indirect floor neighbors (diagnoal). + /// # Arguments + /// * `width` - The width of the cave + /// * `height` - The height of the cave + /// * `x` - The x-coordinate of the grid + /// * `y` - The y-coordinate of the grid + /// * `grid` - The grid of the cave + /// # Returns + /// * The number of indirect floor neighbors + #[inline] + fn count_indirect_floor(width: u8, height: u8, x: u8, y: u8, grid: felt252) -> u8 { + // [Compute] Neighbors + let mut floor_count: u8 = 0; + // [Compute] North West + if y < height - 1 && x > 0 { + let index = (y + 1) * width + x - 1; + floor_count += Bitmap::get(grid, index); + }; + // [Compute] North East + if y < height - 1 && x < width - 1 { + let index = (y + 1) * width + x + 1; + floor_count += Bitmap::get(grid, index); + }; + // [Compute] South East + if y > 0 && x < width - 1 { + let index = (y - 1) * width + x + 1; + floor_count += Bitmap::get(grid, index); + }; + // [Compute] South West + if y > 0 && x > 0 { + let index = (y - 1) * width + x - 1; + floor_count += Bitmap::get(grid, index); + }; + floor_count + } +} + +#[cfg(test)] +mod tests { + // Local imports + + use super::{Caver, Seeder}; + + // Constants + + const SEED: felt252 = 'SEED'; + + #[test] + fn test_caver_generate() { + // Order 0 + // 011011110001110110 + // 100011100010001110 + // 110000111001111100 + // 101111110101111110 + // 000010010001111001 + // 100001111011001110 + // 100111001101100110 + // 100010010110001000 + // 011111001110110110 + // 100001000111110000 + // 111010111100010100 + // 110001110000110111 + // 001000010000000100 + // 000110010110101010 + // Order 1 + // 000000000000000000 + // 000000000000001100 + // 000000111001111100 + // 000001111001111110 + // 000000111001111110 + // 000001111111111110 + // 000011111111111110 + // 000111111111111110 + // 000111111111111110 + // 000011111111111100 + // 010011111100111110 + // 000001111000111110 + // 000000111000011100 + // 000000000000000000 + // Order 2 + // 000000000000000000 + // 000000000000001100 + // 000000111001111100 + // 000001111001111110 + // 000001111111111110 + // 000001111111111110 + // 000011111111111110 + // 000111111111111110 + // 000111111111111110 + // 000011111111111110 + // 000011111101111110 + // 000001111000111110 + // 000000111000011100 + // 000000000000000000 + // Order 3 + // 000000000000000000 + // 000000000000001100 + // 000000111001111100 + // 000001111111111110 + // 000001111111111110 + // 000001111111111110 + // 000011111111111110 + // 000111111111111110 + // 000111111111111110 + // 000011111111111110 + // 000011111111111110 + // 000001111100111110 + // 000000111000011100 + // 000000000000000000 + let width = 18; + let height = 14; + let order = 2; + let seed: felt252 = Seeder::shuffle(SEED, SEED); + let cave = Caver::generate(width, height, order, seed); + assert_eq!(cave, 0xC039F01E7E07FF81FFE0FFF87FFE1FFF83FFE0FDF81E3E038700000); + } + + #[test] + fn test_caver_new_seed_generate() { + // Order 0 + // 011011111001101111 + // 010111100100000110 + // 100111101011010100 + // 001000100010010101 + // 001110001011110010 + // 000000011110011011 + // 111010010011001000 + // 001100000000100001 + // 101111111101010011 + // 011111010000010101 + // 001100101001101001 + // 000101011110101000 + // 101101001100101010 + // 100000100110110110 + // Order 1 + // 000000000000000000 + // 000111100000000000 + // 000111100000000000 + // 001111100000000000 + // 001110001111110000 + // 000100011111111000 + // 001110000011111000 + // 001111000001110000 + // 001111111000110010 + // 011111110000110000 + // 001111111001111000 + // 001111111111111000 + // 001111111111111110 + // 000000000000000000 + // Order 2 + // 000000000000000000 + // 000111100000000000 + // 000111100000000000 + // 001111100000000000 + // 001111001111110000 + // 001110001111111000 + // 001110000011111000 + // 001111000001110000 + // 001111110000110000 + // 011111110000110000 + // 001111111001111000 + // 001111111111111100 + // 001111111111111100 + // 000000000000000000 + // Order 3 + // 000000000000000000 + // 000111100000000000 + // 000111100000000000 + // 001111100000000000 + // 001111001111110000 + // 001110001111111000 + // 001110000011111000 + // 001111000001110000 + // 001111110000110000 + // 011111110000110000 + // 001111111001111000 + // 001111111111111100 + // 001111111111111100 + // 000000000000000000 + // Order 10 + // 000000000000000000 + // 001111111100000000 + // 001111111110000000 + // 001111111111110000 + // 001111111111110000 + // 001111111111111000 + // 001111111111111000 + // 001111111001110000 + // 001111110000110000 + // 011111110000110000 + // 001111111001111000 + // 001111111111111100 + // 001111111111111100 + // 000000000000000000 + // Order 20 + // 000000000000000000 + // 001111111100001110 + // 001111111110011110 + // 001111111111111110 + // 001111111111111110 + // 001111111111111110 + // 001111111111111000 + // 001111111001110000 + // 001111110000110000 + // 011111110000110000 + // 001111111001111000 + // 001111111111111100 + // 001111111111111100 + // 000000000000000000 + let width = 18; + let height = 14; + let order = 2; + let seed: felt252 = Seeder::shuffle(SEED + SEED, SEED + SEED); + let cave = Caver::generate(width, height, order, seed); + assert_eq!(cave, 0x78001E000F80038FC0E3F8383E0F0703F0C1FC303F9E0FFFC3FFF00000); + } +} diff --git a/crates/map/src/helpers/digger.cairo b/crates/map/src/helpers/digger.cairo new file mode 100644 index 00000000..4027b307 --- /dev/null +++ b/crates/map/src/helpers/digger.cairo @@ -0,0 +1,242 @@ +//! Digger to generate entries. + +// Internal imports + +use origami_map::helpers::bitmap::Bitmap; +use origami_map::helpers::caver::Caver; +use origami_map::helpers::seeder::Seeder; +use origami_map::helpers::mazer::Mazer; +use origami_map::helpers::asserter::Asserter; +use origami_map::types::direction::{Direction, DirectionTrait}; + +/// Implementation of the `DiggerTrait` trait. +#[generate_trait] +pub impl Digger of DiggerTrait { + /// Dig a maze from the edge to the grid with a single contact point. + /// # Arguments + /// * `width` - The width of the grid + /// * `height` - The height of the grid + /// * `start` - The starting position + /// * `grid` - The original grid + /// * `seed` - The seed to generate the maze + /// # Returns + /// * The grid with the maze to the exit + #[inline] + fn maze(width: u8, height: u8, start: u8, mut grid: felt252, mut seed: felt252) -> felt252 { + // [Check] Position is not a corner and is on an edge + Asserter::assert_not_corner(width, height, start); + Asserter::assert_on_edge(width, height, start); + // [Effect] Dig the edge and compute the next position + let (x, y) = (start % width, start / width); + let next: u8 = if x == 0 { + Mazer::next(width, start, Direction::East) + } else if x == width - 1 { + Mazer::next(width, start, Direction::West) + } else if y == 0 { + Mazer::next(width, start, Direction::North) + } else { + Mazer::next(width, start, Direction::South) + }; + // [Compute] Generate the maze from the exit to the grid + let mut maze = Bitmap::set(0, start); + Mazer::iter(width, height, next, ref grid, ref maze, ref seed); + // [Return] The original grid with the maze to the exit + let grid: u256 = grid.into() | maze.into(); + grid.try_into().unwrap() + } + + /// Dig a corridor from the edge to the grid with a single contact point. + /// # Arguments + /// * `width` - The width of the grid + /// * `height` - The height of the grid + /// * `start` - The starting position + /// * `grid` - The original grid + /// * `seed` - The seed to generate the maze + /// # Returns + /// * The grid with the maze to the exit + #[inline] + fn corridor(width: u8, height: u8, start: u8, grid: felt252, mut seed: felt252) -> felt252 { + // [Check] Position is not a corner and is on an edge + Asserter::assert_not_corner(width, height, start); + Asserter::assert_on_edge(width, height, start); + // [Effect] Dig the edge and compute the next position + let (x, y) = (start % width, start / width); + let next: u8 = if x == 0 { + Mazer::next(width, start, Direction::East) + } else if x == width - 1 { + Mazer::next(width, start, Direction::West) + } else if y == 0 { + Mazer::next(width, start, Direction::North) + } else { + Mazer::next(width, start, Direction::South) + }; + // [Compute] Generate the maze from the exit to the grid + let mut maze = Bitmap::set(0, start); + let mut stop = false; + Self::iter(width, height, next, grid, ref stop, ref maze, ref seed); + // [Return] The original grid with the maze to the exit + let grid: u256 = grid.into() | maze.into(); + grid.try_into().unwrap() + } + + /// Recursive function to generate the corridor on an existing grid. + /// # Arguments + /// * `width` - The width of the corridor + /// * `height` - The height of the corridor + /// * `start` - The starting position + /// * `grid` - The original grid + /// * `stop` - The stop criteria + /// * `maze` - The generated corridor + /// * `seed` - The seed to generate the corridor + #[inline] + fn iter( + width: u8, + height: u8, + start: u8, + grid: felt252, + ref stop: bool, + ref maze: felt252, + ref seed: felt252 + ) { + // [Check] Stop criteria, the position collides with the grid + if Bitmap::get(grid, start) == 1 { + stop = true; + return; + } + // [Effect] Set the position + maze = Bitmap::set(maze, start); + // [Compute] Generate shuffled neighbors + seed = Seeder::shuffle(seed, seed); + let mut directions = DirectionTrait::compute_shuffled_directions(seed); + // [Assess] Direction 1 + let direction: Direction = DirectionTrait::pop_front(ref directions); + if Self::check(maze, width, height, start, direction, stop) { + let next = Mazer::next(width, start, direction); + Self::iter(width, height, next, grid, ref stop, ref maze, ref seed); + } + // [Assess] Direction 2 + let direction: Direction = DirectionTrait::pop_front(ref directions); + if Self::check(maze, width, height, start, direction, stop) { + let next = Mazer::next(width, start, direction); + Self::iter(width, height, next, grid, ref stop, ref maze, ref seed); + } + // [Assess] Direction 3 + let direction: Direction = DirectionTrait::pop_front(ref directions); + if Self::check(maze, width, height, start, direction, stop) { + let next = Mazer::next(width, start, direction); + Self::iter(width, height, next, grid, ref stop, ref maze, ref seed); + } + // [Assess] Direction 4 + let direction: Direction = DirectionTrait::pop_front(ref directions); + if Self::check(maze, width, height, start, direction, stop) { + let next = Mazer::next(width, start, direction); + Self::iter(width, height, next, grid, ref stop, ref maze, ref seed); + }; + } + + /// Check if the position can be visited in the specified direction. + /// # Arguments + /// * `maze` - The maze + /// * `width` - The width of the maze + /// * `height` - The height of the maze + /// * `position` - The current position + /// * `direction` - The direction to check + /// * `stop` - The stop criteria + /// # Returns + /// * Whether the position can be visited in the specified direction + #[inline] + fn check( + grid: felt252, width: u8, height: u8, position: u8, direction: Direction, stop: bool + ) -> bool { + !stop && Mazer::check(grid, width, height, position, direction) + } +} + +#[cfg(test)] +mod tests { + // Local imports + + use super::Digger; + + // Constants + + const SEED: felt252 = 'SEED'; + + #[test] + fn test_digger_dig_corridor() { + // Input grid + // 000000000000000000 + // 000111111100000000 <-- Start + // 001111111000000000 + // 001111101100000000 + // 000111111100000000 + // 000011110001001000 + // 000111110001111100 + // 001111111111111110 + // 000111101110111110 + // 000011111111111110 + // 000111111111111110 + // 000001111111111110 + // 000000111111111110 + // 000000000000000000 + // Output grid + // 000000000000000000 + // 000111111100000111 + // 001111111000000100 + // 001111101100000110 + // 000111111100000010 + // 000011110001001010 + // 000111110001111110 + // 001111111111111110 + // 000111101110111110 + // 000011111111111110 + // 000111111111111110 + // 000001111111111110 + // 000000111111111110 + // 000000000000000000 + let width = 18; + let height = 14; + let mut grid = 0x7F003F800FB001FC003C481F1F0FFFE1EEF83FFE1FFF81FFE03FF80000; + let walk: felt252 = Digger::corridor(width, height, 216, grid, SEED); + assert_eq!(walk, 0x7F073F810FB061FC083C4A1F1F8FFFE1EEF83FFE1FFF81FFE03FF80000); + } + + #[test] + fn test_digger_dig_maze() { + // Input grid + // 000000000000000000 + // 000111111100000000 <-- Start + // 001111111000000000 + // 001111101100000000 + // 000111111100000000 + // 000011110001001000 + // 000111110001111100 + // 001111111111111110 + // 000111101110111110 + // 000011111111111110 + // 000111111111111110 + // 000001111111111110 + // 000000111111111110 + // 000000000000000000 + // Output grid + // 000000000000000000 + // 000111111101110111 + // 001111111011000100 + // 001111101101111110 + // 000111111100100010 + // 000011110001001010 + // 000111110001111110 + // 001111111111111110 + // 000111101110111110 + // 000011111111111110 + // 000111111111111110 + // 000001111111111110 + // 000000111111111110 + // 000000000000000000 + let width = 18; + let height = 14; + let mut grid = 0x7F003F800FB001FC003C481F1F0FFFE1EEF83FFE1FFF81FFE03FF80000; + let walk: felt252 = Digger::maze(width, height, 216, grid, SEED); + assert_eq!(walk, 0x7F773FB10FB7E1FC883C4A1F1F8FFFE1EEF83FFE1FFF81FFE03FF80000); + } +} diff --git a/crates/map/src/helpers/mazer.cairo b/crates/map/src/helpers/mazer.cairo new file mode 100644 index 00000000..b73f191c --- /dev/null +++ b/crates/map/src/helpers/mazer.cairo @@ -0,0 +1,176 @@ +//! Prim's algorithm to generate Maze. +//! See also https://en.wikipedia.org/wiki/Prim%27s_algorithm + +// Internal imports + +use origami_map::helpers::bitmap::Bitmap; +use origami_map::helpers::seeder::Seeder; +use origami_map::helpers::asserter::Asserter; +use origami_map::types::direction::{Direction, DirectionTrait}; + +/// Implementation of the `MazerTrait` trait. +#[generate_trait] +pub impl Mazer of MazerTrait { + /// Generate a maze. + /// # Arguments + /// * `width` - The width of the maze + /// * `height` - The height of the maze + /// * `seed` - The seed to generate the maze + /// # Returns + /// * The generated maze + #[inline] + fn generate(width: u8, height: u8, mut seed: felt252) -> felt252 { + // [Check] Valid dimensions + Asserter::assert_valid_dimension(width, height); + // [Compute] Generate the maze + let start = Seeder::random_position(width, height, seed); + let mut grid = 0; + let mut maze = 0; + Self::iter(width, height, start, ref grid, ref maze, ref seed); + // [Return] The maze + maze + } + + /// Recursive function to generate the maze on an existing grid allowing a single contact point. + /// # Arguments + /// * `width` - The width of the maze + /// * `height` - The height of the maze + /// * `start` - The starting position + /// * `grid` - The original grid + /// * `maze` - The generated maze + /// * `seed` - The seed to generate the maze + #[inline] + fn iter( + width: u8, height: u8, start: u8, ref grid: felt252, ref maze: felt252, ref seed: felt252 + ) { + // [Check] Stop criteria, the position collides with the original grid + if Bitmap::get(grid, start) == 1 { + // [Effect] Merge the maze with the grid + let merge: u256 = grid.into() | maze.into(); + maze = merge.try_into().unwrap(); + return; + } + // [Effect] Set the position + maze = Bitmap::set(maze, start); + // [Compute] Generate shuffled neighbors + seed = Seeder::shuffle(seed, seed); + let mut directions = DirectionTrait::compute_shuffled_directions(seed); + // [Assess] Direction 1 + let direction: Direction = DirectionTrait::pop_front(ref directions); + if Self::check(maze, width, height, start, direction) { + let next = Self::next(width, start, direction); + Self::iter(width, height, next, ref grid, ref maze, ref seed); + } + // [Assess] Direction 2 + let direction: Direction = DirectionTrait::pop_front(ref directions); + if Self::check(maze, width, height, start, direction) { + let next = Self::next(width, start, direction); + Self::iter(width, height, next, ref grid, ref maze, ref seed); + } + // [Assess] Direction 3 + let direction: Direction = DirectionTrait::pop_front(ref directions); + if Self::check(maze, width, height, start, direction) { + let next = Self::next(width, start, direction); + Self::iter(width, height, next, ref grid, ref maze, ref seed); + } + // [Assess] Direction 4 + let direction: Direction = DirectionTrait::pop_front(ref directions); + if Self::check(maze, width, height, start, direction) { + let next = Self::next(width, start, direction); + Self::iter(width, height, next, ref grid, ref maze, ref seed); + }; + } + + /// Check if the position can be visited in the specified direction. + /// # Arguments + /// * `maze` - The maze + /// * `width` - The width of the maze + /// * `height` - The height of the maze + /// * `position` - The current position + /// * `direction` - The direction to check + /// # Returns + /// * Whether the position can be visited in the specified direction + #[inline] + fn check(maze: felt252, width: u8, height: u8, position: u8, direction: Direction) -> bool { + let (x, y) = (position % width, position / width); + match direction { + Direction::North => (y <= height - 3) + && (x != 0) + && (x != width - 1) + && (Bitmap::get(maze, position + 2 * width) == 0) + && (Bitmap::get(maze, position + width + 1) == 0) + && (Bitmap::get(maze, position + width - 1) == 0), + Direction::East => (x <= width - 3) + && (y != 0) + && (y != height - 1) + && (Bitmap::get(maze, position + 2) == 0) + && (Bitmap::get(maze, position + width + 1) == 0) + && (Bitmap::get(maze, position - width + 1) == 0), + Direction::South => (y >= 2) + && (x != 0) + && (x != width - 1) + && (Bitmap::get(maze, position - 2 * width) == 0) + && (Bitmap::get(maze, position - width + 1) == 0) + && (Bitmap::get(maze, position - width - 1) == 0), + Direction::West => (x >= 2) + && (y != 0) + && (y != height - 1) + && (Bitmap::get(maze, position - 2) == 0) + && (Bitmap::get(maze, position + width - 1) == 0) + && (Bitmap::get(maze, position - width - 1) == 0), + _ => false, + } + } + + /// Get the next position in the specified direction. + /// # Arguments + /// * `width` - The width of the maze + /// * `position` - The current position + /// * `direction` - The direction to move + /// # Returns + /// * The next position in the specified direction + #[inline] + fn next(width: u8, position: u8, direction: Direction) -> u8 { + let new_position = match direction { + Direction::North => { position + width }, + Direction::East => { position + 1 }, + Direction::South => { position - width }, + Direction::West => { position - 1 }, + _ => { position }, + }; + new_position + } +} + +#[cfg(test)] +mod tests { + // Local imports + + use super::Mazer; + + // Constants + + const SEED: felt252 = 'SEED'; + + #[test] + fn test_mazer_generate() { + // 000000000000000000 + // 010111011111111010 + // 011010110010001110 + // 001111011011110010 + // 011001001101010100 + // 010001111010011110 + // 011010000011101010 + // 001110111101010110 + // 010101100111011100 + // 010111011101101010 + // 011000101011011110 + // 001111110110010100 + // 011010011101110110 + // 000000000000000000 + let width = 18; + let height = 14; + let maze: felt252 = Mazer::generate(width, height, SEED); + assert_eq!(maze, 0x177FA6B238F6F264D511E9E683A8EF5656771776A62B78FD9469DD80000); + } +} diff --git a/crates/map/src/helpers/power.cairo b/crates/map/src/helpers/power.cairo new file mode 100644 index 00000000..e03f788a --- /dev/null +++ b/crates/map/src/helpers/power.cairo @@ -0,0 +1,299 @@ +// Powers of two + +const TWO_POWER: [ + u256 + ; 256] = [ + 0x1, + 0x2, + 0x4, + 0x8, + 0x10, + 0x20, + 0x40, + 0x80, + 0x100, + 0x200, + 0x400, + 0x800, + 0x1000, + 0x2000, + 0x4000, + 0x8000, + 0x10000, + 0x20000, + 0x40000, + 0x80000, + 0x100000, + 0x200000, + 0x400000, + 0x800000, + 0x1000000, + 0x2000000, + 0x4000000, + 0x8000000, + 0x10000000, + 0x20000000, + 0x40000000, + 0x80000000, + 0x100000000, + 0x200000000, + 0x400000000, + 0x800000000, + 0x1000000000, + 0x2000000000, + 0x4000000000, + 0x8000000000, + 0x10000000000, + 0x20000000000, + 0x40000000000, + 0x80000000000, + 0x100000000000, + 0x200000000000, + 0x400000000000, + 0x800000000000, + 0x1000000000000, + 0x2000000000000, + 0x4000000000000, + 0x8000000000000, + 0x10000000000000, + 0x20000000000000, + 0x40000000000000, + 0x80000000000000, + 0x100000000000000, + 0x200000000000000, + 0x400000000000000, + 0x800000000000000, + 0x1000000000000000, + 0x2000000000000000, + 0x4000000000000000, + 0x8000000000000000, + 0x10000000000000000, + 0x20000000000000000, + 0x40000000000000000, + 0x80000000000000000, + 0x100000000000000000, + 0x200000000000000000, + 0x400000000000000000, + 0x800000000000000000, + 0x1000000000000000000, + 0x2000000000000000000, + 0x4000000000000000000, + 0x8000000000000000000, + 0x10000000000000000000, + 0x20000000000000000000, + 0x40000000000000000000, + 0x80000000000000000000, + 0x100000000000000000000, + 0x200000000000000000000, + 0x400000000000000000000, + 0x800000000000000000000, + 0x1000000000000000000000, + 0x2000000000000000000000, + 0x4000000000000000000000, + 0x8000000000000000000000, + 0x10000000000000000000000, + 0x20000000000000000000000, + 0x40000000000000000000000, + 0x80000000000000000000000, + 0x100000000000000000000000, + 0x200000000000000000000000, + 0x400000000000000000000000, + 0x800000000000000000000000, + 0x1000000000000000000000000, + 0x2000000000000000000000000, + 0x4000000000000000000000000, + 0x8000000000000000000000000, + 0x10000000000000000000000000, + 0x20000000000000000000000000, + 0x40000000000000000000000000, + 0x80000000000000000000000000, + 0x100000000000000000000000000, + 0x200000000000000000000000000, + 0x400000000000000000000000000, + 0x800000000000000000000000000, + 0x1000000000000000000000000000, + 0x2000000000000000000000000000, + 0x4000000000000000000000000000, + 0x8000000000000000000000000000, + 0x10000000000000000000000000000, + 0x20000000000000000000000000000, + 0x40000000000000000000000000000, + 0x80000000000000000000000000000, + 0x100000000000000000000000000000, + 0x200000000000000000000000000000, + 0x400000000000000000000000000000, + 0x800000000000000000000000000000, + 0x1000000000000000000000000000000, + 0x2000000000000000000000000000000, + 0x4000000000000000000000000000000, + 0x8000000000000000000000000000000, + 0x10000000000000000000000000000000, + 0x20000000000000000000000000000000, + 0x40000000000000000000000000000000, + 0x80000000000000000000000000000000, + 0x100000000000000000000000000000000, + 0x200000000000000000000000000000000, + 0x400000000000000000000000000000000, + 0x800000000000000000000000000000000, + 0x1000000000000000000000000000000000, + 0x2000000000000000000000000000000000, + 0x4000000000000000000000000000000000, + 0x8000000000000000000000000000000000, + 0x10000000000000000000000000000000000, + 0x20000000000000000000000000000000000, + 0x40000000000000000000000000000000000, + 0x80000000000000000000000000000000000, + 0x100000000000000000000000000000000000, + 0x200000000000000000000000000000000000, + 0x400000000000000000000000000000000000, + 0x800000000000000000000000000000000000, + 0x1000000000000000000000000000000000000, + 0x2000000000000000000000000000000000000, + 0x4000000000000000000000000000000000000, + 0x8000000000000000000000000000000000000, + 0x10000000000000000000000000000000000000, + 0x20000000000000000000000000000000000000, + 0x40000000000000000000000000000000000000, + 0x80000000000000000000000000000000000000, + 0x100000000000000000000000000000000000000, + 0x200000000000000000000000000000000000000, + 0x400000000000000000000000000000000000000, + 0x800000000000000000000000000000000000000, + 0x1000000000000000000000000000000000000000, + 0x2000000000000000000000000000000000000000, + 0x4000000000000000000000000000000000000000, + 0x8000000000000000000000000000000000000000, + 0x10000000000000000000000000000000000000000, + 0x20000000000000000000000000000000000000000, + 0x40000000000000000000000000000000000000000, + 0x80000000000000000000000000000000000000000, + 0x100000000000000000000000000000000000000000, + 0x200000000000000000000000000000000000000000, + 0x400000000000000000000000000000000000000000, + 0x800000000000000000000000000000000000000000, + 0x1000000000000000000000000000000000000000000, + 0x2000000000000000000000000000000000000000000, + 0x4000000000000000000000000000000000000000000, + 0x8000000000000000000000000000000000000000000, + 0x10000000000000000000000000000000000000000000, + 0x20000000000000000000000000000000000000000000, + 0x40000000000000000000000000000000000000000000, + 0x80000000000000000000000000000000000000000000, + 0x100000000000000000000000000000000000000000000, + 0x200000000000000000000000000000000000000000000, + 0x400000000000000000000000000000000000000000000, + 0x800000000000000000000000000000000000000000000, + 0x1000000000000000000000000000000000000000000000, + 0x2000000000000000000000000000000000000000000000, + 0x4000000000000000000000000000000000000000000000, + 0x8000000000000000000000000000000000000000000000, + 0x10000000000000000000000000000000000000000000000, + 0x20000000000000000000000000000000000000000000000, + 0x40000000000000000000000000000000000000000000000, + 0x80000000000000000000000000000000000000000000000, + 0x100000000000000000000000000000000000000000000000, + 0x200000000000000000000000000000000000000000000000, + 0x400000000000000000000000000000000000000000000000, + 0x800000000000000000000000000000000000000000000000, + 0x1000000000000000000000000000000000000000000000000, + 0x2000000000000000000000000000000000000000000000000, + 0x4000000000000000000000000000000000000000000000000, + 0x8000000000000000000000000000000000000000000000000, + 0x10000000000000000000000000000000000000000000000000, + 0x20000000000000000000000000000000000000000000000000, + 0x40000000000000000000000000000000000000000000000000, + 0x80000000000000000000000000000000000000000000000000, + 0x100000000000000000000000000000000000000000000000000, + 0x200000000000000000000000000000000000000000000000000, + 0x400000000000000000000000000000000000000000000000000, + 0x800000000000000000000000000000000000000000000000000, + 0x1000000000000000000000000000000000000000000000000000, + 0x2000000000000000000000000000000000000000000000000000, + 0x4000000000000000000000000000000000000000000000000000, + 0x8000000000000000000000000000000000000000000000000000, + 0x10000000000000000000000000000000000000000000000000000, + 0x20000000000000000000000000000000000000000000000000000, + 0x40000000000000000000000000000000000000000000000000000, + 0x80000000000000000000000000000000000000000000000000000, + 0x100000000000000000000000000000000000000000000000000000, + 0x200000000000000000000000000000000000000000000000000000, + 0x400000000000000000000000000000000000000000000000000000, + 0x800000000000000000000000000000000000000000000000000000, + 0x1000000000000000000000000000000000000000000000000000000, + 0x2000000000000000000000000000000000000000000000000000000, + 0x4000000000000000000000000000000000000000000000000000000, + 0x8000000000000000000000000000000000000000000000000000000, + 0x10000000000000000000000000000000000000000000000000000000, + 0x20000000000000000000000000000000000000000000000000000000, + 0x40000000000000000000000000000000000000000000000000000000, + 0x80000000000000000000000000000000000000000000000000000000, + 0x100000000000000000000000000000000000000000000000000000000, + 0x200000000000000000000000000000000000000000000000000000000, + 0x400000000000000000000000000000000000000000000000000000000, + 0x800000000000000000000000000000000000000000000000000000000, + 0x1000000000000000000000000000000000000000000000000000000000, + 0x2000000000000000000000000000000000000000000000000000000000, + 0x4000000000000000000000000000000000000000000000000000000000, + 0x8000000000000000000000000000000000000000000000000000000000, + 0x10000000000000000000000000000000000000000000000000000000000, + 0x20000000000000000000000000000000000000000000000000000000000, + 0x40000000000000000000000000000000000000000000000000000000000, + 0x80000000000000000000000000000000000000000000000000000000000, + 0x100000000000000000000000000000000000000000000000000000000000, + 0x200000000000000000000000000000000000000000000000000000000000, + 0x400000000000000000000000000000000000000000000000000000000000, + 0x800000000000000000000000000000000000000000000000000000000000, + 0x1000000000000000000000000000000000000000000000000000000000000, + 0x2000000000000000000000000000000000000000000000000000000000000, + 0x4000000000000000000000000000000000000000000000000000000000000, + 0x8000000000000000000000000000000000000000000000000000000000000, + 0x10000000000000000000000000000000000000000000000000000000000000, + 0x20000000000000000000000000000000000000000000000000000000000000, + 0x40000000000000000000000000000000000000000000000000000000000000, + 0x80000000000000000000000000000000000000000000000000000000000000, + 0x100000000000000000000000000000000000000000000000000000000000000, + 0x200000000000000000000000000000000000000000000000000000000000000, + 0x400000000000000000000000000000000000000000000000000000000000000, + 0x800000000000000000000000000000000000000000000000000000000000000, + 0x1000000000000000000000000000000000000000000000000000000000000000, + 0x2000000000000000000000000000000000000000000000000000000000000000, + 0x4000000000000000000000000000000000000000000000000000000000000000, + 0x8000000000000000000000000000000000000000000000000000000000000000, +]; + +#[generate_trait] +pub impl TwoPower of TwoPowerTrait { + /// Get the power of two at the specified exponent + /// # Arguments + /// * `exp` - The exponent of the power of two + /// # Returns + /// * The power of two at the specified exponent + #[inline] + fn pow(exp: u8) -> u256 { + *TWO_POWER.span().at(exp.into()) + } +} + +#[cfg(test)] +mod tests { + // Local imports + + use super::TwoPower; + + #[test] + fn test_two_power_exp_0() { + assert_eq!(TwoPower::pow(0), 1); + } + + #[test] + fn test_two_power_exp_1() { + assert_eq!(TwoPower::pow(1), 2); + } + + #[test] + fn test_two_power_exp_255() { + assert_eq!( + TwoPower::pow(255), 0x8000000000000000000000000000000000000000000000000000000000000000 + ); + } +} diff --git a/crates/map/src/helpers/seeder.cairo b/crates/map/src/helpers/seeder.cairo new file mode 100644 index 00000000..7966a8be --- /dev/null +++ b/crates/map/src/helpers/seeder.cairo @@ -0,0 +1,37 @@ +// Core imports + +use core::hash::HashStateTrait; +use core::poseidon::PoseidonTrait; + +#[generate_trait] +pub impl Seeder of SeederTrait { + /// Shuffle two values to generate a new value. + /// # Arguments + /// * `lhs` - The left value + /// * `rhs` - The right value + /// # Returns + /// * The shuffled value + #[inline] + fn shuffle(lhs: felt252, rhs: felt252) -> felt252 { + let mut state = PoseidonTrait::new(); + state = state.update(lhs); + state = state.update(rhs); + state.finalize() + } + + /// Generate a random position on the map excluding the edges. + /// # Arguments + /// * `width` - The width of the map + /// * `height` - The height of the map + /// * `seed` - The seed to generate the position + /// # Returns + /// * The random position + #[inline] + fn random_position(width: u8, height: u8, seed: felt252) -> u8 { + let seed: u256 = seed.into(); + let x: u8 = (seed % (width - 2).into()).try_into().unwrap() + 1; + let seed: u256 = Self::shuffle(seed.low.into(), seed.high.into()).into(); + let y: u8 = (seed % (height - 2).into()).try_into().unwrap() + 1; + x + y * width + } +} diff --git a/crates/map/src/helpers/spreader.cairo b/crates/map/src/helpers/spreader.cairo new file mode 100644 index 00000000..fe0f6583 --- /dev/null +++ b/crates/map/src/helpers/spreader.cairo @@ -0,0 +1,128 @@ +//! Spread objects into a map. + +// Internal imports + +use origami_map::helpers::bitmap::Bitmap; +use origami_map::helpers::seeder::Seeder; +use origami_map::helpers::asserter::{Asserter, MAX_SIZE}; + +// Constants + +const MULTIPLIER: u256 = 10000; + +/// Errors module. +pub mod errors { + pub const SPREADER_INVALID_DIMENSION: felt252 = 'Spreader: invalid dimension'; + pub const SPREADER_NOT_ENOUGH_PLACE: felt252 = 'Spreader: not enough place'; +} + +/// Implementation of the `SpreaderTrait` trait. +#[generate_trait] +pub impl Spreader of SpreaderTrait { + /// Spread objects into a map. + /// # Arguments + /// * `grid` - The grid where to spread the objects + /// * `width` - The width of the grid + /// * `height` - The height of the grid + /// * `count` - The number of objects to spread + /// * `seed` - The seed to spread the objects + /// # Returns + /// * The grid with the objects spread + #[inline] + fn generate(grid: felt252, width: u8, height: u8, count: u8, mut seed: felt252) -> felt252 { + // [Check] Valid dimensions + Asserter::assert_valid_dimension(width, height); + // [Check] Ensure there is enough space for the objects + let total = Bitmap::popcount(grid); + assert(count <= total, errors::SPREADER_NOT_ENOUGH_PLACE); + // [Effect] Deposite objects uniformly + let start = Bitmap::least_significant_bit(grid); + let merge = Self::iter(grid, start, total, count, seed); + let objects: u256 = grid.into() ^ merge.into(); + objects.try_into().unwrap() + } + + /// Recursive function to spread objects into the grid. + /// # Arguments + /// * `grid` - The grid where to spread the objects + /// * `index` - The current index + /// * `total` - The total number of objects + /// * `count` - The number of objects to spread + /// * `seed` - The seed to spread the objects + /// # Returns + /// * The original grid with the objects spread set to 0 + #[inline] + fn iter(mut grid: felt252, index: u8, total: u8, mut count: u8, seed: felt252) -> felt252 { + // [Checl] Stop if all objects are placed + if count == 0 { + return grid; + }; + // [Check] Skip if the position is already occupied + let seed = Seeder::shuffle(seed, seed); + if Bitmap::get(grid, index) == 0 { + return Self::iter(grid, index + 1, total, count, seed); + }; + // [Compute] Uniform random number between 0 and MULTIPLIER + let random = seed.into() % MULTIPLIER; + let probability: u256 = count.into() * MULTIPLIER / total.into(); + // [Check] Probability of being an object + if random <= probability { + // [Compute] Update grid + count -= 1; + // [Effect] Set bit to 0 + grid = Bitmap::unset(grid, index); + }; + Self::iter(grid, index + 1, total - 1, count, seed) + } +} + +#[cfg(test)] +mod tests { + // Local imports + + use super::Spreader; + + // Constants + + const SEED: felt252 = 'SEED'; + + #[test] + fn test_spreader_generate_large() { + // 000000000000100000 + // 000010100000000000 + // 000010000100000000 + // 000000101000000000 + // 011001000000100000 + // 000000100001000000 + // 000100000000001100 + // 000000100000000000 + // 010000110000100011 + // 000000000000000010 + // 010000010000111000 + // 000001000010000000 + // 001000000000000000 + // 001000000100100000 + let width = 18; + let height = 14; + let grid: felt252 = 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF; + let room = Spreader::generate(grid, width, height, 35, SEED); + assert_eq!(room, 0x802800084000A006408008401003008004308C0002410E01080200008120); + } + + #[test] + fn test_spreader_generate_small() { + // 000000001110000000 + // 000000001110000000 + // 000000001110000000 + // Output: + // 000000000110000000 + // 000000000000000000 + // 000000001100000000 + let width = 18; + let height = 14; + let grid: felt252 = 0x38000E000380; + let room = Spreader::generate(grid, width, height, 4, SEED); + assert_eq!(room, 0x180000000300); + } +} + diff --git a/crates/map/src/helpers/walker.cairo b/crates/map/src/helpers/walker.cairo new file mode 100644 index 00000000..30513551 --- /dev/null +++ b/crates/map/src/helpers/walker.cairo @@ -0,0 +1,140 @@ +//! Random walk algorithm to generate a grid. + +// Internal imports + +use origami_map::helpers::bitmap::Bitmap; +use origami_map::helpers::seeder::Seeder; +use origami_map::helpers::mazer::Mazer; +use origami_map::helpers::asserter::Asserter; +use origami_map::types::direction::{Direction, DirectionTrait}; + +// Constants + +const DIRECTION_SIZE: u32 = 0x10; + +/// Implementation of the `WalkerTrait` trait. +#[generate_trait] +pub impl Walker of WalkerTrait { + /// Generate a random walk. + /// # Arguments + /// * `width` - The width of the walk + /// * `height` - The height of the walk + /// * `steps` - The number of steps to walk + /// * `seed` - The seed to generate the walk + /// # Returns + /// * The generated walk + #[inline] + fn generate(width: u8, height: u8, steps: u16, seed: felt252) -> felt252 { + // [Check] Valid dimensions + Asserter::assert_valid_dimension(width, height); + // [Effect] Add start position + // [Compute] Engage the random walk + let start = Seeder::random_position(width, height, seed); + let mut grid = 0; + Self::iter(width, height, start, steps, ref grid, seed); + grid + } + + /// Recursive function to generate the random walk. + /// # Arguments + /// * `width` - The width of the walk + /// * `height` - The height of the walk + /// * `start` - The starting position + /// * `steps` - The number of steps to walk + /// * `grid` - The original grid + /// * `seed` - The seed to generate the walk + /// # Returns + /// * The original grid with the walk + #[inline] + fn iter(width: u8, height: u8, start: u8, mut steps: u16, ref grid: felt252, seed: felt252) { + // [Check] Stop if the recursion runs out of steps + if steps == 0 { + return; + } + steps -= 1; + // [Effect] Set the position + grid = Bitmap::set(grid, start); + // [Compute] Generate shuffled neighbors + let seed = Seeder::shuffle(seed, seed); + let mut directions = DirectionTrait::compute_shuffled_directions(seed); + // [Assess] Direction 1 + let direction: Direction = DirectionTrait::pop_front(ref directions); + if Self::check(grid, width, height, start, direction) { + let start = Mazer::next(width, start, direction); + return Self::iter(width, height, start, steps, ref grid, seed); + } + // [Assess] Direction 2 + let direction: Direction = DirectionTrait::pop_front(ref directions); + if Self::check(grid, width, height, start, direction) { + let start = Mazer::next(width, start, direction); + return Self::iter(width, height, start, steps, ref grid, seed); + } + // [Assess] Direction 3 + let direction: Direction = DirectionTrait::pop_front(ref directions); + if Self::check(grid, width, height, start, direction) { + let start = Mazer::next(width, start, direction); + return Self::iter(width, height, start, steps, ref grid, seed); + } + // [Assess] Direction 4 + let direction: Direction = DirectionTrait::pop_front(ref directions); + if Self::check(grid, width, height, start, direction) { + let start = Mazer::next(width, start, direction); + return Self::iter(width, height, start, steps, ref grid, seed); + }; + } + + /// Check if the position can be visited in the specified direction. + /// # Arguments + /// * `grid` - The grid + /// * `width` - The width of the grid + /// * `height` - The height of the grid + /// * `position` - The current position + /// * `direction` - The direction to check + /// # Returns + /// * Whether the position can be visited + #[inline] + fn check(grid: felt252, width: u8, height: u8, position: u8, direction: Direction) -> bool { + let (x, y) = (position % width, position / width); + match direction { + Direction::North => (y < height - 2) && (x != 0) && (x != width - 1), + Direction::East => (x < width - 2) && (y != 0) && (y != height - 1), + Direction::South => (y > 1) && (x != 0) && (x != width - 1), + Direction::West => (x > 1) && (y != 0) && (y != height - 1), + _ => false, + } + } +} + +#[cfg(test)] +mod tests { + // Local imports + + use super::Walker; + + // Constants + + const SEED: felt252 = 'SEED'; + + #[test] + fn test_walker_generate() { + // 000000000000000000 + // 000111111100000000 + // 001111111000000000 + // 001111101100000000 + // 000111111100000000 + // 000011110001001000 + // 000111110001111100 + // 001111111111111110 + // 000111101110111110 + // 000011111111111110 + // 000111111111111110 + // 000001111111111110 + // 000000111111111110 + // 000000000000000000 + let width = 18; + let height = 14; + let steps: u16 = 500; + let walk: felt252 = Walker::generate(width, height, steps, SEED); + assert_eq!(walk, 0x7F003F800FB001FC003C481F1F0FFFE1EEF83FFE1FFF81FFE03FF80000); + } +} diff --git a/crates/map/src/hex/hex.cairo b/crates/map/src/hex.cairo similarity index 64% rename from crates/map/src/hex/hex.cairo rename to crates/map/src/hex.cairo index f996cbd2..de3595ac 100644 --- a/crates/map/src/hex/hex.cairo +++ b/crates/map/src/hex.cairo @@ -1,42 +1,51 @@ -use super::types::{HexTile, Direction, DirectionIntoFelt252}; - -pub trait IHexTile { - fn new(col: u32, row: u32) -> HexTile; - fn neighbor(self: HexTile, direction: Direction) -> HexTile; - fn neighbor_even_y(self: HexTile, direction: Direction) -> HexTile; - fn neighbors(self: HexTile) -> Array; - fn is_neighbor(self: HexTile, other: HexTile) -> bool; - fn tiles_within_range(self: HexTile, range: u32) -> Array; +/// Internal imports. + +use origami_map::types::direction::Direction; + +/// Types. + +#[derive(Drop, Copy, Serde)] +pub struct Hex { + pub col: u32, + pub row: u32, } -pub impl ImplHexTile of IHexTile { - fn new(col: u32, row: u32) -> HexTile { - HexTile { col, row } +/// Implementation of the `HexTrait` trait for the `Hex` struct. +#[generate_trait] +pub impl HexImpl of HexTrait { + #[inline] + fn new(col: u32, row: u32) -> Hex { + Hex { col, row } } - fn neighbor(self: HexTile, direction: Direction) -> HexTile { + #[inline] + fn neighbor(self: Hex, direction: Direction) -> Hex { match direction { - Direction::East(()) => HexTile { col: self.col + 1, row: self.row }, - Direction::NorthEast(()) => HexTile { col: self.col + 1, row: self.row - 1 }, - Direction::NorthWest(()) => HexTile { col: self.col, row: self.row - 1 }, - Direction::West(()) => HexTile { col: self.col - 1, row: self.row }, - Direction::SouthWest(()) => HexTile { col: self.col, row: self.row + 1 }, - Direction::SouthEast(()) => HexTile { col: self.col + 1, row: self.row + 1 }, + Direction::East(()) => Hex { col: self.col + 1, row: self.row }, + Direction::NorthEast(()) => Hex { col: self.col + 1, row: self.row - 1 }, + Direction::NorthWest(()) => Hex { col: self.col, row: self.row - 1 }, + Direction::West(()) => Hex { col: self.col - 1, row: self.row }, + Direction::SouthWest(()) => Hex { col: self.col, row: self.row + 1 }, + Direction::SouthEast(()) => Hex { col: self.col + 1, row: self.row + 1 }, + _ => self, } } - fn neighbor_even_y(self: HexTile, direction: Direction) -> HexTile { + #[inline] + fn neighbor_even_y(self: Hex, direction: Direction) -> Hex { match direction { - Direction::East(()) => HexTile { col: self.col + 1, row: self.row }, - Direction::NorthEast(()) => HexTile { col: self.col, row: self.row + 1 }, - Direction::NorthWest(()) => HexTile { col: self.col - 1, row: self.row + 1 }, - Direction::West(()) => HexTile { col: self.col - 1, row: self.row }, - Direction::SouthWest(()) => HexTile { col: self.col - 1, row: self.row - 1 }, - Direction::SouthEast(()) => HexTile { col: self.col, row: self.row - 1 }, + Direction::East(()) => Hex { col: self.col + 1, row: self.row }, + Direction::NorthEast(()) => Hex { col: self.col, row: self.row + 1 }, + Direction::NorthWest(()) => Hex { col: self.col - 1, row: self.row + 1 }, + Direction::West(()) => Hex { col: self.col - 1, row: self.row }, + Direction::SouthWest(()) => Hex { col: self.col - 1, row: self.row - 1 }, + Direction::SouthEast(()) => Hex { col: self.col, row: self.row - 1 }, + _ => self, } } - fn neighbors(self: HexTile) -> Array { + #[inline] + fn neighbors(self: Hex) -> Array { if (self.row % 2 == 0) { return array![ self.neighbor_even_y(Direction::East(())), @@ -57,7 +66,7 @@ pub impl ImplHexTile of IHexTile { ]; } - fn is_neighbor(self: HexTile, other: HexTile) -> bool { + fn is_neighbor(self: Hex, other: Hex) -> bool { let mut neighbors = self.neighbors(); loop { @@ -75,7 +84,7 @@ pub impl ImplHexTile of IHexTile { } } - fn tiles_within_range(self: HexTile, range: u32) -> Array { + fn tiles_within_range(self: Hex, range: u32) -> Array { let mut queue = array![self]; let mut visited = array![self]; let mut moves = 0; @@ -125,14 +134,14 @@ pub impl ImplHexTile of IHexTile { } -// tests ----------------------------------------------------------------------- // +/// Tests. #[cfg(test)] mod tests { - use super::{IHexTile, ImplHexTile, Direction, HexTile}; + use super::{HexTrait, Direction, Hex}; #[test] fn test_row_col() { - let mut hex_tile = ImplHexTile::new(5, 5); + let mut hex_tile = HexTrait::new(5, 5); assert(hex_tile.col == 5, 'col should be 5'); assert(hex_tile.row == 5, 'row should be 5'); @@ -141,7 +150,7 @@ mod tests { #[test] fn test_hex_tile_neighbors() { - let mut hex_tile = ImplHexTile::new(5, 5); + let mut hex_tile = HexTrait::new(5, 5); let east_neighbor = hex_tile.neighbor(Direction::East(())); @@ -176,33 +185,32 @@ mod tests { #[test] fn test_is_neighbor() { - let mut hex_tile = ImplHexTile::new(5, 5); + let mut hex_tile = HexTrait::new(5, 5); - assert(hex_tile.is_neighbor(HexTile { col: hex_tile.col + 1, row: hex_tile.row }), 'east'); + assert(hex_tile.is_neighbor(Hex { col: hex_tile.col + 1, row: hex_tile.row }), 'east'); assert( - hex_tile.is_neighbor(HexTile { col: hex_tile.col, row: hex_tile.row + 1 }), 'north east' + hex_tile.is_neighbor(Hex { col: hex_tile.col, row: hex_tile.row + 1 }), 'north east' ); assert( - hex_tile.is_neighbor(HexTile { col: hex_tile.col, row: hex_tile.row - 1 }), 'north west' + hex_tile.is_neighbor(Hex { col: hex_tile.col, row: hex_tile.row - 1 }), 'north west' ); - assert(hex_tile.is_neighbor(HexTile { col: hex_tile.col - 1, row: hex_tile.row }), 'west'); + assert(hex_tile.is_neighbor(Hex { col: hex_tile.col - 1, row: hex_tile.row }), 'west'); assert( - hex_tile.is_neighbor(HexTile { col: hex_tile.col, row: hex_tile.row - 1 }), 'south west' + hex_tile.is_neighbor(Hex { col: hex_tile.col, row: hex_tile.row - 1 }), 'south west' ); assert( - hex_tile.is_neighbor(HexTile { col: hex_tile.col + 1, row: hex_tile.row - 1 }), - 'south east' + hex_tile.is_neighbor(Hex { col: hex_tile.col + 1, row: hex_tile.row - 1 }), 'south east' ); } #[test] fn test_tiles_within_range() { - let mut hex_tile = ImplHexTile::new(5, 5); + let mut hex_tile = HexTrait::new(5, 5); let tiles_range_one = hex_tile.tiles_within_range(1); let tiles_range_two = hex_tile.tiles_within_range(2); diff --git a/crates/map/src/hex/types.cairo b/crates/map/src/hex/types.cairo deleted file mode 100644 index ec9bdf44..00000000 --- a/crates/map/src/hex/types.cairo +++ /dev/null @@ -1,28 +0,0 @@ -#[derive(Drop, Copy, Serde)] -pub struct HexTile { - pub col: u32, - pub row: u32, -} - -#[derive(Drop, Copy, Serde)] -pub enum Direction { - East: (), - NorthEast: (), - NorthWest: (), - West: (), - SouthWest: (), - SouthEast: (), -} - -pub impl DirectionIntoFelt252 of Into { - fn into(self: Direction) -> felt252 { - match self { - Direction::East => 0, - Direction::NorthEast => 1, - Direction::NorthWest => 2, - Direction::West => 3, - Direction::SouthWest => 4, - Direction::SouthEast => 5, - } - } -} diff --git a/crates/map/src/lib.cairo b/crates/map/src/lib.cairo index 5d2224c4..6b71440d 100644 --- a/crates/map/src/lib.cairo +++ b/crates/map/src/lib.cairo @@ -1,4 +1,19 @@ -pub mod hex { - pub mod hex; - pub mod types; +pub mod hex; +pub mod room; + +pub mod types { + pub mod direction; } + +pub mod helpers { + pub mod asserter; + pub mod bitmap; + pub mod power; + pub mod seeder; + pub mod digger; + pub mod mazer; + pub mod caver; + pub mod walker; + pub mod spreader; +} + diff --git a/crates/map/src/room.cairo b/crates/map/src/room.cairo new file mode 100644 index 00000000..0d23801a --- /dev/null +++ b/crates/map/src/room.cairo @@ -0,0 +1,288 @@ +//! Room struct and generation methods. + +// Internal imports + +use origami_map::helpers::power::TwoPower; +use origami_map::helpers::mazer::Mazer; +use origami_map::helpers::asserter::Asserter; +use origami_map::helpers::walker::Walker; +use origami_map::helpers::caver::Caver; +use origami_map::helpers::digger::Digger; +use origami_map::helpers::spreader::Spreader; + +/// Types. +#[derive(Copy, Drop)] +pub struct Room { + pub width: u8, + pub height: u8, + pub grid: felt252, + pub seed: felt252, +} + +/// Implementation of the `RoomTrait` trait for the `Room` struct. +#[generate_trait] +pub impl RoomImpl of RoomTrait { + /// Create an empty room. + /// # Arguments + /// * `width` - The width of the room + /// * `height` - The height of the room + /// * `seed` - The seed to generate the room + /// # Returns + /// * The generated room + #[inline] + fn new_empty(width: u8, height: u8, seed: felt252) -> Room { + // [Check] Valid dimensions + Asserter::assert_valid_dimension(width, height); + // [Effect] Generate room according to the method + let grid = Private::empty(width, height); + // [Effect] Create room + Room { width, height, grid, seed } + } + + /// Create a room with a maze. + /// # Arguments + /// * `width` - The width of the room + /// * `height` - The height of the room + /// * `seed` - The seed to generate the room + /// # Returns + /// * The generated room + #[inline] + fn new_maze(width: u8, height: u8, seed: felt252) -> Room { + let grid = Mazer::generate(width, height, seed); + Room { width, height, grid, seed } + } + + /// Create a room with a cave. + /// # Arguments + /// * `width` - The width of the room + /// * `height` - The height of the room + /// * `order` - The order of the cave + /// * `seed` - The seed to generate the room + /// # Returns + /// * The generated room + #[inline] + fn new_cave(width: u8, height: u8, order: u8, seed: felt252) -> Room { + let grid = Caver::generate(width, height, order, seed); + Room { width, height, grid, seed } + } + + /// Create a room with a random walk. + /// # Arguments + /// * `width` - The width of the room + /// * `height` - The height of the room + /// * `steps` - The number of steps to walk + /// * `seed` - The seed to generate the room + /// # Returns + /// * The generated room + #[inline] + fn new_random_walk(width: u8, height: u8, steps: u16, seed: felt252) -> Room { + let grid = Walker::generate(width, height, steps, seed); + Room { width, height, grid, seed } + } + + /// Open the room with a corridor. + /// # Arguments + /// * `position` - The position of the corridor + /// # Returns + /// * The room with the corridor + #[inline] + fn open_with_corridor(ref self: Room, position: u8) { + // [Effect] Add a corridor to open the room + self.grid = Digger::corridor(self.width, self.height, position, self.grid, self.seed); + } + + /// Open the room with a maze. + /// # Arguments + /// * `position` - The position of the maze + /// # Returns + /// * The room with the maze + #[inline] + fn open_with_maze(ref self: Room, position: u8) { + // [Effect] Add a maze to open the room + self.grid = Digger::maze(self.width, self.height, position, self.grid, self.seed); + } + + /// Compute a distribution of objects in the room. + /// # Arguments + /// * `count` - The number of objects to distribute + /// # Returns + /// * The distribution of objects + #[inline] + fn compute_distribution(ref self: Room, count: u8, seed: felt252) -> felt252 { + Spreader::generate(self.grid, self.width, self.height, count, seed) + } +} + +#[generate_trait] +impl Private of PrivateTrait { + /// Generate an empty room. + /// # Arguments + /// * `width` - The width of the room + /// * `height` - The height of the room + /// # Returns + /// * The generated empty room + #[inline] + fn empty(width: u8, height: u8) -> felt252 { + // [Effect] Generate empty room + let offset: u256 = TwoPower::pow(width); + let row: felt252 = ((offset - 1) / 2).try_into().unwrap() - 1; // Remove head and tail bits + let offset: felt252 = offset.try_into().unwrap(); + let mut index = height - 2; + let mut default: felt252 = 0; + loop { + if index == 0 { + break; + }; + default += row; + default *= offset; + index -= 1; + }; + default + } +} + +#[cfg(test)] +mod tests { + // Local imports + + use super::{Room, RoomTrait}; + use origami_map::helpers::seeder::Seeder; + + // Constants + + const SEED: felt252 = 'S33D'; + + #[test] + fn test_room_new() { + // 000000000000000000 + // 011111111111111110 + // 011111111111111110 + // 011111111111111110 + // 011111111111111110 + // 011111111111111110 + // 011111111111111110 + // 011111111111111110 + // 011111111111111110 + // 011111111111111110 + // 011111111111111110 + // 011111111111111110 + // 011111111111111110 + // 000000000000000010 + let width = 18; + let height = 14; + let mut room: Room = RoomTrait::new_empty(width, height, SEED); + room.open_with_corridor(1); + assert_eq!(room.grid, 0x1FFFE7FFF9FFFE7FFF9FFFE7FFF9FFFE7FFF9FFFE7FFF9FFFE7FFF80002); + } + + #[test] + fn test_room_maze() { + // 000000000000000000 + // 010111011111110110 + // 011101101000011100 + // 001011011011101010 + // 001100110010011110 + // 011011100111101010 + // 010110011101011010 + // 011011101011010110 + // 001000110110011010 + // 001111011010110110 + // 011001110011000100 + // 010110101101011100 + // 011101111011110110 + // 000000000000000010 + let width = 18; + let height = 14; + let mut room: Room = RoomTrait::new_maze(width, height, SEED); + room.open_with_corridor(1); + assert_eq!(room.grid, 0x177F676870B6EA33279B9EA59D69BAD623668F6B6673116B5C77BD80002); + } + + #[test] + fn test_room_cave() { + // 000000000000000000 + // 001100001100000000 + // 011111001100000000 + // 011111000110000110 + // 011111100111000110 + // 011111100011000000 + // 011111100000000000 + // 011111110000000000 + // 011111111100000000 + // 011111111111000000 + // 011111111111100110 + // 001111111111111110 + // 001111111111111110 + // 000000000000000010 + let width = 18; + let height = 14; + let order = 3; + let seed: felt252 = Seeder::shuffle(SEED, SEED); + let mut room: Room = RoomTrait::new_cave(width, height, order, seed); + room.open_with_corridor(1); + assert_eq!(room.grid, 0xC3007CC01F1867E719F8C07E001FC007FC01FFC07FF98FFFE3FFF80002); + } + + #[test] + fn test_room_random_walk() { + // 010000000000000000 + // 010000000011000000 + // 011000000111001100 + // 001101000111111110 + // 011011100011111110 + // 001111111111111110 + // 011010011111111110 + // 001010011101111110 + // 011011111111111110 + // 010011111111111110 + // 010011111111111110 + // 011011111111100000 + // 001101111111100000 + // 000000000000000000 + let width = 18; + let height = 14; + let steps: u16 = 2 * width.into() * height.into(); + let mut room: Room = RoomTrait::new_random_walk(width, height, steps, SEED); + room.open_with_maze(250); + assert_eq!(room.grid, 0x4000100C060730D1FE6E3F8FFFE69FF8A77E6FFF93FFE4FFF9BFE037F800000); + } + + #[test] + fn test_room_compute_distribution() { + // 000000000000000000 + // 000000000011000000 + // 000000000111001100 + // 000001000111111110 + // 000011100011111110 + // 000011111111111110 + // 000010011111111110 + // 000010011101111110 + // 000011111111111110 + // 000011111111111110 + // 000011111111111110 + // 000011111111100000 + // 000001111111100000 + // 000000000000000000 + // Output: + // 000000000000000000 + // 000000000000000000 + // 000000000000000000 + // 000000000000001000 + // 000000100001000000 + // 000010000000001000 + // 000000000000000000 + // 000000000000000000 + // 000000000001001000 + // 000000000100000000 + // 000000000000000000 + // 000000010000100000 + // 000000000000000000 + // 000000000000000000 + let width = 18; + let height = 14; + let steps: u16 = 2 * width.into() * height.into(); + let mut room: Room = RoomTrait::new_random_walk(width, height, steps, SEED); + let distribution = room.compute_distribution(10, SEED); + assert_eq!(distribution, 0x8021002008000000000001200100000000420000000000); + } +} diff --git a/crates/map/src/types/direction.cairo b/crates/map/src/types/direction.cairo new file mode 100644 index 00000000..6648fcb4 --- /dev/null +++ b/crates/map/src/types/direction.cairo @@ -0,0 +1,111 @@ +// Constants + +pub const DIRECTION_SIZE: u32 = 0x10; + +// Types. +#[derive(Drop, Copy, Serde)] +pub struct HexTile { + pub col: u32, + pub row: u32, +} + +#[derive(Drop, Copy, Serde)] +pub enum Direction { + None, + NorthWest, + North, + NorthEast, + East, + SouthEast, + South, + SouthWest, + West, +} + +#[generate_trait] +pub impl DirectionImpl of DirectionTrait { + /// Compute shuffled directions. + /// # Arguments + /// * `seed` - The seed to generate the shuffled directions + /// # Returns + /// * The shuffled directions + /// # Info + /// * 0: North, 1: East, 2: South, 3: West + #[inline] + fn compute_shuffled_directions(seed: felt252) -> u32 { + // [Compute] Random number + let mut random: u32 = (seed.into() % 24_u256).try_into().unwrap(); + // [Return] Pickup a random permutation + match random { + 0 => 0x2468, + 1 => 0x2486, + 2 => 0x2648, + 3 => 0x2684, + 4 => 0x2846, + 5 => 0x2864, + 6 => 0x4268, + 7 => 0x4286, + 8 => 0x4628, + 9 => 0x4682, + 10 => 0x4826, + 11 => 0x4862, + 12 => 0x6248, + 13 => 0x6284, + 14 => 0x6428, + 15 => 0x6482, + 16 => 0x6824, + 17 => 0x6842, + 18 => 0x8246, + 19 => 0x8264, + 20 => 0x8426, + 21 => 0x8462, + 22 => 0x8624, + _ => 0x8642, + } + } + + /// Get the next direction from a packed directions. + /// # Arguments + /// * `directions` - The packed directions + /// # Returns + /// * The next direction + #[inline] + fn pop_front(ref directions: u32) -> Direction { + let direciton: u8 = (directions % DIRECTION_SIZE).try_into().unwrap(); + directions /= DIRECTION_SIZE; + direciton.into() + } +} + +pub impl DirectionIntoFelt252 of Into { + fn into(self: Direction) -> felt252 { + match self { + Direction::None => 0, + Direction::NorthWest => 1, + Direction::North => 2, + Direction::NorthEast => 3, + Direction::East => 4, + Direction::SouthEast => 5, + Direction::South => 6, + Direction::SouthWest => 7, + Direction::West => 8, + } + } +} + +pub impl DirectionFromU8 of Into { + fn into(self: u8) -> Direction { + match self { + 0 => Direction::None, + 1 => Direction::NorthWest, + 2 => Direction::North, + 3 => Direction::NorthEast, + 4 => Direction::East, + 5 => Direction::SouthEast, + 6 => Direction::South, + 7 => Direction::SouthWest, + 8 => Direction::West, + _ => Direction::None, + } + } +}