Initial import
12
README.md
|
@ -1,3 +1,11 @@
|
|||
# tilemapper
|
||||
# TileMapper
|
||||
|
||||
A TTRPG battle map generator using custom tile sets.
|
||||
A TTRPG battle map generator using custom tile sets.
|
||||
|
||||
## Overview
|
||||
|
||||
WIP
|
||||
|
||||
## Quick start
|
||||
|
||||
WIP
|
||||
|
|
51
pyproject.toml
Normal file
|
@ -0,0 +1,51 @@
|
|||
[tool.poetry]
|
||||
name = "tilemapper"
|
||||
version = "0.1.0"
|
||||
description = ""
|
||||
authors = ["evilchili <evilchili@gmail.com>"]
|
||||
readme = "README.md"
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
python = "^3.11"
|
||||
typer = "^0.16.0"
|
||||
pillow = "^11.3.0"
|
||||
|
||||
|
||||
[tool.poetry.group.dev.dependencies]
|
||||
pytest = "^8.4.1"
|
||||
pytest-cov = "^6.2.1"
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry-core"]
|
||||
build-backend = "poetry.core.masonry.api"
|
||||
|
||||
|
||||
### SLAM
|
||||
|
||||
[tool.black]
|
||||
line-length = 120
|
||||
target-version = ['py310']
|
||||
|
||||
[tool.isort]
|
||||
multi_line_output = 3
|
||||
line_length = 120
|
||||
include_trailing_comma = true
|
||||
|
||||
[tool.autoflake]
|
||||
check = false # return error code if changes are needed
|
||||
in-place = true # make changes to files instead of printing diffs
|
||||
recursive = true # drill down directories recursively
|
||||
remove-all-unused-imports = true # remove all unused imports (not just those from the standard library)
|
||||
ignore-init-module-imports = true # exclude __init__.py when removing unused imports
|
||||
remove-duplicate-keys = true # remove all duplicate keys in objects
|
||||
remove-unused-variables = true # remove unused variables
|
||||
|
||||
[tool.pytest.ini_options]
|
||||
log_cli_level = "DEBUG"
|
||||
addopts = "--cov=src --cov-report=term-missing"
|
||||
|
||||
### ENDSLAM
|
||||
|
||||
|
||||
[tool.poetry.scripts]
|
||||
mapper = "tilemapper.cli:app"
|
10
samples/edge_test.txt
Normal file
|
@ -0,0 +1,10 @@
|
|||
,,,,,,,,
|
||||
m,__,,,_,
|
||||
m____,,,,
|
||||
m.__,,,,,
|
||||
mm...,,,,
|
||||
m,___.,,,,
|
||||
m,,,,,,,
|
||||
|
||||
|
||||
|
5
samples/edge_test_small.txt
Normal file
|
@ -0,0 +1,5 @@
|
|||
,,,,,,,
|
||||
,,___,,
|
||||
,,___,,
|
||||
,,___,,
|
||||
,,,,,,,
|
20
samples/five_room_dungeon.txt
Normal file
|
@ -0,0 +1,20 @@
|
|||
..... 1
|
||||
..... ..... 2
|
||||
..........D....
|
||||
..... ..D..
|
||||
.
|
||||
.
|
||||
S.........
|
||||
. .
|
||||
. .
|
||||
..... 3 .L.... 4
|
||||
..... ......
|
||||
..... ......
|
||||
....v.
|
||||
|
||||
.^.........
|
||||
...........
|
||||
...........
|
||||
...........
|
||||
........... 5
|
||||
|
10
samples/outdoor_test.txt
Normal file
|
@ -0,0 +1,10 @@
|
|||
,,,,TTTTT
|
||||
,,,.....TTT
|
||||
,,,.....TTTTT
|
||||
,,.......TTT
|
||||
,,........TT
|
||||
MM._________..M
|
||||
MM.________..MM
|
||||
MM._____o_____..M
|
||||
M__o___________.M
|
||||
|
7
samples/test.txt
Normal file
|
@ -0,0 +1,7 @@
|
|||
,,,,,,,,,,,,,,
|
||||
,,,........,,,
|
||||
,,,.______.,,,
|
||||
,,,._o____.,,,
|
||||
,,,.______.,,,
|
||||
,,,........,,,
|
||||
,,,,,,,,,,,,,,
|
0
src/tilemapper/README.md
Normal file
1
src/tilemapper/__init__.py
Normal file
|
@ -0,0 +1 @@
|
|||
from . import battlemap, tileset
|
94
src/tilemapper/battlemap.py
Normal file
|
@ -0,0 +1,94 @@
|
|||
# import logging
|
||||
from dataclasses import dataclass
|
||||
from functools import cached_property
|
||||
from io import StringIO
|
||||
from pathlib import Path
|
||||
from textwrap import indent
|
||||
from typing import Union
|
||||
|
||||
from PIL import Image
|
||||
|
||||
from tilemapper.grid import Grid, Position
|
||||
from tilemapper.tileset import TileSet
|
||||
|
||||
|
||||
class UnsupportedTileException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
@dataclass
|
||||
class BattleMap:
|
||||
name: str = ""
|
||||
source: Union[StringIO, Path] = None
|
||||
source_data: str = ""
|
||||
tileset: TileSet = None
|
||||
width: int = 0
|
||||
height: int = 0
|
||||
|
||||
def load(self):
|
||||
try:
|
||||
data = self.source.read_bytes().decode()
|
||||
except AttributeError:
|
||||
data = self.source.read()
|
||||
data = data.strip("\n")
|
||||
if not self.validate_source_data(data):
|
||||
return
|
||||
self.source_data = data
|
||||
if hasattr(self, "grid"):
|
||||
del self.grid
|
||||
return self.grid
|
||||
|
||||
def validate_source_data(self, data: str):
|
||||
for char in set(list(data)):
|
||||
if char == "\n":
|
||||
continue
|
||||
if char not in self.tileset.character_map:
|
||||
raise UnsupportedTileException(f"The current tileset does not support the '{char}' character.")
|
||||
return True
|
||||
|
||||
def render(self):
|
||||
map_image = Image.new("RGB", (self.tileset.tile_size * self.width, self.tileset.tile_size * self.height))
|
||||
empty_space = Position(y=-1, x=-1, value=self.tileset.empty_space)
|
||||
for y in range(0, self.height):
|
||||
for x in range(0, self.width):
|
||||
pos = self.grid.at(y, x, empty_space)
|
||||
map_image.paste(
|
||||
self.tileset.render_tile(pos, [a or pos for a in self.grid.adjacent(pos)]),
|
||||
(self.tileset.tile_size * x, self.tileset.tile_size * y),
|
||||
)
|
||||
return map_image
|
||||
|
||||
@cached_property
|
||||
def grid(self):
|
||||
matrix = []
|
||||
for line in self.source_data.splitlines():
|
||||
matrix.append([self.tileset.get(char) for char in line])
|
||||
self.width = max([len(line) for line in matrix])
|
||||
self.height = len(matrix)
|
||||
return Grid(data=matrix)
|
||||
|
||||
@property
|
||||
def title(self):
|
||||
return f"BattleMap: {self.name} ({self.width} x {self.height}, {self.width * self.height * 5}sq.ft.)"
|
||||
|
||||
@property
|
||||
def legend(self):
|
||||
output = ""
|
||||
for char in sorted(set(list(self.source_data)), key=str.lower):
|
||||
if char in self.tileset.character_map:
|
||||
output += f"{char} - {self.tileset.character_map[char]}\n"
|
||||
return output
|
||||
|
||||
def __str__(self):
|
||||
output = ""
|
||||
for y in range(0, self.height):
|
||||
for x in range(0, self.width):
|
||||
try:
|
||||
output += str(self.grid.at(y, x).value)
|
||||
except AttributeError:
|
||||
continue
|
||||
output += "\n"
|
||||
return output.rstrip("\n")
|
||||
|
||||
def __repr__(self):
|
||||
return f"\n{self.title}\n\n{indent(str(self), ' ')}\n\nLegend:\n{indent(self.legend, ' ')}"
|
74
src/tilemapper/cli.py
Normal file
|
@ -0,0 +1,74 @@
|
|||
import logging
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import typer
|
||||
from rich.console import Console
|
||||
from rich.logging import RichHandler
|
||||
|
||||
from tilemapper import battlemap
|
||||
from tilemapper import tileset as _tileset
|
||||
|
||||
app = typer.Typer()
|
||||
app_state = {}
|
||||
|
||||
|
||||
@app.callback(invoke_without_command=True)
|
||||
def main(
|
||||
ctx: typer.Context,
|
||||
config_dir: Path = Path(__file__).parent.parent / "tilesets",
|
||||
verbose: bool = typer.Option(default=False, help="If True, increase verbosity of status messages."),
|
||||
):
|
||||
app_state["tileset_manager"] = _tileset.TileSetManager(config_dir)
|
||||
app_state["config_dir"] = config_dir
|
||||
app_state["verbose"] = verbose
|
||||
debug = os.getenv("DEBUG", None)
|
||||
logging.basicConfig(
|
||||
format="%(name)s %(message)s",
|
||||
level=logging.DEBUG if debug else logging.INFO,
|
||||
handlers=[RichHandler(rich_tracebacks=True, tracebacks_suppress=[typer])],
|
||||
)
|
||||
|
||||
|
||||
@app.command()
|
||||
def list():
|
||||
manager = app_state["tileset_manager"]
|
||||
print("\n".join(manager.available))
|
||||
|
||||
|
||||
@app.command()
|
||||
def inspect(source: Path):
|
||||
manager = app_state["tileset_manager"]
|
||||
bmap = battlemap.BattleMap(name=source.name, source=source, tileset=manager.console_map)
|
||||
bmap.load()
|
||||
console = Console()
|
||||
console.print(repr(bmap), highlight=False)
|
||||
|
||||
|
||||
@app.command()
|
||||
def render(source: Path, outfile: Path, tileset: str = typer.Option(help="The tile set to use.")):
|
||||
manager = app_state["tileset_manager"]
|
||||
if tileset not in manager.available:
|
||||
raise RuntimeError(f"Could not locate the tile set {tileset} in {manager.config_dir}.")
|
||||
|
||||
try:
|
||||
bmap = battlemap.BattleMap(name=source.name, source=source, tileset=manager.load(tileset))
|
||||
except _tileset.MissingTileException as e:
|
||||
logging.error(e)
|
||||
sys.exit(1)
|
||||
|
||||
try:
|
||||
bmap.load()
|
||||
except battlemap.UnsupportedTileException as e:
|
||||
logging.error(e)
|
||||
sys.exit(1)
|
||||
|
||||
image = bmap.render()
|
||||
# image = image.resize((int(0.5 * image.size[0]), int(0.5 * image.size[1])))
|
||||
image.save(outfile)
|
||||
print(f"Wrote {outfile.stat().st_size} bytes to {outfile}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
app()
|
2
src/tilemapper/generator.py
Normal file
|
@ -0,0 +1,2 @@
|
|||
def random():
|
||||
pass
|
55
src/tilemapper/grid.py
Normal file
|
@ -0,0 +1,55 @@
|
|||
from collections import namedtuple
|
||||
|
||||
Position = namedtuple("GridPosition", ["y", "x", "value"])
|
||||
|
||||
|
||||
class Grid:
|
||||
def __init__(self, data):
|
||||
self.data = []
|
||||
for y in range(len(data)):
|
||||
row = []
|
||||
for x in range(len(data[y])):
|
||||
row.append(Position(y=y, x=x, value=data[y][x]))
|
||||
self.data.append(row)
|
||||
|
||||
def at(self, y, x, default=None):
|
||||
try:
|
||||
return self.data[y][x]
|
||||
except IndexError:
|
||||
return default
|
||||
|
||||
def north(self, position: Position):
|
||||
return self.at(position.y - 1, position.x)
|
||||
|
||||
def east(self, position: Position):
|
||||
return self.at(position.y, position.x + 1)
|
||||
|
||||
def south(self, position: Position):
|
||||
return self.at(position.y + 1, position.x)
|
||||
|
||||
def west(self, position: Position):
|
||||
return self.at(position.y, position.x - 1)
|
||||
|
||||
def northwest(self, position: Position):
|
||||
return self.at(position.y - 1, position.x - 1)
|
||||
|
||||
def northeast(self, position: Position):
|
||||
return self.at(position.y - 1, position.x + 1)
|
||||
|
||||
def southwest(self, position: Position):
|
||||
return self.at(position.y + 1, position.x - 1)
|
||||
|
||||
def southeast(self, position: Position):
|
||||
return self.at(position.y + 1, position.x + 1)
|
||||
|
||||
def adjacent(self, position: Position):
|
||||
return (
|
||||
self.northwest(position),
|
||||
self.north(position),
|
||||
self.northeast(position),
|
||||
self.east(position),
|
||||
self.southeast(position),
|
||||
self.south(position),
|
||||
self.southwest(position),
|
||||
self.west(position),
|
||||
)
|
284
src/tilemapper/tileset.py
Normal file
|
@ -0,0 +1,284 @@
|
|||
import random
|
||||
import tomllib
|
||||
from dataclasses import dataclass, field
|
||||
from functools import cached_property
|
||||
from pathlib import Path
|
||||
from typing import Dict, List
|
||||
|
||||
from PIL import Image, ImageDraw
|
||||
|
||||
from tilemapper.grid import Position
|
||||
|
||||
DEFAULT_TILE_SIZE_IN_PIXELS = 128
|
||||
|
||||
COLOR_MAP = {
|
||||
".": "white on grey58",
|
||||
"d": "bold cyan on grey58",
|
||||
"D": "bold green on grey58",
|
||||
"L": "bold dark_red on grey58",
|
||||
"S": "bold black on grey58",
|
||||
"v": "bold steel_blue on grey3",
|
||||
"^": "bold steel_blue on grey3",
|
||||
"0": "white on dark_red",
|
||||
"1": "white on dark_red",
|
||||
"2": "white on dark_red",
|
||||
"3": "white on dark_red",
|
||||
"4": "white on dark_red",
|
||||
"5": "white on dark_red",
|
||||
"6": "white on dark_red",
|
||||
"7": "white on dark_red",
|
||||
"8": "white on dark_red",
|
||||
"9": "white on dark_red",
|
||||
}
|
||||
|
||||
|
||||
class MissingTileException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
class Tile:
|
||||
name: str
|
||||
char: str
|
||||
|
||||
def render(self):
|
||||
return str(self)
|
||||
|
||||
@cached_property
|
||||
def terrain_name(self):
|
||||
return self.name.split("_", 1)[0]
|
||||
|
||||
def __str__(self):
|
||||
return self.char
|
||||
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
class TileSet:
|
||||
name: str
|
||||
desc: str = ""
|
||||
tiles: Dict[str, Tile] = field(default_factory=dict)
|
||||
character_map: Dict[str, str] = field(
|
||||
default_factory=lambda: {
|
||||
" ": "empty",
|
||||
".": "ground",
|
||||
"d": "door, open",
|
||||
"D": "door, closed",
|
||||
"L": "door, locked",
|
||||
"S": "door, secret",
|
||||
"v": "stairs, down",
|
||||
"^": "stairs, up",
|
||||
"0": "location 0",
|
||||
"1": "location 1",
|
||||
"2": "location 2",
|
||||
"3": "location 3",
|
||||
"4": "location 4",
|
||||
"5": "location 5",
|
||||
"6": "location 6",
|
||||
"7": "location 7",
|
||||
"8": "location 8",
|
||||
"9": "location 9",
|
||||
}
|
||||
)
|
||||
|
||||
def add(self, *tiles: Tile):
|
||||
for tile in tiles:
|
||||
self.tiles[tile.name] = tile
|
||||
|
||||
def get(self, char: str):
|
||||
t = self.character_map[char]
|
||||
try:
|
||||
return self.tiles[f"{t}"]
|
||||
except KeyError:
|
||||
raise MissingTileException(f'"{self}" does not contain a "{t}" tile.')
|
||||
|
||||
@property
|
||||
def empty_space(self):
|
||||
return self.tiles[self.character_map[" "]]
|
||||
|
||||
def __str__(self):
|
||||
return f"[Tileset] {self.name}: {self.desc}"
|
||||
|
||||
|
||||
@dataclass
|
||||
class ColorizedTile(Tile):
|
||||
color: str
|
||||
|
||||
def __str__(self):
|
||||
return f"[{self.color}]{self.char}[/{self.color}]"
|
||||
|
||||
|
||||
class ColorizedTileSet(TileSet):
|
||||
tiles: Dict[str, ColorizedTile] = {}
|
||||
|
||||
|
||||
@dataclass
|
||||
class ImageTile(Tile):
|
||||
paths: List[Path]
|
||||
buffer: Image = None
|
||||
|
||||
@property
|
||||
def image(self):
|
||||
if self.buffer:
|
||||
return self.buffer
|
||||
return Image.open(random.choice(self.paths))
|
||||
|
||||
@property
|
||||
def width(self):
|
||||
return self.image.size[0]
|
||||
|
||||
@property
|
||||
def height(self):
|
||||
return self.image.size[1]
|
||||
|
||||
def render(
|
||||
self,
|
||||
size: int = 0,
|
||||
nw: Tile = None,
|
||||
n: Tile = None,
|
||||
ne: Tile = None,
|
||||
e: Tile = None,
|
||||
se: Tile = None,
|
||||
s: Tile = None,
|
||||
sw: Tile = None,
|
||||
w: Tile = None,
|
||||
):
|
||||
rendered = self.image.copy()
|
||||
|
||||
if nw:
|
||||
rendered.paste(nw.image, (0, 0))
|
||||
if n:
|
||||
rendered.paste(n.image, (32, 0))
|
||||
rendered.paste(n.image, (64, 0))
|
||||
if ne:
|
||||
rendered.paste(ne.image, (96, 0))
|
||||
if e:
|
||||
rendered.paste(e.image, (96, 32))
|
||||
rendered.paste(e.image, (96, 64))
|
||||
if se:
|
||||
rendered.paste(se.image, (96, 96))
|
||||
if s:
|
||||
rendered.paste(s.image, (32, 96))
|
||||
rendered.paste(s.image, (64, 96))
|
||||
if sw:
|
||||
rendered.paste(sw.image, (0, 96))
|
||||
if w:
|
||||
rendered.paste(w.image, (0, 32))
|
||||
rendered.paste(w.image, (0, 64))
|
||||
|
||||
if size == rendered.width and size == rendered.height:
|
||||
return rendered
|
||||
return rendered.resize((size, size))
|
||||
|
||||
|
||||
@dataclass
|
||||
class ImageTileSet(TileSet):
|
||||
image_dir: Path
|
||||
tile_size: int = 128
|
||||
|
||||
_cache = {}
|
||||
|
||||
def _load_images(self, char: str, name: str):
|
||||
paths = []
|
||||
lastkey = None
|
||||
key = None
|
||||
for imgfile in sorted(self.image_dir.glob(f"{name}_*.png")):
|
||||
(terrain_name, *parts) = imgfile.stem.rsplit("_")
|
||||
key = terrain_name
|
||||
if parts[0] in ("edge", "corner"):
|
||||
key = "_".join([terrain_name, *parts[:-1]])
|
||||
if not lastkey:
|
||||
lastkey = key
|
||||
if lastkey != key:
|
||||
self.add(*[self._get_or_create_tile(char=char, name=lastkey, paths=paths)])
|
||||
lastkey = key
|
||||
paths = []
|
||||
paths.append(imgfile)
|
||||
if key:
|
||||
self.add(*[self._get_or_create_tile(char=char, name=key, paths=paths)])
|
||||
|
||||
def load(self):
|
||||
self.tiles = {}
|
||||
for char, name in self.character_map.items():
|
||||
self._load_images(char, name)
|
||||
|
||||
def _get_or_create_tile(self, char: str, name: str, paths: List[Path]):
|
||||
if name in self._cache:
|
||||
return self._cache[name]
|
||||
buffer = None
|
||||
if not paths:
|
||||
buffer = Image.new("RGB", (self.tile_size, self.tile_size))
|
||||
ImageDraw.Draw(buffer).text((3, 3), name, (255, 255, 255))
|
||||
self._cache[name] = ImageTile(char=char, name=name, paths=paths, buffer=buffer)
|
||||
return self._cache[name]
|
||||
|
||||
def _get_overlays(self, position: Position, adjacent: List[Position] = []):
|
||||
terrain = position.value.terrain_name
|
||||
(nw_terrain, n_terrain, ne_terrain, e_terrain, se_terrain, s_terrain, sw_terrain, w_terrain) = [
|
||||
a.value.terrain_name for a in adjacent
|
||||
]
|
||||
n = f"{terrain}_edge_{n_terrain}_n" if n_terrain != terrain else None
|
||||
s = f"{terrain}_edge_{s_terrain}_s" if s_terrain != terrain else None
|
||||
e = f"{terrain}_edge_{e_terrain}_e" if e_terrain != terrain else None
|
||||
w = f"{terrain}_edge_{w_terrain}_w" if w_terrain != terrain else None
|
||||
nw = f"{n}w" if n_terrain != terrain and w_terrain == n_terrain else n
|
||||
nw = f"{terrain}_corner_nw" if n_terrain == terrain and nw_terrain != terrain else nw or w
|
||||
sw = f"{s}w" if s_terrain != terrain and w_terrain == s_terrain else s
|
||||
sw = f"{terrain}_corner_sw" if s_terrain == terrain and sw_terrain != terrain else sw or w
|
||||
ne = f"{n}e" if n_terrain != terrain and e_terrain == n_terrain else n
|
||||
ne = f"{terrain}_corner_ne" if n_terrain == terrain and ne_terrain != terrain else ne or e
|
||||
se = f"{s}e" if s_terrain != terrain and e_terrain == s_terrain else s
|
||||
se = f"{terrain}_corner_se" if s_terrain == terrain and se_terrain != terrain else se or e
|
||||
|
||||
return (nw, n, ne, e, se, s, sw, w)
|
||||
|
||||
def render_tile(self, position, adjacent):
|
||||
return position.value.render(
|
||||
self.tile_size, *[self._cache.get(overlay) for overlay in self._get_overlays(position, adjacent)]
|
||||
)
|
||||
|
||||
|
||||
class TileSetManager:
|
||||
def __init__(self, config_dir: Path):
|
||||
self.config_dir = config_dir
|
||||
self._available = {}
|
||||
self._load_tilesets()
|
||||
|
||||
@property
|
||||
def available(self):
|
||||
return {"console": self.console_map, "colorized": self.console_map_colorized, **self._available}
|
||||
|
||||
def _load_tilesets(self):
|
||||
self._available = {}
|
||||
for config_file in self.config_dir.rglob("tileset.toml"):
|
||||
config = tomllib.loads(config_file.read_bytes().decode())
|
||||
config["config_dir"] = config_file.parent
|
||||
self._available[config["config_dir"].name] = config
|
||||
|
||||
def load(self, name: str):
|
||||
config = self.available[name]
|
||||
tileset = ImageTileSet(
|
||||
image_dir=config["config_dir"],
|
||||
name=config["tileset"]["name"],
|
||||
desc=config["tileset"]["desc"],
|
||||
tile_size=config["tileset"].get("size", DEFAULT_TILE_SIZE_IN_PIXELS),
|
||||
character_map=config["legend"],
|
||||
)
|
||||
tileset.load()
|
||||
return tileset
|
||||
|
||||
@cached_property
|
||||
def console_map(self):
|
||||
ts = TileSet(name="console", desc="Tiles used for input and text rendering.")
|
||||
ts.add(*[Tile(char=key, name=value) for key, value in ts.character_map.items()])
|
||||
return ts
|
||||
|
||||
@cached_property
|
||||
def console_map_colorized(self):
|
||||
ts = ColorizedTileSet(name="colorized", desc="Colorized ASCII.")
|
||||
ts.add(
|
||||
*[
|
||||
ColorizedTile(char=key, name=value, color=COLOR_MAP.get(key, "grey"))
|
||||
for key, value in ts.character_map.items()
|
||||
]
|
||||
)
|
||||
return ts
|
BIN
src/tilesets/test/background_1.png
Normal file
After Width: | Height: | Size: 89 B |
BIN
src/tilesets/test/grass_1.png
Normal file
After Width: | Height: | Size: 415 B |
BIN
src/tilesets/test/grass_2.png
Normal file
After Width: | Height: | Size: 415 B |
BIN
src/tilesets/test/ground_1.png
Normal file
After Width: | Height: | Size: 30 KiB |
BIN
src/tilesets/test/mountain_1.png
Normal file
After Width: | Height: | Size: 2.8 KiB |
11
src/tilesets/test/tileset.toml
Normal file
|
@ -0,0 +1,11 @@
|
|||
[tileset]
|
||||
name = "Test Tiles"
|
||||
desc = "Testing edge-aware tiles"
|
||||
size = 128
|
||||
|
||||
[legend]
|
||||
" " = "background"
|
||||
"," = "grass"
|
||||
"_" = "water"
|
||||
"." = "ground"
|
||||
"m" = "mountain"
|
BIN
src/tilesets/test/water_1.png
Normal file
After Width: | Height: | Size: 415 B |
BIN
src/tilesets/test/water_corner_ne_1.png
Normal file
After Width: | Height: | Size: 243 B |
BIN
src/tilesets/test/water_corner_nw_1.png
Normal file
After Width: | Height: | Size: 240 B |
BIN
src/tilesets/test/water_corner_se_1.png
Normal file
After Width: | Height: | Size: 227 B |
BIN
src/tilesets/test/water_corner_sw_1.png
Normal file
After Width: | Height: | Size: 237 B |
BIN
src/tilesets/test/water_edge_grass_e_1.png
Normal file
After Width: | Height: | Size: 738 B |
BIN
src/tilesets/test/water_edge_grass_n_1.png
Normal file
After Width: | Height: | Size: 755 B |
BIN
src/tilesets/test/water_edge_grass_ne_1.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
src/tilesets/test/water_edge_grass_nw_1.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
src/tilesets/test/water_edge_grass_s_1.png
Normal file
After Width: | Height: | Size: 742 B |
BIN
src/tilesets/test/water_edge_grass_se_1.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
src/tilesets/test/water_edge_grass_sw_1.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
src/tilesets/test/water_edge_grass_w_1.png
Normal file
After Width: | Height: | Size: 765 B |
46
test/test_mapper.py
Normal file
|
@ -0,0 +1,46 @@
|
|||
from io import StringIO
|
||||
from pathlib import Path
|
||||
from textwrap import dedent
|
||||
|
||||
import pytest
|
||||
|
||||
from tilemapper import battlemap, tileset
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def manager():
|
||||
return tileset.TileSetManager(Path(__file__).parent / "fixtures")
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def sample_map():
|
||||
return dedent(
|
||||
"""
|
||||
........ 1
|
||||
........ ........ 2
|
||||
........ ........
|
||||
.......L...D... D
|
||||
........ .... ...
|
||||
........ ...d ...
|
||||
.
|
||||
.........S. 3
|
||||
5 . .
|
||||
....S... ...d.... 4
|
||||
........ ........
|
||||
........ ........
|
||||
|
||||
"""
|
||||
)
|
||||
|
||||
|
||||
def test_tileset_loader(manager):
|
||||
assert manager.console_map.name in manager.available
|
||||
|
||||
|
||||
def test_renderer(manager, sample_map):
|
||||
test_map = battlemap.BattleMap("test map", source=StringIO(sample_map), tileset=manager.console_map)
|
||||
test_map.load()
|
||||
assert test_map.width == 21
|
||||
assert test_map.height == 12
|
||||
assert test_map.source_data == sample_map.strip("\n")
|
||||
assert str(test_map) == sample_map.strip("\n")
|