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