Initial commit for the Python version
This commit is contained in:
		
							
								
								
									
										4
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,4 @@
 | 
			
		||||
*.pyc
 | 
			
		||||
__pycache__/
 | 
			
		||||
/.venv/
 | 
			
		||||
/.coverage
 | 
			
		||||
							
								
								
									
										32
									
								
								data/plane.erodar.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								data/plane.erodar.txt
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,32 @@
 | 
			
		||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 | 
			
		||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 | 
			
		||||
~~~~~~~===~===~~~~~~~~=~~~~~~~~~
 | 
			
		||||
~~~~~~====~~==~~~~~~~=~~~~~~~~~~
 | 
			
		||||
~~~~~=====#=======~~~=~~~~~~~~~~
 | 
			
		||||
~~~~=====#======~==~===~~~~~~~~~
 | 
			
		||||
~~~=======#==============~~~~~~~
 | 
			
		||||
~~=========#==============~~~~~~
 | 
			
		||||
~~~=========#==============~~~~~
 | 
			
		||||
~~===========#==============~~~~
 | 
			
		||||
~~~===========#======~=======~~~
 | 
			
		||||
~~~=======####==============~~~~
 | 
			
		||||
~~~~======#================~~~~~
 | 
			
		||||
~~~~~=====#=====~========~~~~~~~
 | 
			
		||||
~~~~~~~====#===~~====~==~~~~~~~~
 | 
			
		||||
~~~~~~~~==#===~~~~===~~==~~~~~~~
 | 
			
		||||
~~~~~~~===#=~~~~~~~~===~=~~~~~~~
 | 
			
		||||
~~~~~~===#===~~~~~~====~~~~~~~~~
 | 
			
		||||
~~~~~~~~=#====~~~~~~~~~~~~~~~~~~
 | 
			
		||||
~~~~~~~~==#==~~~~~~~=~~~~~~~~~~~
 | 
			
		||||
~~~~~~~~~=#=~~~~~~~==~~~~~~~~~~~
 | 
			
		||||
~~~~~~~~~===~~~~~~~~~~~~~~~~~~~~
 | 
			
		||||
~~~~~~~~====~~~~~~~~~~~~~~~~~~~~
 | 
			
		||||
~~~~~~~~~====~~~~~~~~~~~~~~~~~~~
 | 
			
		||||
~~~~~~~~~~===~~~~~~~~~~~~~~~~~~~
 | 
			
		||||
~~~~~~~~~=====~~~~~~~~~~~~~~~~~~
 | 
			
		||||
~~~~~~~~~~===~~~~~~~=~~~~~~~~~~~
 | 
			
		||||
~~~~~~~~~~~==~~~~~~==~~~~~~~~~~~
 | 
			
		||||
~~~~~~~~~~==~~~~~~=~==~~~~~~~~~~
 | 
			
		||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 | 
			
		||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 | 
			
		||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 | 
			
		||||
							
								
								
									
										23
									
								
								data/terrain.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								data/terrain.json
									
									
									
									
									
										Normal file
									
								
							@@ -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"
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										0
									
								
								wmud/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								wmud/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										8
									
								
								wmud/__main__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								wmud/__main__.py
									
									
									
									
									
										Normal file
									
								
							@@ -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)
 | 
			
		||||
							
								
								
									
										30
									
								
								wmud/world/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								wmud/world/__init__.py
									
									
									
									
									
										Normal file
									
								
							@@ -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)
 | 
			
		||||
							
								
								
									
										175
									
								
								wmud/world/map.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										175
									
								
								wmud/world/map.py
									
									
									
									
									
										Normal file
									
								
							@@ -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 '<Map>'
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
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
 | 
			
		||||
		Reference in New Issue
	
	Block a user