Compare commits

..

No commits in common. "b03f4c1193f37cdd3218c3337f89aab5fb5a2456" and "cdbb2336221e5c9cb0211f9f27b573a81b9ef6d6" have entirely different histories.

9 changed files with 73 additions and 216 deletions

View File

@ -1,41 +0,0 @@
# ..... 1
# ..... ..... 2
# ..........D....
# ..... ..D..
# .
# .
# S.........
# . .
# . .
# ..... 3 .L.... 4
# ..... ......
# ..... ......
# ....v.
#
# .^.........
# ...,,,,,...
# ...,___,...
# ...,,,,,...
# ........... 5
#
[map]
name = "Five Room Dungeon"
description = "An example dungeon"
author = "evilchili <hello@evilchi.li>"
license = "CC0"
[1]
description = "First Room"
[2]
description = "Second Room"
[3]
description = "Third Room"
[4]
description = "Forth Room"
[5]
description = "Fifth Room"

View File

@ -0,0 +1,20 @@
..... 1
..... ..... 2
..........D....
..... ..D..
.
.
S.........
. .
. .
..... 3 .L.... 4
..... ......
..... ......
....v.
.^.........
...,,,,,...
...,___,...
...,,,,,...
........... 5

View File

@ -5,9 +5,6 @@ from io import StringIO
from pathlib import Path from pathlib import Path
from textwrap import indent from textwrap import indent
from typing import List, Union, get_type_hints from typing import List, Union, get_type_hints
from types import SimpleNamespace
import tomllib
from PIL import Image from PIL import Image
@ -31,14 +28,14 @@ class BattleMap(BattleMapType):
Example Usage: Example Usage:
>>> console = TileSetManager().load("colorized") >>> console = TileSetManager().load("colorized")
>>> bmap = BattleMap("input.txt", tileset=console) >>> bmap = BattleMap(name="Example Map", source="input.txt", tileset=console)
>>> print(bmap.as_string()) >>> print(bmap.as_string())
""" """
name: str = ""
source: Union[StringIO, Path] = None source: Union[StringIO, Path] = None
map_string: str = "" source_data: str = ""
metadata: SimpleNamespace = None
tileset: TileSet = None tileset: TileSet = None
width: int = 0 width: int = 0
height: int = 0 height: int = 0
@ -54,37 +51,16 @@ class BattleMap(BattleMapType):
except AttributeError: except AttributeError:
data = self.source.read() data = self.source.read()
data = data.strip("\n") data = data.strip("\n")
if not self.validate_source_data(data):
map_data = "" return
metadata = "" self.source_data = data
in_map = True
for line in data.splitlines():
line = line.lstrip(" ")
if in_map:
if line and line[0] == "#":
map_data += line[1:] + "\n"
continue
in_map = False
metadata += line + "\n"
self.validate_map_string(map_data)
self.map_string = map_data
self.metadata = SimpleNamespace(**tomllib.loads(metadata))
# invalidate the cache # invalidate the cache
if hasattr(self, "grid"): if hasattr(self, "grid"):
del self.grid del self.grid
if hasattr(self, "header"):
del self.header
if hasattr(self, "footer"):
del self.footer
if hasattr(self, "legend"):
del self.legend
if hasattr(self, "coordinates"):
del self.coordinates
return self.grid return self.grid
def validate_map_string(self, data: str) -> bool: def validate_source_data(self, data: str) -> bool:
""" """
Return True if every terrain type in the input data has a corresponding tile in the current tile set. Return True if every terrain type in the input data has a corresponding tile in the current tile set.
""" """
@ -105,7 +81,7 @@ class BattleMap(BattleMapType):
Returns an Image instance. Returns an Image instance.
""" """
if get_type_hints(self.tileset.render_grid)["return"] != Image: if get_type_hints(self.tileset.render_grid)["return"] != Image:
raise NotImplementedError("Tile set does not support image rendering.") raise NotImplementedError(f"Tile set does not support image rendering.")
return self.tileset.render_grid(grid=self.grid, width=self.width, height=self.height) return self.tileset.render_grid(grid=self.grid, width=self.width, height=self.height)
def as_string(self) -> str: def as_string(self) -> str:
@ -119,87 +95,38 @@ class BattleMap(BattleMapType):
Return the current map's Grid instance. Return the current map's Grid instance.
""" """
matrix = [] matrix = []
for line in self.map_string.splitlines(): for line in self.source_data.splitlines():
matrix.append([self.tileset.get(char) for char in line]) matrix.append([self.tileset.get(char) for char in line])
self.width = max([len(line) for line in matrix]) self.width = max([len(line) for line in matrix])
self.height = len(matrix) self.height = len(matrix)
return Grid(data=matrix) return Grid(data=matrix)
@cached_property @property
def header(self) -> str: def title(self) -> str:
lines = [f"BattleMap: {self.metadata.map['name']} ({self.width}x{self.height})"] return f"BattleMap: {self.name} ({self.width} x {self.height}, {self.width * self.height * 5}sq.ft.)"
lines.append(('-' * len(lines[0])))
for key, value in self.metadata.map.items():
if key in ("name", "description"):
continue
lines.append(f"{key.title()}: {value}")
return "\n".join(lines)
@cached_property @property
def footer(self) -> str:
lines = []
if 'description' in self.metadata.map:
lines.append(f"{self.metadata.map['description']}")
lines.append("")
lines.append("Legend:")
lines.append(self.legend)
return "\n".join(lines)
@cached_property
def legend(self) -> str: def legend(self) -> str:
output = "" output = ""
locations = False locations = 0
for char in sorted(set(list(self.map_string)), key=str.lower): for char in sorted(set(list(self.source_data)), key=str.lower):
if char in self.tileset.config.legend: if char in self.tileset.config.legend:
if char in "0123456789": if char in '0123456789':
locations = True locations = max(locations, int(char))
continue
output += f"{char} - {self.tileset.config.legend[char]}\n" output += f"{char} - {self.tileset.config.legend[char]}\n"
if locations: if locations:
output += "0-9 - locations" location_key = "1" if locations == 1 else f"1-{locations}"
output += f"{location_key} - location"
width = len(location_key)
justified = "" justified = ""
for line in output.splitlines(): for line in output.splitlines():
(key, sep, desc) = line.partition(" - ") (key, sep, desc) = line.partition(" - ")
justified += f"{key.rjust(3, ' ')}{sep}{desc}\n" justified += f"{key.rjust(width, ' ')}{sep}{desc}\n"
output = "".join(justified) output = "".join(justified)
return output return output
@cached_property
def top_coordinates(self) -> str:
row = next(row for row in self.grid.data if len(row) == self.width)
max_len = max([len(cell.x_coordinate) for cell in row])
lines = list(("" for i in range(max_len)))
for pos in row:
for i in range(len(pos.x_coordinate)):
lines[max_len - i - 1] += pos.x_coordinate[i]
return "\n".join((line.rjust(len(row), " ") for line in lines))
@cached_property
def left_coordinates(self) -> List[str]:
return [row[0].y_coordinate if row else "" for row in self.grid.data]
def __str__(self) -> str: def __str__(self) -> str:
return self.as_string() return self.as_string()
def __repr__(self) -> str: def __repr__(self) -> str:
lines = "" return f"\n{self.title}\n\n{indent(str(self), ' ')}\n\nLegend:\n{indent(self.legend, ' ')}"
left_coords = self.left_coordinates
i = 0
for line in str(self).splitlines():
lines += f"{left_coords[i].rjust(2, ' ')}{line}".ljust(self.width, " ") + "\n"
i = i + 1
top_break = "" + ("" * (self.width + 2)) + ""
bot_break = "" + ("" * (self.width + 2)) + ""
return "\n".join(
[
"",
self.header,
"",
indent(self.top_coordinates, " " * 5),
indent(top_break, " " * 3),
lines.rstrip(),
indent(bot_break, " " * 3),
"",
indent(self.footer, " ")
]
)

View File

@ -71,14 +71,14 @@ def render(
def render_map( def render_map(
source: typer.FileText = INSTALL_DIR / "examples" / "five_room_dungeon.txt", source: typer.FileText = INSTALL_DIR / "examples" / "five_room_dungeon.txt",
outfile: Union[Path, None] = None, outfile: Union[Path, None] = None,
tileset: str = "colorized", tileset: str = 'colorized'
): ):
manager = app_state["tileset_manager"] manager = app_state["tileset_manager"]
if tileset not in manager.available: if tileset not in manager.available:
raise RuntimeError(f"Could not locate the tile set {tileset} in {manager.config_dir}.") raise RuntimeError(f"Could not locate the tile set {tileset} in {manager.config_dir}.")
try: try:
bmap = battlemap.BattleMap(source=source, tileset=manager.load(tileset)) bmap = battlemap.BattleMap(name=source.name, source=source, tileset=manager.load(tileset))
bmap.load() bmap.load()
except battlemap.UnsupportedTileException as e: except battlemap.UnsupportedTileException as e:
logging.error(e) logging.error(e)

View File

@ -1,18 +1,22 @@
import json
from types import SimpleNamespace from types import SimpleNamespace
from collections import defaultdict
import json
terrain_map = { terrain_map = {
"0": " ", # nothing "0": " ", # nothing
"16": " ", # perimeter "16": " ", # perimeter
"4": ".", "4": ".",
"4194308": "v", # stair_down "4194308": "v", # stair_down
"8388612": "^", # stair_up "8388612": "^", # stair_up
"13107": "d", # door "13107": "d", # door
"26214": "L", # door, locked "26214": "L", # door, locked
"52429": "T", # door, trapped "52429": "T", # door, trapped
"10485": "S", # door, secret "10485": "S", # door, secret
"65540": "A", # arch "65540": "A", # arch
"20971": "H", # portcullis "20971": "H", # portcullis
"82208": "1", "82208": "1",
"83886": "2", "83886": "2",
"8556": "3", "8556": "3",
@ -23,6 +27,7 @@ terrain_map = {
"93952": "8", "93952": "8",
"95630": "9", "95630": "9",
"80530": "0", "80530": "0",
} }
@ -34,12 +39,12 @@ def convert_donjon(source: str) -> str:
src = SimpleNamespace(**json.loads(source)) src = SimpleNamespace(**json.loads(source))
textmap = "" textmap = ""
for y in range(1, len(src.cells) - 1): for y in range(len(src.cells)):
row = src.cells[y] row = src.cells[y]
for x in range(1, len(row) - 1): for x in range(len(row)):
char = get_char(str(row[x]), default=".") char = get_char(str(row[x]), default=".")
if not char: if not char:
raise Exception(f"{textmap}\nMissing value {row[x]} at ({y}, {x})") raise Exception(f"{textmap}\nMissing value {row[x]} at ({y}, {x})")
textmap += char textmap += char
textmap += "\n" textmap += "\n"
return textmap.rstrip() return textmap

View File

@ -1,9 +1,9 @@
import string from collections import namedtuple
from dataclasses import dataclass from dataclasses import dataclass
from typing import Any, Union from typing import Any, Union
Y_COORDS = string.digits # a position inside a grid.
X_COORDS = list(string.ascii_uppercase + string.ascii_lowercase) Position = namedtuple("Position", ["y", "x", "value"])
@dataclass @dataclass
@ -12,20 +12,6 @@ class Position:
x: int x: int
value: Any value: Any
@property
def coordinates(self) -> str:
return (self.y_coordinate, self.x_coordinate)
@property
def y_coordinate(self) -> str:
max_coord = len(Y_COORDS)
return str((int(self.y / max_coord) * max_coord) + (self.y % max_coord))
@property
def x_coordinate(self) -> str:
max_coord = len(X_COORDS)
return (int(self.x / max_coord) * X_COORDS[0]) + X_COORDS[(self.x % max_coord)]
def __str__(self): def __str__(self):
return f"posiiton-{self.y}-{self.x}-{self.value}" return f"posiiton-{self.y}-{self.x}-{self.value}"

View File

@ -96,8 +96,6 @@ class TileSet:
continue continue
if pos and pos.value: if pos and pos.value:
output += pos.value.render() output += pos.value.render()
else:
output += self.empty_space.render()
output += "\n" output += "\n"
return output.rstrip("\n") return output.rstrip("\n")

View File

@ -8,13 +8,10 @@ class = "tilemapper.tileset.TileSet"
"." = "ground" "." = "ground"
"," = "grass" "," = "grass"
"_" = "water" "_" = "water"
"A" = "archway"
"H" = "portcullis"
"d" = "door, open" "d" = "door, open"
"D" = "door, closed" "D" = "door, closed"
"L" = "door, locked" "L" = "door, locked"
"S" = "door, secret" "S" = "door, secret"
"T" = "door, trapped"
"v" = "stairs, down" "v" = "stairs, down"
"^" = "stairs, up" "^" = "stairs, up"
"0" = "location 0" "0" = "location 0"
@ -28,4 +25,3 @@ class = "tilemapper.tileset.TileSet"
"8" = "location 8" "8" = "location 8"
"9" = "location 9" "9" = "location 9"

View File

@ -4,7 +4,6 @@ from textwrap import dedent
import pytest import pytest
from tilemapper import battlemap, tileset from tilemapper import battlemap, tileset
from tilemapper.grid import X_COORDS
@pytest.fixture @pytest.fixture
@ -16,33 +15,18 @@ def manager():
def sample_map(): def sample_map():
return dedent( return dedent(
""" """
# ........ 1 ........ 1
# ........ ........ 2 ........ ........ 2
# ........ ........ ........ ........
# .......L...D... D .......L...D... D
# ........ .... ... ........ .... ...
# ........ ...d ... ........ ...d ...
# . .
# .........S. 3 .........S. 3
# 5 . . 5 . .
# ....S... ...d.... 4 ....S... ...d.... 4
# ........ ........ ........ ........
# ........ ........ ........ ........
[map]
name = "sample map"
description = "sample dungeon"
[1]
name = "room 1"
[2]
name = "room 2"
[3]
name = "room 3"
[4]
name = "room 4"
[5]
name = "room 5"
""" """
) )
@ -54,27 +38,9 @@ def test_tileset_loader(manager):
def test_renderer(manager, sample_map): def test_renderer(manager, sample_map):
test_map = battlemap.BattleMap(source=StringIO(sample_map), tileset=manager.load("ascii")) test_map = battlemap.BattleMap("test map", source=StringIO(sample_map), tileset=manager.load("ascii"))
test_map.load() test_map.load()
assert test_map.width == 21 assert test_map.width == 21
assert test_map.height == 12 assert test_map.height == 12
assert test_map.source_data == sample_map.strip("\n")
srclines = sample_map.splitlines()[1:] assert str(test_map) == sample_map.strip("\n")
strlines = str(test_map).splitlines()
for i in range(len(strlines)):
assert strlines[i] == srclines[i].lstrip(' #').ljust(21, " ")
def test_grid_coordiates(manager):
coord_length = len(X_COORDS)
map_size = 2 * coord_length + 1
bigmap = StringIO((' # ' + ("." * map_size) + "\n") * map_size)
test_map = battlemap.BattleMap(source=bigmap, tileset=manager.load("ascii"))
test_map.load()
assert test_map.grid.data[-1][-1].coordinates == (f"{map_size - 1}", X_COORDS[0] * 3)
lines = test_map.top_coordinates.splitlines()
assert len(lines) == 3
assert lines[0] == (" " * (map_size - 1)) + X_COORDS[0]
assert lines[1][: (coord_length + 1)] == (" " * coord_length) + X_COORDS[0]
assert lines[2] == "".join(X_COORDS) + ((coord_length + 1) * X_COORDS[0])