Source data is now in toml format
The input text is now expected to be a toml file with a leading comment section defining the battle map, according to the following rules: 1. Blank lines are ignored 2. Lines at the top of the file beginning with '#', optionally with leading spaces, is treated as map data 3. Map data continues until the first line not starting with a #. 4. Any subsequent lines are treated as metadata in toml format. 5. The metadata must contain the [map] section. 6. The [map] section must contain the "name" attribute. The metadata is available as BattleMap.metadata; the map source is available as BattleMap.map_string. New convenience properties ahve been added to the BattleMap class for accessing portions of the text-only output. This includes the header and footer attributes.
This commit is contained in:
parent
ee8c2af2d8
commit
b03f4c1193
41
src/examples/five_room_dungeon.toml
Normal file
41
src/examples/five_room_dungeon.toml
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
# ..... 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"
|
|
@ -1,20 +0,0 @@
|
||||||
..... 1
|
|
||||||
..... ..... 2
|
|
||||||
..........D....
|
|
||||||
..... ..D..
|
|
||||||
.
|
|
||||||
.
|
|
||||||
S.........
|
|
||||||
. .
|
|
||||||
. .
|
|
||||||
..... 3 .L.... 4
|
|
||||||
..... ......
|
|
||||||
..... ......
|
|
||||||
....v.
|
|
||||||
|
|
||||||
.^.........
|
|
||||||
...,,,,,...
|
|
||||||
...,___,...
|
|
||||||
...,,,,,...
|
|
||||||
........... 5
|
|
||||||
|
|
|
@ -5,6 +5,9 @@ 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
|
||||||
|
|
||||||
|
@ -28,14 +31,14 @@ class BattleMap(BattleMapType):
|
||||||
Example Usage:
|
Example Usage:
|
||||||
|
|
||||||
>>> console = TileSetManager().load("colorized")
|
>>> console = TileSetManager().load("colorized")
|
||||||
>>> bmap = BattleMap(name="Example Map", source="input.txt", tileset=console)
|
>>> bmap = BattleMap("input.txt", tileset=console)
|
||||||
>>> print(bmap.as_string())
|
>>> print(bmap.as_string())
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
name: str = ""
|
|
||||||
source: Union[StringIO, Path] = None
|
source: Union[StringIO, Path] = None
|
||||||
source_data: str = ""
|
map_string: str = ""
|
||||||
|
metadata: SimpleNamespace = None
|
||||||
tileset: TileSet = None
|
tileset: TileSet = None
|
||||||
width: int = 0
|
width: int = 0
|
||||||
height: int = 0
|
height: int = 0
|
||||||
|
@ -51,22 +54,37 @@ 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):
|
|
||||||
return
|
map_data = ""
|
||||||
self.source_data = data
|
metadata = ""
|
||||||
|
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, "title"):
|
if hasattr(self, "header"):
|
||||||
del self.title
|
del self.header
|
||||||
|
if hasattr(self, "footer"):
|
||||||
|
del self.footer
|
||||||
if hasattr(self, "legend"):
|
if hasattr(self, "legend"):
|
||||||
del self.legend
|
del self.legend
|
||||||
if hasattr(self, "coordinates"):
|
if hasattr(self, "coordinates"):
|
||||||
del self.coordinates
|
del self.coordinates
|
||||||
return self.grid
|
return self.grid
|
||||||
|
|
||||||
def validate_source_data(self, data: str) -> bool:
|
def validate_map_string(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.
|
||||||
"""
|
"""
|
||||||
|
@ -101,28 +119,44 @@ class BattleMap(BattleMapType):
|
||||||
Return the current map's Grid instance.
|
Return the current map's Grid instance.
|
||||||
"""
|
"""
|
||||||
matrix = []
|
matrix = []
|
||||||
for line in self.source_data.splitlines():
|
for line in self.map_string.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
|
@cached_property
|
||||||
def title(self) -> str:
|
def header(self) -> str:
|
||||||
return f"BattleMap: {self.name} ({self.width} x {self.height})"
|
lines = [f"BattleMap: {self.metadata.map['name']} ({self.width}x{self.height})"]
|
||||||
|
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
|
||||||
|
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
|
@cached_property
|
||||||
def legend(self) -> str:
|
def legend(self) -> str:
|
||||||
output = ""
|
output = ""
|
||||||
locations = False
|
locations = False
|
||||||
for char in sorted(set(list(self.source_data)), key=str.lower):
|
for char in sorted(set(list(self.map_string)), 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 = True
|
||||||
continue
|
continue
|
||||||
output += f"{char} - {self.tileset.config.legend[char]}\n"
|
output += f"{char} - {self.tileset.config.legend[char]}\n"
|
||||||
if locations:
|
if locations:
|
||||||
output += f"0-9 - locations"
|
output += "0-9 - locations"
|
||||||
justified = ""
|
justified = ""
|
||||||
for line in output.splitlines():
|
for line in output.splitlines():
|
||||||
(key, sep, desc) = line.partition(" - ")
|
(key, sep, desc) = line.partition(" - ")
|
||||||
|
@ -159,14 +193,13 @@ class BattleMap(BattleMapType):
|
||||||
return "\n".join(
|
return "\n".join(
|
||||||
[
|
[
|
||||||
"",
|
"",
|
||||||
self.title,
|
self.header,
|
||||||
"",
|
"",
|
||||||
indent(self.top_coordinates, " " * 5),
|
indent(self.top_coordinates, " " * 5),
|
||||||
indent(top_break, " " * 3),
|
indent(top_break, " " * 3),
|
||||||
lines.rstrip(),
|
lines.rstrip(),
|
||||||
indent(bot_break, " " * 3),
|
indent(bot_break, " " * 3),
|
||||||
"",
|
"",
|
||||||
"Legend:",
|
indent(self.footer, " ")
|
||||||
indent(self.legend, " "),
|
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
|
@ -78,7 +78,7 @@ def render_map(
|
||||||
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(name=source.name, source=source, tileset=manager.load(tileset))
|
bmap = battlemap.BattleMap(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)
|
||||||
|
|
|
@ -16,18 +16,33 @@ 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"
|
||||||
|
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
|
@ -39,23 +54,22 @@ def test_tileset_loader(manager):
|
||||||
|
|
||||||
|
|
||||||
def test_renderer(manager, sample_map):
|
def test_renderer(manager, sample_map):
|
||||||
test_map = battlemap.BattleMap("test map", source=StringIO(sample_map), tileset=manager.load("ascii"))
|
test_map = battlemap.BattleMap(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:]
|
srclines = sample_map.splitlines()[1:]
|
||||||
strlines = str(test_map).splitlines()
|
strlines = str(test_map).splitlines()
|
||||||
for i in range(len(strlines)):
|
for i in range(len(strlines)):
|
||||||
assert strlines[i] == srclines[i].ljust(21, " ")
|
assert strlines[i] == srclines[i].lstrip(' #').ljust(21, " ")
|
||||||
|
|
||||||
|
|
||||||
def test_grid_coordiates(manager):
|
def test_grid_coordiates(manager):
|
||||||
coord_length = len(X_COORDS)
|
coord_length = len(X_COORDS)
|
||||||
map_size = 2 * coord_length + 1
|
map_size = 2 * coord_length + 1
|
||||||
bigmap = StringIO((("." * map_size) + "\n") * map_size)
|
bigmap = StringIO((' # ' + ("." * map_size) + "\n") * map_size)
|
||||||
test_map = battlemap.BattleMap("test map", source=bigmap, tileset=manager.load("ascii"))
|
test_map = battlemap.BattleMap(source=bigmap, tileset=manager.load("ascii"))
|
||||||
test_map.load()
|
test_map.load()
|
||||||
assert test_map.grid.data[-1][-1].coordinates == (f"{map_size - 1}", X_COORDS[0] * 3)
|
assert test_map.grid.data[-1][-1].coordinates == (f"{map_size - 1}", X_COORDS[0] * 3)
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user