#!/usr/bin/env python3 """Generate map images from map files""" import argparse import pathlib import os from typing import Sequence, Tuple import yaml import cv2 # type: ignore import numpy MAP_ROOT = pathlib.Path(os.getcwd()) TILES_DIR = MAP_ROOT / '../assets/tiles' SHOW_MAP = False Row = Sequence[str] Tiles = Sequence[Row] def get_tile_path(tile: str) -> pathlib.Path: """Return the corresponding image file name for a tile""" return TILES_DIR / f'{tile.lower().replace(" ", "_")}.png' def is_connected_to(tiles: Tiles, x: int, y: int, kind=None) -> Tuple[bool, bool, bool, bool]: is_kind = lambda tile: tile == kind if kind else tile != 'neutral' row = tiles[y] left = x > 0 and is_kind(row[x - 1]) right = x < len(row) - 1 and is_kind(row[x + 1]) above = y > 0 and is_kind(tiles[y - 1][x]) below = y < len(tiles) - 1 and is_kind(tiles[y + 1][x]) return left, right, above, below def count_connections(left: bool, right: bool, above: bool, below: bool) -> int: return len([b for b in [left, right, above, below] if b is True]) def find_street_tile(tiles: Tiles, x: int, y: int): # find street continuations left, right, above, below = is_connected_to(tiles, x, y, kind='street') connections = count_connections(left, right, above, below) if connections == 0: # This street is not connected to another street -> # check any other non neutral tiles left, right, above, below = is_connected_to(tiles, x, y, kind=None) connections = count_connections(left, right, above, below) # This street is not connected to anything. Seams odd! # Use a 4 way street if connections == 0: tile_img = cv2.imread(str(get_tile_path('street_4'))) # straight lines elif connections == 1 or (connections == 2 and ((left and right) or (above and below))): tile_img = cv2.imread(str(get_tile_path('street'))) if above or below: tile_img = numpy.rot90(tile_img) # elbow elif connections == 2: # normal orientation above and right tile_img = cv2.imread(str(get_tile_path('street_2'))) if right and below: tile_img = numpy.rot90(tile_img, 3) elif left and below: tile_img = numpy.rot90(tile_img, 2) elif above and left: tile_img = numpy.rot90(tile_img) elif connections == 3: # normal orientation left above right tile_img = cv2.imread(str(get_tile_path('street_3'))) if above and right and below: tile_img = numpy.rot90(tile_img, 3) elif left and below and right: tile_img = numpy.rot90(tile_img, 2) elif below and left and above: tile_img = numpy.rot90(tile_img) elif connections == 4: tile_img = cv2.imread(str(get_tile_path('street_4'))) return tile_img def find_wall_tile(tiles: Sequence[Sequence[str]], x: int, y: int): # find wall continuations left, right, above, below = is_connected_to(tiles, x, y, kind='wall') connections = count_connections(left, right, above, below) if connections == 0: # This wall is not connected to another wall -> # check any other non neutral tiles left, right, above, below = is_connected_to(tiles, x, y, kind=None) connections = count_connections(left, right, above, below) # This wall is not connected to anything. Seams odd! # Use default horizontal wall if connections == 0: tile_img = cv2.imread(str(get_tile_path('wall'))) # straight walls elif connections == 1 or (connections == 2 and ((left and right) or (above and below))): if above or below: tile_img = cv2.imread(str(get_tile_path('wall_h'))) else: tile_img = cv2.imread(str(get_tile_path('wall'))) # elbow elif connections == 2: if above: tile_img = cv2.imread(str(get_tile_path('wall_elbow_up'))) else: tile_img = cv2.imread(str(get_tile_path('wall_elbow_down'))) if left: tile_img = cv2.flip(tile_img, 1) elif connections == 3 or connections == 4: # We don't have wall tiles for those structures yet use default vertical wall tile_img = cv2.imread(str(get_tile_path('wall'))) return tile_img def find_gate_tile(tiles: Sequence[Sequence[str]], x: int, y: int): # find wall continuations left, right, above, below = is_connected_to(tiles, x, y, kind='wall') connections = count_connections(left, right, above, below) if connections == 0: # This gate is not connected to a wall -> check towers next left, right, above, below = is_connected_to(tiles, x, y, kind='tower') connections = count_connections(left, right, above, below) if connections == 0: # If the gate is not connected to any fortification structure use default tile return cv2.imread(str(get_tile_path('gate'))) # straight gates are the only special gates we have if connections == 2 and ((left and right) or (above and below)): if above or below: tile_img = cv2.imread(str(get_tile_path('gate'))) else: tile_img = cv2.imread(str(get_tile_path('gate_wall_h'))) else: tile_img = cv2.imread(str(get_tile_path('gate'))) return tile_img def generate_img(map_path: pathlib.Path): """Generate a image from a map file""" with open(map_path, 'r', encoding="utf8") as map_file: map_definition = yaml.full_load(map_file) # remove the trailing new line map_string = map_definition['map'][:-1] # ensure that all keys are actually strings symbols = {str(k): v for k, v in map_definition['symbols'].items()} # build up tiles tiles = [] for line in map_string.splitlines(): row = [] for symbol in line: tile = "neutral" if symbol != ' ': if symbol not in symbols: print(f'WARNING unknown symbol "{symbol}"') else: tile = symbols[symbol] row.append(tile) tiles.append(row) rows_imgs = [] for y, row in enumerate(tiles): tile_imgs = [] for x, tile in enumerate(row): # find the right street and rotation if tile == 'street': tile_img = find_street_tile(tiles, x, y) elif tile == 'wall': tile_img = find_wall_tile(tiles, x, y) elif tile == 'gate': tile_img = find_gate_tile(tiles, x, y) else: tile_img = cv2.imread(str(get_tile_path(tile))) tile_imgs.append(tile_img) row_img = cv2.hconcat(tile_imgs) rows_imgs.append(row_img) map_img = cv2.vconcat(rows_imgs) if SHOW_MAP: cv2.imshow(f'{map_path.stem}.png', map_img) cv2.waitKey(0) cv2.imwrite(f'{map_path.stem}.png', map_img) def main(map_input): map_path = pathlib.Path(map_input) map_files = [map_path] if map_path.is_dir(): map_files = map_path.glob('*.yml') for m in map_files: generate_img(m) if __name__ == '__main__': parser = argparse.ArgumentParser( description='generate latex standalone cards') parser.add_argument('map_input', help='map file or directory containing map files') parser.add_argument('--map-root', help='path to the map root directory') parser.add_argument('--show-map', help='show the generated map', action='store_true') args = parser.parse_args() if args.map_root: MAP_ROOT = pathlib.Path(args.latex_root) TILES_DIR = MAP_ROOT / '../assets/tiles' if args.show_map: SHOW_MAP = True main(args.map_input)