aboutsummaryrefslogtreecommitdiff
path: root/scripts/generate_map_img.py
blob: f3b3671d053834b16b695dedbc8b736d836e5d9b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
#!/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
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') 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)


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

    map_path = pathlib.Path(args.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)