251 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
		
		
			
		
	
	
			251 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
|   | from .tileset import TileSet, solid_tiles, open_tiles, vertical_edge_tiles, horizontal_edge_tiles | ||
|  | from .map import Map | ||
|  | from typing import Set | ||
|  | import random | ||
|  | 
 | ||
|  | 
 | ||
|  | class ContradictionException(Exception): | ||
|  |     def __init__(self, x, y): | ||
|  |         self.x = x | ||
|  |         self.y = y | ||
|  | 
 | ||
|  | 
 | ||
|  | class Cell: | ||
|  |     def __init__(self, x, y, tileset: TileSet, options: Set[int]): | ||
|  |         self.x = x | ||
|  |         self.y = y | ||
|  |         self.tileset = tileset | ||
|  |         self.init_options = options | ||
|  |         self.options = None | ||
|  |         self.result = None | ||
|  | 
 | ||
|  |     def __set_new_options(self, new_options): | ||
|  |         if new_options != self.options: | ||
|  |             if self.result is not None: | ||
|  |                 raise ContradictionException(self.x, self.y) | ||
|  |             if not new_options: | ||
|  |                 raise ContradictionException(self.x, self.y) | ||
|  |             self.options = new_options | ||
|  |             return True | ||
|  |         return False | ||
|  | 
 | ||
|  |     def update_options_up(self, cell: "Cell") -> bool: | ||
|  |         new_options = set() | ||
|  |         for tile in cell.options: | ||
|  |             new_options.update(cell.tileset.tiles[tile].up) | ||
|  |         new_options.intersection_update(self.options) | ||
|  |         if (self.y % 8) == 7: | ||
|  |             if cell.options.issubset(solid_tiles): | ||
|  |                 new_options.intersection_update(solid_tiles) | ||
|  |             if cell.options.issubset(open_tiles): | ||
|  |                 new_options.intersection_update(open_tiles) | ||
|  |         return self.__set_new_options(new_options) | ||
|  | 
 | ||
|  |     def update_options_right(self, cell: "Cell") -> bool: | ||
|  |         new_options = set() | ||
|  |         for tile in cell.options: | ||
|  |             new_options.update(cell.tileset.tiles[tile].right) | ||
|  |         new_options.intersection_update(self.options) | ||
|  |         if (self.x % 10) == 0: | ||
|  |             if cell.options.issubset(solid_tiles): | ||
|  |                 new_options.intersection_update(solid_tiles) | ||
|  |             if cell.options.issubset(open_tiles): | ||
|  |                 new_options.intersection_update(open_tiles) | ||
|  |         return self.__set_new_options(new_options) | ||
|  | 
 | ||
|  |     def update_options_down(self, cell: "Cell") -> bool: | ||
|  |         new_options = set() | ||
|  |         for tile in cell.options: | ||
|  |             new_options.update(cell.tileset.tiles[tile].down) | ||
|  |         new_options.intersection_update(self.options) | ||
|  |         if (self.y % 8) == 0: | ||
|  |             if cell.options.issubset(solid_tiles): | ||
|  |                 new_options.intersection_update(solid_tiles) | ||
|  |             if cell.options.issubset(open_tiles): | ||
|  |                 new_options.intersection_update(open_tiles) | ||
|  |         return self.__set_new_options(new_options) | ||
|  | 
 | ||
|  |     def update_options_left(self, cell: "Cell") -> bool: | ||
|  |         new_options = set() | ||
|  |         for tile in cell.options: | ||
|  |             new_options.update(cell.tileset.tiles[tile].left) | ||
|  |         new_options.intersection_update(self.options) | ||
|  |         if (self.x % 10) == 9: | ||
|  |             if cell.options.issubset(solid_tiles): | ||
|  |                 new_options.intersection_update(solid_tiles) | ||
|  |             if cell.options.issubset(open_tiles): | ||
|  |                 new_options.intersection_update(open_tiles) | ||
|  |         return self.__set_new_options(new_options) | ||
|  | 
 | ||
|  |     def __repr__(self): | ||
|  |         return f"Cell<{self.options}>" | ||
|  | 
 | ||
|  | 
 | ||
|  | class WFCMap: | ||
|  |     def __init__(self, the_map: Map, tilesets, *, step_callback=None): | ||
|  |         self.cell_data = {} | ||
|  |         self.on_step = step_callback | ||
|  |         self.w = the_map.w * 10 | ||
|  |         self.h = the_map.h * 8 | ||
|  | 
 | ||
|  |         for y in range(self.h): | ||
|  |             for x in range(self.w): | ||
|  |                 tileset = tilesets[the_map.get(x//10, y//8).tileset_id] | ||
|  |                 new_cell = Cell(x, y, tileset, tileset.all.copy()) | ||
|  |                 self.cell_data[(new_cell.x, new_cell.y)] = new_cell | ||
|  |         for y in range(self.h): | ||
|  |             self.cell_data[(0, y)].init_options.intersection_update(solid_tiles) | ||
|  |             self.cell_data[(self.w-1, y)].init_options.intersection_update(solid_tiles) | ||
|  |         for x in range(self.w): | ||
|  |             self.cell_data[(x, 0)].init_options.intersection_update(solid_tiles) | ||
|  |             self.cell_data[(x, self.h-1)].init_options.intersection_update(solid_tiles) | ||
|  | 
 | ||
|  |         for x in range(0, self.w, 10): | ||
|  |             for y in range(self.h): | ||
|  |                 self.cell_data[(x, y)].init_options.intersection_update(vertical_edge_tiles) | ||
|  |         for x in range(9, self.w, 10): | ||
|  |             for y in range(self.h): | ||
|  |                 self.cell_data[(x, y)].init_options.intersection_update(vertical_edge_tiles) | ||
|  |         for y in range(0, self.h, 8): | ||
|  |             for x in range(self.w): | ||
|  |                 self.cell_data[(x, y)].init_options.intersection_update(horizontal_edge_tiles) | ||
|  |         for y in range(7, self.h, 8): | ||
|  |             for x in range(self.w): | ||
|  |                 self.cell_data[(x, y)].init_options.intersection_update(horizontal_edge_tiles) | ||
|  | 
 | ||
|  |         for sy in range(the_map.h): | ||
|  |             for sx in range(the_map.w): | ||
|  |                 the_map.get(sx, sy).room_type.seed(self, sx*10, sy*8) | ||
|  | 
 | ||
|  |         for sy in range(the_map.h): | ||
|  |             for sx in range(the_map.w): | ||
|  |                 room = the_map.get(sx, sy) | ||
|  |                 room.edge_left.seed(self, sx * 10, sy * 8) | ||
|  |                 room.edge_right.seed(self, sx * 10 + 9, sy * 8) | ||
|  |                 room.edge_up.seed(self, sx * 10, sy * 8) | ||
|  |                 room.edge_down.seed(self, sx * 10, sy * 8 + 7) | ||
|  | 
 | ||
|  |     def initialize(self): | ||
|  |         for y in range(self.h): | ||
|  |             for x in range(self.w): | ||
|  |                 cell = self.cell_data[x, y] | ||
|  |                 cell.options = cell.init_options.copy() | ||
|  |         if self.on_step: | ||
|  |             self.on_step(self) | ||
|  |         propegation_set = set() | ||
|  |         for y in range(self.h): | ||
|  |             for x in range(self.w): | ||
|  |                 propegation_set.add((x, y)) | ||
|  |         self.propegate(propegation_set) | ||
|  |         for y in range(self.h): | ||
|  |             for x in range(self.w): | ||
|  |                 cell = self.cell_data[x, y] | ||
|  |                 cell.init_options = cell.options.copy() | ||
|  | 
 | ||
|  |     def clear(self): | ||
|  |         for y in range(self.h): | ||
|  |             for x in range(self.w): | ||
|  |                 cell = self.cell_data[(x, y)] | ||
|  |                 if cell.result is None: | ||
|  |                     cell.options = cell.init_options.copy() | ||
|  | 
 | ||
|  |         propegation_set = set() | ||
|  |         for y in range(self.h): | ||
|  |             for x in range(self.w): | ||
|  |                 cell = self.cell_data[(x, y)] | ||
|  |                 if cell.result is not None: | ||
|  |                     propegation_set.add((x, y)) | ||
|  |         self.propegate(propegation_set) | ||
|  | 
 | ||
|  |     def random_pick(self, cell): | ||
|  |         pick_list = list(cell.options) | ||
|  |         if not pick_list: | ||
|  |             raise ContradictionException(cell.x, cell.y) | ||
|  |         freqs = {} | ||
|  |         if (cell.x - 1, cell.y) in self.cell_data and len(self.cell_data[(cell.x - 1, cell.y)].options) == 1: | ||
|  |             tile_id = next(iter(self.cell_data[(cell.x - 1, cell.y)].options)) | ||
|  |             for k, v in self.cell_data[(cell.x - 1, cell.y)].tileset.tiles[tile_id].right_freq.items(): | ||
|  |                 freqs[k] = freqs.get(k, 0) + v | ||
|  |         if (cell.x + 1, cell.y) in self.cell_data and len(self.cell_data[(cell.x + 1, cell.y)].options) == 1: | ||
|  |             tile_id = next(iter(self.cell_data[(cell.x + 1, cell.y)].options)) | ||
|  |             for k, v in self.cell_data[(cell.x + 1, cell.y)].tileset.tiles[tile_id].left_freq.items(): | ||
|  |                 freqs[k] = freqs.get(k, 0) + v | ||
|  |         if (cell.x, cell.y - 1) in self.cell_data and len(self.cell_data[(cell.x, cell.y - 1)].options) == 1: | ||
|  |             tile_id = next(iter(self.cell_data[(cell.x, cell.y - 1)].options)) | ||
|  |             for k, v in self.cell_data[(cell.x, cell.y - 1)].tileset.tiles[tile_id].down_freq.items(): | ||
|  |                 freqs[k] = freqs.get(k, 0) + v | ||
|  |         if (cell.x, cell.y + 1) in self.cell_data and len(self.cell_data[(cell.x, cell.y + 1)].options) == 1: | ||
|  |             tile_id = next(iter(self.cell_data[(cell.x, cell.y + 1)].options)) | ||
|  |             for k, v in self.cell_data[(cell.x, cell.y + 1)].tileset.tiles[tile_id].up_freq.items(): | ||
|  |                 freqs[k] = freqs.get(k, 0) + v | ||
|  |         if freqs: | ||
|  |             weights_list = [freqs.get(n, 1) for n in pick_list] | ||
|  |         else: | ||
|  |             weights_list = [cell.tileset.tiles[n].frequency for n in pick_list] | ||
|  |         return random.choices(pick_list, weights_list)[0] | ||
|  | 
 | ||
|  |     def build(self, start_x, start_y, w, h): | ||
|  |         cell_todo_list = [] | ||
|  |         for y in range(start_y, start_y + h): | ||
|  |             for x in range(start_x, start_x+w): | ||
|  |                 cell_todo_list.append(self.cell_data[(x, y)]) | ||
|  | 
 | ||
|  |         while cell_todo_list: | ||
|  |             cell_todo_list.sort(key=lambda c: len(c.options)) | ||
|  |             l0 = len(cell_todo_list[0].options) | ||
|  |             idx = 1 | ||
|  |             while idx < len(cell_todo_list) and len(cell_todo_list[idx].options) == l0: | ||
|  |                 idx += 1 | ||
|  |             idx = random.randint(0, idx - 1) | ||
|  |             cell = cell_todo_list[idx] | ||
|  |             if self.on_step: | ||
|  |                 self.on_step(self, cur=(cell.x, cell.y)) | ||
|  |             pick = self.random_pick(cell) | ||
|  |             cell_todo_list.pop(idx) | ||
|  |             cell.options = {pick} | ||
|  |             self.propegate({(cell.x, cell.y)}) | ||
|  | 
 | ||
|  |         for y in range(start_y, start_y + h): | ||
|  |             for x in range(start_x, start_x + w): | ||
|  |                 self.cell_data[(x, y)].result = next(iter(self.cell_data[(x, y)].options)) | ||
|  | 
 | ||
|  |     def propegate(self, propegation_set): | ||
|  |         while propegation_set: | ||
|  |             xy = next(iter(propegation_set)) | ||
|  |             propegation_set.remove(xy) | ||
|  | 
 | ||
|  |             cell = self.cell_data[xy] | ||
|  |             if not cell.options: | ||
|  |                 raise ContradictionException(cell.x, cell.y) | ||
|  |             x, y = xy | ||
|  |             if (x, y + 1) in self.cell_data and self.cell_data[(x, y + 1)].update_options_down(cell): | ||
|  |                 propegation_set.add((x, y + 1)) | ||
|  |             if (x + 1, y) in self.cell_data and self.cell_data[(x + 1, y)].update_options_right(cell): | ||
|  |                 propegation_set.add((x + 1, y)) | ||
|  |             if (x, y - 1) in self.cell_data and self.cell_data[(x, y - 1)].update_options_up(cell): | ||
|  |                 propegation_set.add((x, y - 1)) | ||
|  |             if (x - 1, y) in self.cell_data and self.cell_data[(x - 1, y)].update_options_left(cell): | ||
|  |                 propegation_set.add((x - 1, y)) | ||
|  | 
 | ||
|  |     def store_tile_data(self, the_map: Map): | ||
|  |         for sy in range(the_map.h): | ||
|  |             for sx in range(the_map.w): | ||
|  |                 tiles = [] | ||
|  |                 for y in range(8): | ||
|  |                     for x in range(10): | ||
|  |                         cell = self.cell_data[(x+sx*10, y+sy*8)] | ||
|  |                         if cell.result is not None: | ||
|  |                             tiles.append(cell.result) | ||
|  |                         elif len(cell.options) == 0: | ||
|  |                             tiles.append(1) | ||
|  |                         else: | ||
|  |                             tiles.append(2) | ||
|  |                 the_map.get(sx, sy).tiles = tiles | ||
|  | 
 | ||
|  |     def dump_option_count(self): | ||
|  |         for y in range(self.h): | ||
|  |             for x in range(self.w): | ||
|  |                 print(f"{len(self.cell_data[(x, y)].options):2x}", end="") | ||
|  |             print() | ||
|  |         print() |