minimal spanning tree implemented
no disconnected rooms
This commit is contained in:
		
							parent
							
								
									b4122d321b
								
							
						
					
					
						commit
						91160edb3f
					
				
							
								
								
									
										33
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										33
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							| @ -86,10 +86,23 @@ name = "el_diabolo" | |||||||
| version = "0.1.0" | version = "0.1.0" | ||||||
| dependencies = [ | dependencies = [ | ||||||
|  "crossterm", |  "crossterm", | ||||||
|  |  "petgraph", | ||||||
|  "rand", |  "rand", | ||||||
|  "ratatui", |  "ratatui", | ||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "equivalent" | ||||||
|  | version = "1.0.1" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" | ||||||
|  | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "fixedbitset" | ||||||
|  | version = "0.4.2" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" | ||||||
|  | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "getrandom" | name = "getrandom" | ||||||
| version = "0.2.11" | version = "0.2.11" | ||||||
| @ -117,6 +130,16 @@ version = "0.4.1" | |||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" | checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" | ||||||
| 
 | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "indexmap" | ||||||
|  | version = "2.1.0" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" | ||||||
|  | dependencies = [ | ||||||
|  |  "equivalent", | ||||||
|  |  "hashbrown", | ||||||
|  | ] | ||||||
|  | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "indoc" | name = "indoc" | ||||||
| version = "2.0.4" | version = "2.0.4" | ||||||
| @ -210,6 +233,16 @@ version = "1.0.14" | |||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" | checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" | ||||||
| 
 | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "petgraph" | ||||||
|  | version = "0.6.4" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "e1d3afd2628e69da2be385eb6f2fd57c8ac7977ceeff6dc166ff1657b0e386a9" | ||||||
|  | dependencies = [ | ||||||
|  |  "fixedbitset", | ||||||
|  |  "indexmap", | ||||||
|  | ] | ||||||
|  | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "ppv-lite86" | name = "ppv-lite86" | ||||||
| version = "0.2.17" | version = "0.2.17" | ||||||
|  | |||||||
| @ -9,3 +9,4 @@ edition = "2021" | |||||||
| ratatui = "0.24.0" | ratatui = "0.24.0" | ||||||
| crossterm = "0.27.0" | crossterm = "0.27.0" | ||||||
| rand = "0.8.5" | rand = "0.8.5" | ||||||
|  | petgraph = "0.6.4" | ||||||
|  | |||||||
| @ -1,5 +1,10 @@ | |||||||
| use rand::prelude::SliceRandom; | use rand::prelude::SliceRandom; | ||||||
| use rand::Rng; | use rand::Rng; | ||||||
|  | use petgraph::graph::UnGraph; | ||||||
|  | use petgraph::graph::Graph; | ||||||
|  | use petgraph::data::*; | ||||||
|  | use petgraph::algo::min_spanning_tree; | ||||||
|  | use rand::rngs::ThreadRng; | ||||||
| 
 | 
 | ||||||
| use crate::level::{Level, LevelElement, RoomType}; | use crate::level::{Level, LevelElement, RoomType}; | ||||||
| 
 | 
 | ||||||
| @ -15,9 +20,9 @@ struct Room { | |||||||
|     pub offset_y: usize, |     pub offset_y: usize, | ||||||
|     pub width: usize, |     pub width: usize, | ||||||
|     pub height: usize, |     pub height: usize, | ||||||
|     pub connection_down: bool, |     // pub connection_down: bool,
 | ||||||
|     pub connection_right: bool, |     // pub connection_right: bool,
 | ||||||
|     pub connected: bool, |     // pub connected: bool,
 | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl Room { | impl Room { | ||||||
| @ -28,11 +33,20 @@ impl Room { | |||||||
|             offset_y: 0, |             offset_y: 0, | ||||||
|             width: 0, |             width: 0, | ||||||
|             height: 0, |             height: 0, | ||||||
|             connection_down: false, |             // connection_down: false,
 | ||||||
|             connection_right: false, |             // connection_right: false,
 | ||||||
|             connected: false, |             // connected: false,
 | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |     /// change the size and position of a room randomly within its bounds
 | ||||||
|  |     fn random(&mut self, rng: &mut ThreadRng) { | ||||||
|  |         let width = rng.gen_range(3..6); | ||||||
|  |         let height = rng.gen_range(3..5); | ||||||
|  |         self.width = width; | ||||||
|  |         self.height = height; | ||||||
|  |         self.offset_x = rng.gen_range(0..(ROOM_WIDTH - width)); | ||||||
|  |         self.offset_y = rng.gen_range(0..(ROOM_HEIGHT - height)); | ||||||
|  |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #[derive(Debug)] | #[derive(Debug)] | ||||||
| @ -42,74 +56,114 @@ pub struct LevelGenerator { | |||||||
| 
 | 
 | ||||||
| impl LevelGenerator { | impl LevelGenerator { | ||||||
|     pub fn generate(level: usize) -> Self { |     pub fn generate(level: usize) -> Self { | ||||||
|         let mut room_types: Vec<RoomType> = Vec::with_capacity(ROOMS_HORIZONTAL * ROOMS_VERTICAL); |  | ||||||
|         if level > 0 { |  | ||||||
|             room_types.push(RoomType::StairUp); |  | ||||||
|         } else { |  | ||||||
|             room_types.push(RoomType::Start); |  | ||||||
|         } |  | ||||||
|         if level < 24 { |  | ||||||
|             room_types.push(RoomType::StairDown); |  | ||||||
|         } else { |  | ||||||
|             room_types.push(RoomType::End); |  | ||||||
|         } |  | ||||||
|         let mut rng = rand::thread_rng(); |         let mut rng = rand::thread_rng(); | ||||||
|  |         let mut rooms = [[Room::new(); ROOMS_HORIZONTAL]; ROOMS_VERTICAL]; | ||||||
|  |         let mut graph = UnGraph::<(usize, usize), u16>::default(); | ||||||
|  | 
 | ||||||
|  |         // trick the room_connectable function into failing on the first iteration
 | ||||||
|  |         rooms[0][0].kind = RoomType::BasicRoom; | ||||||
|  | 
 | ||||||
|  |         while !LevelGenerator::rooms_connectable(&rooms) { | ||||||
|  |             let mut room_types: Vec<RoomType> = Vec::with_capacity(ROOMS_HORIZONTAL * ROOMS_VERTICAL); | ||||||
|  |             // level 0 contains a start room, all others contain a stair up
 | ||||||
|  |             if level == 0 { | ||||||
|  |                 room_types.push(RoomType::Start); | ||||||
|  |             } else { | ||||||
|  |                 room_types.push(RoomType::StairUp); | ||||||
|  |             } | ||||||
|  |             // level 24 (the last) contains an end room, all others a stair down
 | ||||||
|  |             if level == 24 { | ||||||
|  |                 room_types.push(RoomType::End); | ||||||
|  |             } else { | ||||||
|  |                 room_types.push(RoomType::StairDown); | ||||||
|  |             } | ||||||
|  |             // generate a random set of rooms and shuffle them
 | ||||||
|             for _ in room_types.len()..ROOMS_HORIZONTAL * ROOMS_VERTICAL { |             for _ in room_types.len()..ROOMS_HORIZONTAL * ROOMS_VERTICAL { | ||||||
|                 match rng.gen_range(1..=6) { |                 match rng.gen_range(1..=6) { | ||||||
|  |                     // TODO tune room type distribution
 | ||||||
|                     1..=3 => { room_types.push(RoomType::EmptyRoom) } |                     1..=3 => { room_types.push(RoomType::EmptyRoom) } | ||||||
|                     _ => { room_types.push(RoomType::BasicRoom) } |                     _ => { room_types.push(RoomType::BasicRoom) } | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|             room_types.shuffle(&mut rng); |             room_types.shuffle(&mut rng); | ||||||
| 
 | 
 | ||||||
|         let mut rooms = [[Room::new(); ROOMS_HORIZONTAL]; ROOMS_VERTICAL]; |             graph.clear(); | ||||||
|         for r in 0..ROOMS_VERTICAL { |             // place the rooms in the array an add nodes to the graph for every non empty room
 | ||||||
|             for c in 0..ROOMS_HORIZONTAL { |             for c in 0..ROOMS_VERTICAL { | ||||||
|                 rooms[r][c].kind = room_types.pop().unwrap(); |                 for r in 0..ROOMS_HORIZONTAL { | ||||||
|                 if rooms[r][c].kind != RoomType::EmptyRoom { |                     rooms[c][r].kind = room_types.pop().unwrap(); | ||||||
|                     let width = rng.gen_range(3..6); |                     if rooms[c][r].kind != RoomType::EmptyRoom { | ||||||
|                     let height = rng.gen_range(3..5); |                         rooms[c][r].random(&mut rng); | ||||||
|                     rooms[r][c].width = width; |                         graph.add_node((c, r)); | ||||||
|                     rooms[r][c].height = height; |  | ||||||
|                     rooms[r][c].offset_x = rng.gen_range(0..(ROOM_WIDTH - width)); |  | ||||||
|                     rooms[r][c].offset_y = rng.gen_range(0..(ROOM_HEIGHT - height)); |  | ||||||
|                     } |                     } | ||||||
|                 if r == ROOMS_HORIZONTAL - 1 || c == ROOMS_HORIZONTAL - 1 { |  | ||||||
|                     continue; |  | ||||||
|                 } |  | ||||||
|                 match rng.gen_range(0..3) { |  | ||||||
|                     0 => { rooms[r][c].connection_down = true } |  | ||||||
|                     1 => { rooms[r][c].connection_right = true } |  | ||||||
|                     2 => { |  | ||||||
|                         rooms[r][c].connection_down = true; |  | ||||||
|                         rooms[r][c].connection_down = true |  | ||||||
|                     } |  | ||||||
|                     _ => {} |  | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         let mut unconnected_rooms = 0;//ROOMS_HORIZONTAL * ROOMS_VERTICAL - 1;
 |         // add edges to the graph connecting each room to all of its neighbours (max 4 of them)
 | ||||||
|         rooms[0][0].connected = true; |         for c in 0..ROOMS_VERTICAL { | ||||||
|         for r in 0..ROOMS_VERTICAL { |             for r in 0..ROOMS_HORIZONTAL { | ||||||
|             for c in 0..ROOMS_HORIZONTAL { |                 if rooms[c][r].kind == RoomType::EmptyRoom { | ||||||
|                 if !rooms[r][c].connected { |  | ||||||
|                     continue; |                     continue; | ||||||
|                 } |                 } | ||||||
|                 if rooms[r][c].connection_right { |                 let src_index = graph.node_indices().find(|i| graph[*i] == (c, r)).unwrap(); | ||||||
|                     rooms[r][c + 1].connected = true; |                 for r_1 in r + 1..ROOMS_HORIZONTAL { | ||||||
|  |                     if rooms[c][r_1].kind != RoomType::EmptyRoom { | ||||||
|  |                         let tgt_index = graph.node_indices().find(|i| graph[*i] == (c, r_1)).unwrap(); | ||||||
|  |                         // todo use random weight for edge
 | ||||||
|  |                         graph.add_edge(src_index, tgt_index, 1); | ||||||
|  |                         break; | ||||||
|                     } |                     } | ||||||
|                 if rooms[r][c].connection_down { |                 } | ||||||
|                     rooms[r + 1][c].connected = true; |                 for c_1 in c + 1..ROOMS_VERTICAL { | ||||||
|  |                     if rooms[c_1][r].kind != RoomType::EmptyRoom { | ||||||
|  |                         let tgt_index = graph.node_indices().find(|i| graph[*i] == (c_1, r)).unwrap(); | ||||||
|  |                         // todo use random weight for edge
 | ||||||
|  |                         graph.add_edge(src_index, tgt_index, 1); | ||||||
|  |                         break; | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|         println!("Unconnected: {}", unconnected_rooms); |         } | ||||||
|  | 
 | ||||||
|  |         // calculate a minimum spanning tree
 | ||||||
|  |         let mst: Graph<(usize, usize), u16, petgraph::Undirected> = Graph::from_elements(min_spanning_tree(&graph)); | ||||||
|  |         for edge in mst.raw_edges() { | ||||||
|  |             let src = mst[edge.source()]; | ||||||
|  |             let tgt = mst[edge.target()]; | ||||||
|  |             println!("{:?} {:?}", src, tgt); | ||||||
|  |         } | ||||||
|  |         // println!("Level {}\n{}", level, Dot::new(&mst));
 | ||||||
| 
 | 
 | ||||||
|         LevelGenerator { |         LevelGenerator { | ||||||
|             rooms |             rooms | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |     /// Verifies that for a given matrix of rooms each room has at least one other room in the
 | ||||||
|  |     /// same row or column.
 | ||||||
|  |     fn rooms_connectable(rooms: &[[Room; ROOMS_HORIZONTAL]; ROOMS_VERTICAL]) -> bool { | ||||||
|  |         for c in 0..ROOMS_VERTICAL { | ||||||
|  |             for r in 0..ROOMS_HORIZONTAL { | ||||||
|  |                 if rooms[c][r].kind != RoomType::EmptyRoom { | ||||||
|  |                     let mut connected = 0; | ||||||
|  |                     for c1 in 0..ROOMS_VERTICAL { | ||||||
|  |                         if rooms[c1][r].kind != RoomType::EmptyRoom { | ||||||
|  |                             connected += 1; | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                     for r1 in 0..ROOMS_HORIZONTAL { | ||||||
|  |                         if rooms[c][r1].kind != RoomType::EmptyRoom { | ||||||
|  |                             connected += 1; | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                     if connected <= 2 { | ||||||
|  |                         return false; | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         return true; | ||||||
|  |     } | ||||||
|     pub fn render(&self) -> Level { |     pub fn render(&self) -> Level { | ||||||
|         let mut rng = rand::thread_rng(); |         let mut rng = rand::thread_rng(); | ||||||
|         let mut s = [[LevelElement::Wall; 1 + ROOMS_HORIZONTAL * ROOM_HEIGHT]; 1 + ROOMS_VERTICAL * ROOM_WIDTH]; |         let mut s = [[LevelElement::Wall; 1 + ROOMS_HORIZONTAL * ROOM_HEIGHT]; 1 + ROOMS_VERTICAL * ROOM_WIDTH]; | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user