creator.py
6.1 kB · python · 185 lines
1import math2from typing import List, Tuple3from mrlypy.core.state import choice, bool, sample, shuffle4from .config import Config5from .enums import Design, Group6from .models import Tile7from .randomizer import random_design, random_rotation89NUMBERS = [3, 5, 7, 9, 11]1011# MATH HELPERS1213def generals(min_size: int, max_size: int) -> List[int]:14 return [n for n in range(3, max_size + 1, 2) if min_size <= n <= max_size]1516def powers(min_size: int, max_size: int) -> List[Tuple[int, int]]:17 options = []18 for n in NUMBERS:19 try:20 min_level = math.ceil(math.log(min_size, n)) if min_size > 1 else 221 max_level = math.floor(math.log(max_size, n))22 min_level = max(2, min_level)23 if min_level <= max_level:24 for level in range(min_level, max_level + 1):25 options.append((n, level))26 except ValueError:27 continue28 return options2930def products(min_size: int, max_size: int, count: int) -> List[Tuple[int, ...]]:31 if count < 1:32 return []33 def find_recursive(current_min: float, current_max: float, remaining_count: int) -> List[List[int]]:34 if current_min > current_max:35 return []36 if remaining_count == 1:37 min_n = math.ceil(current_min)38 if min_n < 3:39 min_n = 340 if min_n % 2 == 0:41 min_n += 142 results = []43 for n in range(int(min_n), int(current_max) + 1, 2):44 results.append([n])45 return results46 options = []47 for n in NUMBERS:48 new_min = current_min / n49 new_max = current_max / n50 sub_options = find_recursive(new_min, new_max, remaining_count - 1)51 for sub_option in sub_options:52 options.append([n] + sub_option)53 return options54 return [tuple(opt) for opt in find_recursive(float(min_size), float(max_size), count)]5556# TILE CREATORS5758def general_tile(tile: Tile, config: Config) -> Tile | None:59 tile.group = Group.GENERAL60 tile.design = [random_design(config.designs)]61 possible_numbers = generals(config.min_size, config.max_size)62 if not possible_numbers:63 return None64 tile.number = [choice(possible_numbers)]65 tile.level = [1]66 tile.rotation = [random_rotation(tile.design[0])]67 size = tile.number[0]68 tile.unit_size(size, size)69 tile.mask = tile.number[0]70 return tile7172def fractal_tile(tile: Tile, config: Config) -> Tile | None:73 tile.group = Group.FRACTAL74 tile.design = [random_design(config.designs)]75 possible_options = powers(config.min_size, config.max_size)76 if not possible_options:77 return None78 n, level = choice(possible_options)79 tile.number = [n]80 tile.level = [level]81 tile.rotation = [random_rotation(tile.design[0])]82 size = tile.number[0] ** tile.level[0]83 tile.unit_size(size, size)84 tile.mask = tile.number[0]85 return tile8687def magic_tile(tile: Tile, config: Config) -> Tile | None:88 tile.group = Group.MAGIC89 counts = {}90 counts[2] = products(config.min_size, config.max_size, 2)91 counts[3] = products(config.min_size, config.max_size, 3)92 valid_counts = [k for k, v in counts.items() if v]93 if not valid_counts:94 return None95 count = choice(valid_counts)96 tile.design = [random_design(config.designs) for _ in range(count)]97 options = counts[count]98 magic_options = []99 fractal_options = []100 is_same_design = len(set(tile.design)) == 1101 for option in options:102 is_same_number = len(set(option)) == 1103 if is_same_design and is_same_number:104 fractal_options.append(option)105 else:106 magic_options.append(option)107 if not magic_options:108 if not fractal_options:109 return None110 picked = choice(fractal_options)111 else:112 picked = choice(magic_options)113 tile.number = list(picked)114 tile.level = [1] * count115 tile.rotation = [random_rotation(d) for d in tile.design]116 size = 1117 for n in tile.number:118 size *= n119 tile.unit_size(size, size)120 tile.mask = tile.number[0]121 return tile122123def special_tile(tile: Tile, config: Config) -> Tile | None:124 tile.group = Group.SPECIAL125 tile.design = [random_design(config.designs)]126 possible_options = products(config.min_size, config.max_size, 2)127 if not possible_options:128 return None129 mask, n = choice(possible_options)130 tile.mask = mask131 tile.number = [n]132 tile.level = [1]133 tile.rotation = [random_rotation(tile.design[0])]134 size = tile.mask * tile.number[0]135 tile.unit_size(size, size)136 tile.flip = bool()137 return tile138139def mosaic_tile(tile: Tile, config: Config) -> Tile | None:140 tile.group = Group.MOSAIC141 choices = list(Design) if config.designs is None else config.designs142 if len(choices) < 3:143 return None144 tile.design = sample(choices, 3)145 possible_options = products(config.min_size, config.max_size, 2)146 if not possible_options:147 return None148 mask, n = choice(possible_options)149 tile.mask = mask150 tile.number = [n, n, n]151 tile.level = [1, 1, 1]152 tile.rotation = [random_rotation(d) for d in tile.design]153 size = tile.mask * n154 tile.unit_size(size, size)155 return tile156157TILE_FACTORY = {158 Group.GENERAL: general_tile,159 Group.FRACTAL: fractal_tile,160 Group.MAGIC: magic_tile,161 Group.SPECIAL: special_tile,162 Group.MOSAIC: mosaic_tile,163}164165# CREATE166167def create(config: Config) -> Tile:168 candidate = None169 all_groups = list(Group)170 possible_groups = all_groups if config.groups is None else list(config.groups)171 shuffle(possible_groups)172 for group in possible_groups:173 candidate = TILE_FACTORY[group](Tile(), config)174 if candidate:175 break176 if not candidate:177 raise ValueError("Could not generate a tile with the given size constraints.")178 tile = candidate179 if tile.design:180 if config.anti is None:181 tile.anti = [bool() for _ in range(len(tile.design))]182 else:183 tile.anti = [config.anti] * len(tile.design)184 tile.invert = bool() if config.invert is None else config.invert185 return tile