From 7415f825efb7d957af5cece07b19df463641f362 Mon Sep 17 00:00:00 2001 From: Gergely Polonkai Date: Mon, 12 Jun 2017 10:56:08 +0200 Subject: [PATCH] Initial commit for the Python version --- .gitignore | 4 + data/plane.erodar.txt | 32 ++++++++ data/terrain.json | 23 ++++++ wmud/__init__.py | 0 wmud/__main__.py | 8 ++ wmud/world/__init__.py | 30 +++++++ wmud/world/map.py | 175 +++++++++++++++++++++++++++++++++++++++++ 7 files changed, 272 insertions(+) create mode 100644 .gitignore create mode 100644 data/plane.erodar.txt create mode 100644 data/terrain.json create mode 100644 wmud/__init__.py create mode 100644 wmud/__main__.py create mode 100644 wmud/world/__init__.py create mode 100644 wmud/world/map.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c748a52 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +*.pyc +__pycache__/ +/.venv/ +/.coverage diff --git a/data/plane.erodar.txt b/data/plane.erodar.txt new file mode 100644 index 0000000..dcd7f6c --- /dev/null +++ b/data/plane.erodar.txt @@ -0,0 +1,32 @@ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +~~~~~~~===~===~~~~~~~~=~~~~~~~~~ +~~~~~~====~~==~~~~~~~=~~~~~~~~~~ +~~~~~=====#=======~~~=~~~~~~~~~~ +~~~~=====#======~==~===~~~~~~~~~ +~~~=======#==============~~~~~~~ +~~=========#==============~~~~~~ +~~~=========#==============~~~~~ +~~===========#==============~~~~ +~~~===========#======~=======~~~ +~~~=======####==============~~~~ +~~~~======#================~~~~~ +~~~~~=====#=====~========~~~~~~~ +~~~~~~~====#===~~====~==~~~~~~~~ +~~~~~~~~==#===~~~~===~~==~~~~~~~ +~~~~~~~===#=~~~~~~~~===~=~~~~~~~ +~~~~~~===#===~~~~~~====~~~~~~~~~ +~~~~~~~~=#====~~~~~~~~~~~~~~~~~~ +~~~~~~~~==#==~~~~~~~=~~~~~~~~~~~ +~~~~~~~~~=#=~~~~~~~==~~~~~~~~~~~ +~~~~~~~~~===~~~~~~~~~~~~~~~~~~~~ +~~~~~~~~====~~~~~~~~~~~~~~~~~~~~ +~~~~~~~~~====~~~~~~~~~~~~~~~~~~~ +~~~~~~~~~~===~~~~~~~~~~~~~~~~~~~ +~~~~~~~~~=====~~~~~~~~~~~~~~~~~~ +~~~~~~~~~~===~~~~~~~=~~~~~~~~~~~ +~~~~~~~~~~~==~~~~~~==~~~~~~~~~~~ +~~~~~~~~~~==~~~~~~=~==~~~~~~~~~~ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/data/terrain.json b/data/terrain.json new file mode 100644 index 0000000..b406eb5 --- /dev/null +++ b/data/terrain.json @@ -0,0 +1,23 @@ +{ + "water": { + "icon": "~", + "map_char": "~", + "color16": 12, + "color256": 57, + "color": "#0000bb" + }, + "field": { + "icon": "=", + "map_char": "=", + "color16": 11, + "color256": 228, + "color": "#bbbb00" + }, + "road": { + "icon": "#", + "map_char": "#", + "color16": 8, + "color256": 244, + "color": "#808080" + } +} diff --git a/wmud/__init__.py b/wmud/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/wmud/__main__.py b/wmud/__main__.py new file mode 100644 index 0000000..56f7d93 --- /dev/null +++ b/wmud/__main__.py @@ -0,0 +1,8 @@ +from wmud.world.map import Plane, load_terrain + +load_terrain('data/terrain.json') +map = Plane() +map.read('data/plane.erodar.txt') +print(map) +print('') +map.print((0, 0), 11) diff --git a/wmud/world/__init__.py b/wmud/world/__init__.py new file mode 100644 index 0000000..1d255a0 --- /dev/null +++ b/wmud/world/__init__.py @@ -0,0 +1,30 @@ +"""World definitions for wMUD +""" + +from enum import IntEnum + + +class Directions(IntEnum): + """All the available directions + """ + + NORTH = 0 + SOUTH = 1 + EAST = 2 + WEST = 3 + NORTH_EAST = 4 + SOUTH_WEST = 5 + NORTH_WEST = 6 + SOUTH_EAST = 7 + UP = 8 + DOWN = 9 + + @property + def opposite(self): + """Get the opposite direction + """ + + if self % 2 == 0: + return self.__class__(self + 1) + + return self.__class__(self - 1) diff --git a/wmud/world/map.py b/wmud/world/map.py new file mode 100644 index 0000000..cb4e8ec --- /dev/null +++ b/wmud/world/map.py @@ -0,0 +1,175 @@ +import json +from math import sqrt + + +TYPES = {} + + +class TileType(object): + def __init__(self, data): + self.map_char = data.pop('map_char') + self.extra_data = data + + def __str__(self): + return self.map_char + + +class Coordinate(object): + def __init__(self, x, y): + if x < 0 or y < 0: + raise ValueError(f'Invalid coordinates [{x}; {y}]') + + self.__x = x + self.__y = y + + @property + def x(self): + return self.__x + + @property + def y(self): + return self.__y + + def __str__(self): + return f'[{self.x}; {self.y}]' + + +class Tile(object): + def __init__(self, type): + self.__type = type + + def __str__(self): + return self.__type.char + + +class Plane(object): + def __init__(self): + self.__tiles = {} + self.width = None + self.height = 0 + + def read(self, filename): + with open(filename) as f: + fields = f.read().split() + + self.width = None + + for row_num, row in enumerate(fields): + # We simply skip completely empty rows + if not row: + continue + + if self.width is None: + self.width = len(row) + elif self.width != len(row): + raise ValueError(f'Row {row_num} differs in length!') + + for col_num, field in enumerate(row): + self.__tiles[Coordinate(col_num, row_num)] = get_type(field) + + self.height = row_num + 1 + + def print(self, coords, radius): + """Print a round part of the map, with `coords` in the midpoint, and the + horizontal radius (ie. width in characters) `radius` + + This will result in a shape that resembles a circle, but, as text + characters have about 1:2 ratio, is actually an ellipse. + """ + + if not isinstance(coords, Coordinate): + coords = Coordinate(*coords) + + if radius > self.width // 2: + raise ValueError('Radius is too big') + + if radius % 2 != 1: + raise ValueError('Radius must be odd') + + width = (radius - 1) * 2 + 1 + + mid_x, mid_y = width // 2, radius // 2 + + for row in range(0, radius): + y_offset = row - mid_y + y = y_offset * 2 + map_y = coords.y + y_offset + + if map_y < 0: + map_y += self.height + elif map_y >= self.height: + map_y -= self.height + + for column in range(0, width): + x_offset = column - mid_x + x = x_offset + map_x = coords.x + x_offset + + if map_x < 0: + map_x += self.width + elif map_x >= self.width: + map_x -= self.width + + if x < 0: + x -= 1 + elif x > 0: + x += 1 + + distance = sqrt(y ** 2 + x ** 2) + + if row == mid_y and column == mid_x: + print('@', end='') + elif distance > radius + 0.25: + print(' ', end='') + else: + tile = self[(map_x, map_y)] + print(tile, end='') + + print('') + + def __getitem__(self, coord): + if not isinstance(coord, Coordinate): + coord = Coordinate(*coord) + + for key, tile in self.__tiles.items(): + if key.x == coord.x and key.y == coord.y: + return tile + + def __str__(self): + map_str = '' + + prev_y = -1 + + for coord in sorted(self.__tiles.keys(), key=lambda coord: (coord.y, coord.x)): + if coord.y != prev_y: + prev_y = coord.y + map_str += '\n' + + map_str += self.__tiles[coord].map_char + + return map_str + + def __repr__(self): + return '' + + +def get_type(type_char): + candidates = {identifier: data for identifier, data in TYPES.items() if data.map_char == type_char} + + if not candidates: + raise KeyError(f'Map character "{type_char}" is unknown') + + if len(candidates) > 1: + raise KeyError(f'Map character "{type_char}" has multiple definitions!') + + return candidates.popitem()[1] + + +def load_terrain(filename): + with open(filename, 'r') as f: + terrain_data = json.load(f) + + for identifier, data in terrain_data.items(): + # TODO: Validate terrain data + tile_type = TileType(data) + TYPES[identifier] = tile_type