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" | ||||
| dependencies = [ | ||||
|  "crossterm", | ||||
|  "petgraph", | ||||
|  "rand", | ||||
|  "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]] | ||||
| name = "getrandom" | ||||
| version = "0.2.11" | ||||
| @ -117,6 +130,16 @@ version = "0.4.1" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| 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]] | ||||
| name = "indoc" | ||||
| version = "2.0.4" | ||||
| @ -210,6 +233,16 @@ version = "1.0.14" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| 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]] | ||||
| name = "ppv-lite86" | ||||
| version = "0.2.17" | ||||
|  | ||||
| @ -9,3 +9,4 @@ edition = "2021" | ||||
| ratatui = "0.24.0" | ||||
| crossterm = "0.27.0" | ||||
| rand = "0.8.5" | ||||
| petgraph = "0.6.4" | ||||
|  | ||||
| @ -1,5 +1,10 @@ | ||||
| use rand::prelude::SliceRandom; | ||||
| 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}; | ||||
| 
 | ||||
| @ -15,9 +20,9 @@ struct Room { | ||||
|     pub offset_y: usize, | ||||
|     pub width: usize, | ||||
|     pub height: usize, | ||||
|     pub connection_down: bool, | ||||
|     pub connection_right: bool, | ||||
|     pub connected: bool, | ||||
|     // pub connection_down: bool,
 | ||||
|     // pub connection_right: bool,
 | ||||
|     // pub connected: bool,
 | ||||
| } | ||||
| 
 | ||||
| impl Room { | ||||
| @ -28,11 +33,20 @@ impl Room { | ||||
|             offset_y: 0, | ||||
|             width: 0, | ||||
|             height: 0, | ||||
|             connection_down: false, | ||||
|             connection_right: false, | ||||
|             connected: false, | ||||
|             // connection_down: false,
 | ||||
|             // connection_right: 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)] | ||||
| @ -42,74 +56,114 @@ pub struct LevelGenerator { | ||||
| 
 | ||||
| impl LevelGenerator { | ||||
|     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(); | ||||
|         for _ in room_types.len()..ROOMS_HORIZONTAL * ROOMS_VERTICAL { | ||||
|             match rng.gen_range(1..=6) { | ||||
|                 1..=3 => { room_types.push(RoomType::EmptyRoom) } | ||||
|                 _ => { room_types.push(RoomType::BasicRoom) } | ||||
|             } | ||||
|         } | ||||
|         room_types.shuffle(&mut rng); | ||||
| 
 | ||||
|         let mut rooms = [[Room::new(); ROOMS_HORIZONTAL]; ROOMS_VERTICAL]; | ||||
|         for r in 0..ROOMS_VERTICAL { | ||||
|             for c in 0..ROOMS_HORIZONTAL { | ||||
|                 rooms[r][c].kind = room_types.pop().unwrap(); | ||||
|                 if rooms[r][c].kind != RoomType::EmptyRoom { | ||||
|                     let width = rng.gen_range(3..6); | ||||
|                     let height = rng.gen_range(3..5); | ||||
|                     rooms[r][c].width = width; | ||||
|                     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)); | ||||
|         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 { | ||||
|                 match rng.gen_range(1..=6) { | ||||
|                     // TODO tune room type distribution
 | ||||
|                     1..=3 => { room_types.push(RoomType::EmptyRoom) } | ||||
|                     _ => { room_types.push(RoomType::BasicRoom) } | ||||
|                 } | ||||
|                 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 | ||||
|             } | ||||
|             room_types.shuffle(&mut rng); | ||||
| 
 | ||||
|             graph.clear(); | ||||
|             // place the rooms in the array an add nodes to the graph for every non empty room
 | ||||
|             for c in 0..ROOMS_VERTICAL { | ||||
|                 for r in 0..ROOMS_HORIZONTAL { | ||||
|                     rooms[c][r].kind = room_types.pop().unwrap(); | ||||
|                     if rooms[c][r].kind != RoomType::EmptyRoom { | ||||
|                         rooms[c][r].random(&mut rng); | ||||
|                         graph.add_node((c, r)); | ||||
|                     } | ||||
|                     _ => {} | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         let mut unconnected_rooms = 0;//ROOMS_HORIZONTAL * ROOMS_VERTICAL - 1;
 | ||||
|         rooms[0][0].connected = true; | ||||
|         for r in 0..ROOMS_VERTICAL { | ||||
|             for c in 0..ROOMS_HORIZONTAL { | ||||
|                 if !rooms[r][c].connected { | ||||
|         // add edges to the graph connecting each room to all of its neighbours (max 4 of them)
 | ||||
|         for c in 0..ROOMS_VERTICAL { | ||||
|             for r in 0..ROOMS_HORIZONTAL { | ||||
|                 if rooms[c][r].kind == RoomType::EmptyRoom { | ||||
|                     continue; | ||||
|                 } | ||||
|                 if rooms[r][c].connection_right { | ||||
|                     rooms[r][c + 1].connected = true; | ||||
|                 let src_index = graph.node_indices().find(|i| graph[*i] == (c, r)).unwrap(); | ||||
|                 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 { | ||||
|             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 { | ||||
|         let mut rng = rand::thread_rng(); | ||||
|         let mut s = [[LevelElement::Wall; 1 + ROOMS_HORIZONTAL * ROOM_HEIGHT]; 1 + ROOMS_VERTICAL * ROOM_WIDTH]; | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user