From b117bd6fc2543e12dd366ecb01cf5cad0d9612b2 Mon Sep 17 00:00:00 2001 From: Benjamin Kirkbride Date: Sun, 28 Apr 2019 21:41:55 -0400 Subject: [PATCH] init commit --- dev_requirements.txt | 10 + make.sh | 15 + pytiled_parser/__init__.py | 0 pytiled_parser/foo.py | 1244 +++++++++++++++++ setup.py | 40 + test/assets | 1 + test/collision_test.py | 65 + test/config.py | 1 + test/foo.py | 1 + test/output.py | 23 + test/test2.py | 27 + test/test_tiled-bak.py | 28 + test/test_tiled.py | 18 + tests/__init__.py | 3 + tests/test_data/images/tmw_desert_spacing.png | Bin 0 -> 37830 bytes tests/test_data/test_map_image_tile_set.tmx | 91 ++ tests/test_data/test_map_infinite.tmx | 390 ++++++ tests/test_data/test_map_simple.tmx | 23 + tests/test_data/tile_set_image.tsx | 4 + tests/unit2/test_pytiled_parser.py | 83 ++ 20 files changed, 2067 insertions(+) create mode 100644 dev_requirements.txt create mode 100755 make.sh create mode 100644 pytiled_parser/__init__.py create mode 100644 pytiled_parser/foo.py create mode 100644 setup.py create mode 120000 test/assets create mode 100644 test/collision_test.py create mode 100644 test/config.py create mode 100644 test/foo.py create mode 100644 test/output.py create mode 100644 test/test2.py create mode 100644 test/test_tiled-bak.py create mode 100644 test/test_tiled.py create mode 100644 tests/__init__.py create mode 100644 tests/test_data/images/tmw_desert_spacing.png create mode 100644 tests/test_data/test_map_image_tile_set.tmx create mode 100644 tests/test_data/test_map_infinite.tmx create mode 100644 tests/test_data/test_map_simple.tmx create mode 100644 tests/test_data/tile_set_image.tsx create mode 100644 tests/unit2/test_pytiled_parser.py diff --git a/dev_requirements.txt b/dev_requirements.txt new file mode 100644 index 0000000..de3f832 --- /dev/null +++ b/dev_requirements.txt @@ -0,0 +1,10 @@ +-e . +pytest +sphinx +coverage +coveralls +sphinx_rtd_theme +setuptools +pytest-cov +pytest-mock +wheel diff --git a/make.sh b/make.sh new file mode 100755 index 0000000..529ac3f --- /dev/null +++ b/make.sh @@ -0,0 +1,15 @@ +#!/bin/bash + +rm -rf doc/build +rm dist/* +python3 setup.py clean +python3 setup.py build +python3 setup.py bdist_wheel +pip3 uninstall -y pytiled_parser +for file in dist/* +do + pip3 install $file +done +# sphinx-build -b html doc doc/build/html +coverage run --source arcade setup.py test +coverage report -m diff --git a/pytiled_parser/__init__.py b/pytiled_parser/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pytiled_parser/foo.py b/pytiled_parser/foo.py new file mode 100644 index 0000000..6becb91 --- /dev/null +++ b/pytiled_parser/foo.py @@ -0,0 +1,1244 @@ +""" +Functions and classes for managing a map created in the "Tiled Map Editor" +""" + +import csv +import dataclasses +import functools +import re + +from collections import OrderedDict +from pathlib import Path + +import xml.etree.ElementTree as etree + +from typing import * # pylint: disable=W0401 + + +class EncodingError(Exception): + """ + Tmx layer encoding is of an unknown type. + """ + + +class TileNotFoundError(Exception): + """ + Tile not found in tileset. + """ + + +class ImageNotFoundError(Exception): + """ + Image not found. + """ + + +class Color(NamedTuple): + """ + Color object. + + Attributes: + :red (int): Red, between 1 and 255. + :green (int): Green, between 1 and 255. + :blue (int): Blue, between 1 and 255. + :alpha (int): Alpha, between 1 and 255. + """ + red: int + green: int + blue: int + alpha: int + + +class OrderedPair(NamedTuple): + """ + OrderedPair NamedTuple. + + Attributes: + :x (Union[int, float]): X coordinate. + :y (Union[int, float]): Y coordinate. + """ + x: Union[int, float] + y: Union[int, float] + + +def _parse_color(color: str) -> Color: + """ + Converts the color formats that Tiled uses into ones that Arcade accepts. + + Returns: + :Color: Color object in the format that Arcade understands. + """ + # strip initial '#' character + if not len(color) % 2 == 0: # pylint: disable=C2001 + color = color[1:] + + if len(color) == 6: + # full opacity if no alpha specified + alpha = 0xFF + red = int(color[0:2], 16) + green = int(color[2:4], 16) + blue = int(color[4:6], 16) + else: + alpha = int(color[0:2], 16) + red = int(color[2:4], 16) + green = int(color[4:6], 16) + blue = int(color[6:8], 16) + + return Color(red, green, blue, alpha) + + +class Template: + """ + FIXME TODO + """ + +@dataclasses.dataclass +class Chunk: + """ + Chunk object for infinite maps. + + See: https://doc.mapeditor.org/en/stable/reference/tmx-map-format/#chunk + + Attributes: + :location (OrderedPair): Location of chunk in tiles. + :width (int): The width of the chunk in tiles. + :height (int): The height of the chunk in tiles. + :layer_data (List[List(int)]): The global tile IDs in chunky + according to row. + """ + location: OrderedPair + width: int + height: int + chunk_data: List[List[int]] + + +class Image(NamedTuple): + """ + Image object. + + This module does not support embedded data in image elements. + + Attributes: + :source (Optional[str]): The reference to the tileset image file. + Not that this is a relative path compared to FIXME + :trans (Optional[Color]): Defines a specific color that is treated + as transparent. + :width (Optional[str]): The image width in pixels + (optional, used for tile index correction when the image changes). + :height (Optional[str]): The image height in pixels (optional). + """ + source: str + trans: Optional[Color] + width: Optional[int] + height: Optional[int] + + +def _parse_image_element(image_element: etree.Element) -> Image: + """ + Parse image element given. + + Returns: + :Color: Color in Arcade's preffered format. + """ + source = image_element.attrib['source'] + + trans = None + try: + trans = _parse_color(image_element.attrib['trans']) + except KeyError: + pass + + width = None + try: + width = int(image_element.attrib['width']) + except KeyError: + pass + + height = None + try: + height = int(image_element.attrib['height']) + except KeyError: + pass + + return Image(source, trans, width, height) + + +Properties = Dict[str, Union[int, float, Color, Path, str]] + + +def _parse_properties_element( + properties_element: etree.Element) -> Properties: + """ + Adds Tiled property to Properties dict. + + Args: + :name (str): Name of property. + :property_type (str): Type of property. Can be string, int, float, + bool, color or file. Defaults to string. + :value (str): The value of the property. + + Returns: + :Properties: Properties Dict object. + """ + properties: Properties = {} + for property_element in properties_element.findall('./property'): + name = property_element.attrib['name'] + try: + property_type = property_element.attrib['type'] + except KeyError: + # strings do not have an attribute in property elements + property_type = 'string' + value = property_element.attrib['value'] + + property_types = ['string', 'int', 'float', 'bool', 'color', 'file'] + assert property_type in property_types, ( + f"Invalid type for property {name}") + + if property_type == 'int': + properties[name] = int(value) + elif property_type == 'float': + properties[name] = float(value) + elif property_type == 'color': + properties[name] = _parse_color(value) + elif property_type == 'file': + properties[name] = Path(value) + elif property_type == 'bool': + if value == 'true': + properties[name] = True + else: + properties[name] = False + else: + properties[name] = value + + return properties + + +class Grid(NamedTuple): + """ + Contains info for isometric maps. + + This element is only used in case of isometric orientation, and + determines how tile overlays for terrain and collision information + are rendered. + """ + orientation: str + width: int + height: int + + +class Terrain(NamedTuple): + """ + Terrain object. + + Args: + :name (str): The name of the terrain type. + :tile (int): The local tile-id of the tile that represents the + terrain visually. + """ + name: str + tile: int + + +class Frame(NamedTuple): + """ + Animation Frame object. + + This is only used as a part of an animation for Tile objects. + + Args: + :tileid (int): The local ID of a tile within the parent tile set + object. + :duration (int): How long in milliseconds this frame should be + displayed before advancing to the next frame. + """ + tileid: int + duration: int + + +@dataclasses.dataclass +class TileTerrain: + """ + Defines each corner of a tile by Terrain index in + 'TileSet.terrain_types'. + + Defaults to 'None'. 'None' means that corner has no terrain. + + Attributes: + :top_left (Optional[int]): Top left terrain type. + :top_right (Optional[int]): Top right terrain type. + :bottom_left (Optional[int]): Bottom left terrain type. + :bottom_right (Optional[int]): Bottom right terrain type. + """ + top_left: Optional[int] = None + top_right: Optional[int] = None + bottom_left: Optional[int] = None + bottom_right: Optional[int] = None + + +@dataclasses.dataclass +class _LayerTypeBase: + id: int # pylint: disable=C0103 + name: str + + +@dataclasses.dataclass +class _LayerTypeDefaults: + offset: OrderedPair = OrderedPair(0, 0) + opacity: int = 0xFF + + properties: Optional[Properties] = None + + +@dataclasses.dataclass +class LayerType(_LayerTypeDefaults, _LayerTypeBase): + """ + Class that all layer classes inherit from. + + Not to be directly used. + + Args: + :layer_element (etree.Element): Element to be parsed into a + LayerType object. + + Attributes: + :id (int): Unique ID of the layer. Each layer that added to a map + gets a unique id. Even if a layer is deleted, no layer ever gets + the same ID. + :name (Optional[str):] The name of the layer object. + :offset (OrderedPair): Rendering offset of the layer object in + pixels. (default: (0, 0). + :opacity (int): Value between 0 and 255 to determine opacity. NOTE: + this value is converted from a float provided by Tiled, so some + precision is lost. + :properties (Optional[Properties]): Properties object for layer + object. + """ + + +def _parse_layer_type(layer_element: etree.Element) -> LayerType: + """ + Parse layer type element given. + """ + id = int(layer_element.attrib['id']) + + name = layer_element.attrib['name'] + + layer_type_object = LayerType(id, name) + + try: + offset_x = float(layer_element.attrib['offsetx']) + except KeyError: + offset_x = 0 + + try: + offset_y = float(layer_element.attrib['offsety']) + except KeyError: + offset_y = 0 + offset = OrderedPair(offset_x, offset_y) + + try: + layer_type_object.opacity = round( + float(layer_element.attrib['opacity']) * 255) + except KeyError: + pass + + properties_element = layer_element.find('./properties') + if properties_element is not None: + layer_type_object.properties = _parse_properties_element( + properties_element) + + if layer_element.tag == 'layer': + return _parse_layer(layer_element, layer_type_object) + elif layer_element.tag == 'objectgroup': + return _parse_object_group(layer_element, layer_type_object) + # else: + # return _parse_layer_group(layer_element, layer_type_object) + + +LayerData = Union[List[List[int]], List[Chunk]] +""" +The tile data for one layer. + +Either a 2 dimensional array of integers representing the global tile IDs + for a map layer, or a lists of chunks for an infinite map layer. +""" + + +@dataclasses.dataclass +class _LayerBase: + width: int + height: int + data: LayerData + + +@dataclasses.dataclass +class Layer(LayerType, _LayerBase): + """ + Map layer object. + + See: https://doc.mapeditor.org/en/stable/reference/tmx-map-format/#layer + + Attributes: + :width (int): The width of the layer in tiles. Always the same as + the map width for fixed-size maps. + :height (int): The height of the layer in tiles. Always the same as + the map height for fixed-size maps. + :data (LayerData): Either an 2 dimensional array of integers + representing the global tile IDs for the map layer, or a list of + chunks for an infinite map. + """ + + +def _parse_layer(element: etree.Element, layer_type: LayerType) -> Layer: + """ + Parse layer element given. + """ + width = int(element.attrib['width']) + height = int(element.attrib['height']) + data_element = element.find('./data') + if data_element is not None: + data: LayerData = _parse_data(data_element, width) + else: + raise ValueError('{element} has no child data element.') + + return Layer(width, height, data, **layer_type.__dict__) + + +@dataclasses.dataclass +class _ObjectBase: + id: int + location: OrderedPair + + +@dataclasses.dataclass +class _ObjectDefaults: + size: OrderedPair = OrderedPair(0, 0) + rotation: int = 0 + opacity: int = 0xFF + + name: Optional[str] = None + type: Optional[str] = None + + properties: Optional[Properties] = None + template: Optional[Template] = None + + +@dataclasses.dataclass +class Object(_ObjectDefaults, _ObjectBase): + """ + ObjectGroup Object. + + See: \ + https://doc.mapeditor.org/en/stable/reference/tmx-map-format/#object + + Args: + :id (int): Unique ID of the object. Each object that is placed on a + map gets a unique id. Even if an object was deleted, no object + gets the same ID. + :location (OrderedPair): The location of the object in pixels. + :name (Optional[str]): The name of the object. + :type (Optional[str]): The type of the object. + :width (int): The width of the object in pixels (default: 0). + :height (int): The height of the object in pixels (default: 0). + :opacity (int): The opacity of the object. (default: 255) + :rotation (int): The rotation of the object in degrees clockwise + (default: 0). + :template Optional[Template]: A reference to a Template object + FIXME + """ + + +@dataclasses.dataclass +class RectangleObject(Object): + """ + Rectangle shape defined by a point, width, and height. + + See: https://doc.mapeditor.org/en/stable/manual/objects/#insert-rectangle + (objects in tiled are rectangles by default, so there is no specific + documentation on the tmx-map-format page for it.) + """ + + +@dataclasses.dataclass +class ElipseObject(Object): + """ + Elipse shape defined by a point, width, and height. + + See: https://doc.mapeditor.org/en/stable/reference/tmx-map-format/#ellipse + """ + + +@dataclasses.dataclass +class PointObject(Object): + """ + Point defined by a point (x,y). + + See: https://doc.mapeditor.org/en/stable/reference/tmx-map-format/#point + """ + + +@dataclasses.dataclass +class _TileObjectBase(_ObjectBase): + gid: int + + +@dataclasses.dataclass +class TileObject(Object, _TileObjectBase): + """ + Polygon shape defined by a set of connections between points. + + See: https://doc.mapeditor.org/en/stable/manual/objects/#insert-tile + + Attributes: + :gid (int): Refference to a global tile id. + """ + + +@dataclasses.dataclass +class _PointsObjectBase(_ObjectBase): + points: List[OrderedPair] + + +@dataclasses.dataclass +class PolygonObject(Object, _PointsObjectBase): + """ + Polygon shape defined by a set of connections between points. + + See: https://doc.mapeditor.org/en/stable/reference/tmx-map-format/#polygon + + Attributes: + :points (List[OrderedPair]) + """ + + +@dataclasses.dataclass +class PolylineObject(Object, _PointsObjectBase): + """ + Polyline defined by a set of connections between points. + + See: \ +https://doc.mapeditor.org/en/stable/reference/tmx-map-format/#polyline + + Attributes: + :points (List[Tuple[int, int]]): List of coordinates relative to \ + the location of the object. + """ + + +@dataclasses.dataclass +class _TextObjectBase(_ObjectBase): + text: str + + +@dataclasses.dataclass +class _TextObjectDefaults(_ObjectDefaults): + font_family: str = 'sans-serif' + font_size: int = 16 + wrap: bool = False + color: Color = _parse_color('#000000') + bold: bool = False + italic: bool = False + underline: bool = False + strike_out: bool = False + kerning: bool = False + horizontal_align: str = 'left' + vertical_align: str = 'top' + + +@dataclasses.dataclass +class TextObject(Object, _TextObjectDefaults, _TextObjectBase): + """ + Text object with associated settings. + + See: https://doc.mapeditor.org/en/stable/reference/tmx-map-format/#text + and https://doc.mapeditor.org/en/stable/manual/objects/#insert-text + + Attributes: + :font_family (str): The font family used (default: “sans-serif”) + :font_size (int): The size of the font in pixels. (default: 16) + :wrap (bool): Whether word wrapping is enabled. (default: False) + :color (Color): Color of the text. (default: #000000) + :bold (bool): Whether the font is bold. (default: False) + :italic (bool): Whether the font is italic. (default: False) + :underline (bool): Whether the text is underlined. (default: False) + :strike_out (bool): Whether the text is striked-out. (default: False) + :kerning (bool): Whether kerning should be used while rendering the \ + text. (default: False) + :horizontal_align (str): Horizontal alignment of the text \ + (default: "left") + :vertical_align (str): Vertical alignment of the text (defalt: "top") + """ + + +@dataclasses.dataclass +class _ObjectGroupBase: + objects: List[Object] + + +@dataclasses.dataclass +class _ObjectGroupDefaults(_LayerTypeDefaults): + color: Optional[Color] = None + draworder: Optional[str] = 'topdown' + + +@dataclasses.dataclass +class ObjectGroup(LayerType, _ObjectGroupDefaults, _ObjectGroupBase): + """ + Object Group Object. + + The object group is in fact a map layer, and is hence called \ + “object layer” in Tiled. + + See: \ +https://doc.mapeditor.org/en/stable/reference/tmx-map-format/#objectgroup + + Attributes: + :color (Optional[Color]): The color used to display the objects + in this group. FIXME: editor only? + :draworder (str): Whether the objects are drawn according to the + order of the object elements in the object group element + ('manual'), or sorted by their y-coordinate ('topdown'). Defaults + to 'topdown'. See: + https://doc.mapeditor.org/en/stable/manual/objects/#changing-stacking-order + for more info. + :objects (Dict[int, Object]): Dict Object objects by Object.id. + """ + + +def _parse_object_group( + element: etree.Element, layer_type: LayerType) -> ObjectGroup: + """ + Parse object group element given. + """ + object_elements = element.findall('./object') + objects: List[Object] = [] + + for object_element in object_elements: + id = int(object_element.attrib['id']) + location_x = float(object_element.attrib['x']) + location_y = float(object_element.attrib['y']) + location = OrderedPair(location_x, location_y) + + object = Object(id, location) + + try: + width = float(object_element.attrib['width']) + except KeyError: + width = 0 + + try: + height = float(object_element.attrib['height']) + except KeyError: + height = 0 + + object.size = OrderedPair(width, height) + + try: + object.opacity = round( + float(object_element.attrib['opacity']) * 255) + except KeyError: + pass + + try: + object.rotation = int(object_element.attrib['rotation']) + except KeyError: + pass + + try: + object.name = object_element.attrib['name'] + except KeyError: + pass + + + properties_element = object_element.find('./properties') + if properties_element is not None: + print('hi') + object.properties = _parse_properties_element(properties_element) + + objects.append(object) + + object_group = ObjectGroup(objects, **layer_type.__dict__) + try: + color = _parse_color(element.attrib['color']) + except KeyError: + pass + + try: + draworder = element.attrib['draworder'] + except KeyError: + pass + + return object_group + + +class LayerGroup: + """ + Object for a Layer Group. + + A LayerGroup can be thought of as a layer that contains layers + (potentially including other LayerGroups). + + Attributes offset_x, offset_y, and opacity recursively affect child + layers. + + See: https://doc.mapeditor.org/en/stable/reference/tmx-map-format/#group + + Attributes: + FIXME + """ + + +def _decode_csv_layer(data_text): + tile_grid = [] + lines = data_text.split("\n") + # remove erronious empty lists due to a newline being on both ends of text + lines = lines[1:] + lines = lines[:-1] + for line in lines: + line_list = line.split(",") + while '' in line_list: + line_list.remove('') + line_list_int = [int(item) for item in line_list] + tile_grid.append(line_list_int) + + return tile_grid + + +def _decode_base64_data(data_text, compression, layer_width): + tile_grid = [[]] + + unencoded_data = base64.b64decode(data_text) + if compression == "zlib": + unzipped_data = zlib.decompress(unencoded_data) + elif compression == "gzip": + unzipped_data = gzip.decompress(unencoded_data) + elif compression is None: + unzipped_data = unencoded_data + else: + raise ValueError(f"Unsupported compression type '{compression}'.") + + # Turn bytes into 4-byte integers + byte_count = 0 + int_count = 0 + int_value = 0 + row_count = 0 + for byte in unzipped_data: + int_value += byte << (byte_count * 8) + byte_count += 1 + if byte_count % 4 == 0: + byte_count = 0 + int_count += 1 + tile_grid[row_count].append(int_value) + int_value = 0 + if int_count % layer_width == 0: + row_count += 1 + tile_grid.append([]) + + tile_grid.pop() + return tile_grid + + +def _decode_data(element: etree.Element, + layer_width: int, + encoding: str, + compression: Optional[str]) -> List[List[int]]: + """ + Decodes data or chunk data. + + Args: + :element (Element): Element to have text decoded. + :layer_width (int): Number of tiles per column in this layer. Used + for determining when to cut off a row when decoding base64 + encoding layers. + :encoding (str): Encoding format of the layer data. + :compression (str): Compression format of the layer data. + """ + # etree.Element.text comes with an appended and a prepended '\n' + supported_encodings = ['base64', 'csv'] + if encoding not in supported_encodings: + raise ValueError('{encoding} is not a valid encoding') + + supported_compression = [None, 'gzip', 'zlib'] + if compression is not None: + if encoding != 'base64': + raise ValueError('{encoding} does not support compression') + if compression not in supported_compression: + raise ValueError('{compression} is not a valid compression type') + + try: + data_text = element.text[1:-1] # type: ignore + except AttributeError: + raise AttributeError('{element} lacks layer data.') + + if encoding == 'csv': + return _decode_csv_layer(data_text) + + return _decode_base64_data(data_text, compression, layer_width) + +def _parse_data(element: etree.Element, layer_width: int) -> LayerData: + """ + Parses layer data. + + Will parse CSV, base64, gzip-base64, or zlip-base64 encoded data. + + Args: + :element (Element): Data element to parse. + :width (int): Layer width. Used for base64 decoding. + + Returns: + :LayerData: Data object containing layer data or chunks of data. + """ + encoding = element.attrib['encoding'] + compression = None + try: + compression = element.attrib['compression'] + except KeyError: + pass + + chunk_elements = element.findall('./chunk') + if chunk_elements: + chunks: List[Chunk] = [] + for chunk_element in chunk_elements: + x = int(chunk_element.attrib['x']) + y = int(chunk_element.attrib['y']) + location = OrderedPair(x, y) + width = int(chunk_element.attrib['width']) + height = int(chunk_element.attrib['height']) + layer_data = _decode_data( + chunk_element, layer_width, encoding, compression) + chunks.append(Chunk(location, width, height, layer_data)) + return chunks + + return _decode_data(element, layer_width, encoding, compression) + + + + +@dataclasses.dataclass +class Tile: + """ + Individual tile object. + + Args: + :id (int): The local tile ID within its tileset. + :type (str): The type of the tile. Refers to an object type and is + used by tile objects. + :terrain (int): Defines the terrain type of each corner of the tile. + :animation (List[Frame]): Each tile can have exactly one animation + associated with it. + """ + id: int + type: Optional[str] + terrain: Optional[TileTerrain] + animation: Optional[List[Frame]] + image: Optional[Image] + object_group: Optional[ObjectGroup] + + +@dataclasses.dataclass +class TileSet: + """ + Object for storing a TSX with all associated collision data. + + Args: + :name (str): The name of this tileset. + :max_tile_size (OrderedPair): The maximum size of a tile in this + tile set in pixels. + :spacing (int): The spacing in pixels between the tiles in this + tileset (applies to the tileset image). + :margin (int): The margin around the tiles in this tileset + (applies to the tileset image). + :tile_count (int): The number of tiles in this tileset. + :columns (int): The number of tile columns in the tileset. + For image collection tilesets it is editable and is used when + displaying the tileset. + :grid (Grid): Only used in case of isometric orientation, and + determines how tile overlays for terrain and collision information + are rendered. + :tileoffset (Optional[OrderedPair]): Used to specify an offset in + pixels when drawing a tile from the tileset. When not present, no + offset is applied. + :image (Image): Used for spritesheet tile sets. + :terrain_types (Dict[str, int]): List of of terrain types which + can be referenced from the terrain attribute of the tile object. + Ordered according to the terrain element's appearance in the TSX + file. + :tiles (Optional[Dict[int, Tile]]): Dict of Tile objects by Tile.id. + """ + name: str + max_tile_size: OrderedPair + spacing: Optional[int] + margin: Optional[int] + tile_count: Optional[int] + columns: Optional[int] + tile_offset: Optional[OrderedPair] + grid: Optional[Grid] + properties: Optional[Properties] + image: Optional[Image] + terrain_types: Optional[List[Terrain]] + tiles: Optional[Dict[int, Tile]] + + +def _parse_tiles(tile_element_list: List[etree.Element]) -> Dict[int, Tile]: + tiles: Dict[int, Tile] = {} + for tile_element in tile_element_list: + # id is not optional + id = int(tile_element.attrib['id']) + + # optional attributes + type = None + try: + type = tile_element.attrib['type'] + except KeyError: + pass + + tile_terrain = None + try: + tile_terrain_attrib = tile_element.attrib['terrain'] + except KeyError: + pass + else: + # https://doc.mapeditor.org/en/stable/reference/tmx-map-format/#tile + # an attempt to explain how terrains are handled is below. + # 'terrain' attribute is a comma seperated list of 4 values, + # each is either an integer or blank + + # convert to list of values + terrain_list_attrib = re.split(',', tile_terrain_attrib) + # terrain_list is list of indexes of Tileset.terrain_types + terrain_list: List[Optional[int]] = [] + # each index in terrain_list_attrib reffers to a corner + for corner in terrain_list_attrib: + if corner == '': + terrain_list.append(None) + else: + terrain_list.append(int(corner)) + tile_terrain = TileTerrain(*terrain_list) + + # tile element optional sub-elements + animation: Optional[List[Frame]] = None + tile_animation_element = tile_element.find('./animation') + if tile_animation_element: + animation = [] + frames = tile_animation_element.findall('./frame') + for frame in frames: + # tileid reffers to the Tile.id of the animation frame + tileid = int(frame.attrib['tileid']) + # duration is in MS. Should perhaps be converted to seconds. + # FIXME: make decision + duration = int(frame.attrib['duration']) + animation.append(Frame(tileid, duration)) + + # if this is None, then the Tile is part of a spritesheet + tile_image = None + tile_image_element = tile_element.find('./image') + if tile_image_element: + tile_image = _parse_image_element(tile_image_element) + + object_group = None + tile_object_group_element = tile_element.find('./objectgroup') + if tile_object_group_element: + object_group = ObjectGroup(tile_object_group_element) + + tiles[id] = Tile(id, + type, + tile_terrain, + animation, + tile_image, + object_group) + + return tiles + + +def _parse_tile_set(tile_set_element: etree.Element) -> TileSet: + """ + Parses a tile set that is embedded into a TMX. + """ + # get all basic attributes + name = tile_set_element.attrib['name'] + max_tile_width = int(tile_set_element.attrib['tilewidth']) + max_tile_height = int(tile_set_element.attrib['tileheight']) + max_tile_size = OrderedPair(max_tile_width, max_tile_height) + + spacing = None + try: + spacing = int(tile_set_element.attrib['spacing']) + except KeyError: + pass + + margin = None + try: + margin = int(tile_set_element.attrib['margin']) + except KeyError: + pass + + tile_count = None + try: + tile_count = int(tile_set_element.attrib['tilecount']) + except KeyError: + pass + + columns = None + try: + columns = int(tile_set_element.attrib['columns']) + except KeyError: + pass + + tile_offset = None + tileoffset_element = tile_set_element.find('./tileoffset') + if tileoffset_element is not None: + tile_offset_x = int(tileoffset_element.attrib['x']) + tile_offset_y = int(tileoffset_element.attrib['y']) + tile_offset = OrderedPair(tile_offset_x, tile_offset_y) + + grid = None + grid_element = tile_set_element.find('./grid') + if grid_element is not None: + grid_orientation = grid_element.attrib['orientation'] + grid_width = int(grid_element.attrib['width']) + grid_height = int(grid_element.attrib['height']) + grid = Grid(grid_orientation, grid_width, grid_height) + + properties = None + properties_element = tile_set_element.find('./properties') + if properties_element is not None: + properties = _parse_properties_element(properties_element) + + terrain_types: Optional[List[Terrain]] = None + terrain_types_element = tile_set_element.find('./terraintypes') + if terrain_types_element is not None: + terrain_types = [] + for terrain in terrain_types_element.findall('./terrain'): + name = terrain.attrib['name'] + terrain_tile = int(terrain.attrib['tile']) + terrain_types.append(Terrain(name, terrain_tile)) + + image = None + image_element = tile_set_element.find('./image') + if image_element is not None: + image = _parse_image_element(image_element) + + tile_element_list = tile_set_element.findall('./tile') + tiles = _parse_tiles(tile_element_list) + + return TileSet( + name, + max_tile_size, + spacing, + margin, + tile_count, + columns, + tile_offset, + grid, + properties, + image, + terrain_types, + tiles, + ) + + +@functools.lru_cache() +def _parse_external_tile_set( + parent_dir: Path, tile_set_element: etree.Element) -> TileSet: + """ + Parses an external tile set. + + Caches the results to speed up subsequent instances. + """ + source = Path(tile_set_element.attrib['source']) + tile_set_tree = etree.parse(str(parent_dir / Path(source))).getroot() + + return _parse_tile_set(tile_set_tree) + + +@dataclasses.dataclass +class TileMap: + """ + Object for storing a TMX with all associated layers and properties. + + See: https://doc.mapeditor.org/en/stable/reference/tmx-map-format/#map + + Attributes: + :parent_dir (Path): The directory the TMX file is in. Used for finding + relative paths to TSX files and other assets. + :version (str): The TMX format version. + :tiledversion (str): The Tiled version used to save the file. May + be a date (for snapshot builds). + :orientation (str): Map orientation. Tiled supports “orthogonal”, + “isometric”, “staggered” and “hexagonal” + :renderorder (str): The order in which tiles on tile layers are + rendered. Valid values are right-down, right-up, left-down and + left-up. In all cases, the map is drawn row-by-row. (only + supported for orthogonal maps at the moment) + :width (int): The map width in tiles. + :height (int): The map height in tiles. + :tile_width (int): The width of a tile. + :tile_height (int): The height of a tile. + :infinite (bool): If the map is infinite or not. + :hexsidelength (int): Only for hexagonal maps. Determines the width or + height (depending on the staggered axis) of the tile’s edge, in + pixels. + :stagger_axis (str): For staggered and hexagonal maps, determines + which axis (“x” or “y”) is staggered. + :staggerindex (str): For staggered and hexagonal maps, determines + whether the “even” or “odd” indexes along the staggered axis are + shifted. + :backgroundcolor (##FIXME##): The background color of the map. + :nextlayerid (int): Stores the next available ID for new layers. + :nextobjectid (int): Stores the next available ID for new objects. + :tile_sets (dict[str, TileSet]): Dict of tile sets used + in this map. Key is the source for external tile sets or the name + for embedded ones. The value is a TileSet object. + :layers (OrderedDict[str, Union[Layer, ObjectGroup, LayerGroup]]): + OrderedDict of layer objects by draw order. + """ + parent_dir: Path + + version: str + tiled_version: str + orientation: str + render_order: str + width: int + height: int + tile_width: int + tile_height: int + infinite: bool + next_layer_id: int + next_object_id: int + + tile_sets: Dict[int, TileSet] + layers: List[LayerType] + + hex_side_length: Optional[int] = None + stagger_axis: Optional[int] = None + stagger_index: Optional[int] = None + background_color: Optional[Color] = None + + properties: Optional[Properties] = None + + +def parse_tile_map(tmx_file: Union[str, Path]): + # setting up XML parsing + map_tree = etree.parse(str(tmx_file)) + map_element = map_tree.getroot() + + # positional arguments for TileMap + parent_dir = Path(tmx_file).parent + + version = map_element.attrib['version'] + tiled_version = map_element.attrib['tiledversion'] + orientation = map_element.attrib['orientation'] + render_order = map_element.attrib['renderorder'] + width = int(map_element.attrib['width']) + height = int(map_element.attrib['height']) + tile_width = int(map_element.attrib['tilewidth']) + tile_height = int(map_element.attrib['tileheight']) + + infinite_attribute = map_element.attrib['infinite'] + infinite = True if infinite_attribute == 'true' else False + + next_layer_id = int(map_element.attrib['nextlayerid']) + next_object_id = int(map_element.attrib['nextobjectid']) + + # parse all tilesets + tile_sets: Dict[int, TileSet] = {} + tile_set_element_list = map_element.findall('./tileset') + for tile_set_element in tile_set_element_list: + # tiled docs are ambiguous about the 'firstgid' attribute + # current understanding is for the purposes of mapping the layer + # data to the tile set data, add the 'firstgid' value to each + # tile 'id'; this means that the 'firstgid' is specific to each, + # tile set as they pertain to the map, not tile set specific as + # the tiled docs can make it seem + # 'firstgid' is saved beside each TileMapXML + firstgid = int(tile_set_element.attrib['firstgid']) + try: + # check if is an external TSX + source = tile_set_element.attrib['source'] + except KeyError: + # the tile set in embedded + name = tile_set_element.attrib['name'] + tile_sets[firstgid] = _parse_tile_set( + tile_set_element) + else: + # tile set is external + tile_sets[firstgid] = _parse_external_tile_set( + parent_dir, tile_set_element) + + # parse all layers + layers: List[LayerType] = [] + layer_tags = ['layer', 'objectgroup', 'group'] + for element in map_element.findall('./'): + if element.tag not in layer_tags: + # only layer_tags are layer elements + continue + layers.append(_parse_layer_type(element)) + + tile_map = TileMap( + parent_dir, + version, + tiled_version, + orientation, + render_order, + width, + height, + tile_width, + tile_height, + infinite, + next_layer_id, + next_object_id, + tile_sets, + layers, + ) + + try: + tile_map.hex_side_length = int(map_element.attrib['hexsidelength']) + except KeyError: + pass + + try: + tile_map.stagger_axis = int(map_element.attrib['staggeraxis']) + except KeyError: + pass + + try: + tile_map.stagger_index = int(map_element.attrib['staggerindex']) + except KeyError: + pass + + try: + backgroundcolor = map_element.attrib['backgroundcolor'] + except KeyError: + pass + else: + tile_map.background_color = _parse_color(backgroundcolor) + + properties_element = map_tree.find('./properties') + if properties_element is not None: + tile_map.properties = _parse_properties_element(properties_element) + + return tile_map + + +''' +[22:16] <__m4ch1n3__> i would "[i for i in int_list if i < littler_then_value]" +[22:16] <__m4ch1n3__> it returns a list of integers below "littler_then_value" +[22:17] <__m4ch1n3__> !py3 [i for i in [1,2,3,4,1,2,3,4] if i < 3] +[22:17] __m4ch1n3__: [1, 2, 1, 2] +[22:17] <__m4ch1n3__> !py3 [i for i in [1,2,3,4,1,2,3,4] if i < 4] +[22:17] __m4ch1n3__: [1, 2, 3, 1, 2, 3] +[22:22] <__m4ch1n3__> !py3 max([i for i in [1,2,3,4,1,2,3,4] if i < 4]) +[22:22] __m4ch1n3__: 3 +[22:22] <__m4ch1n3__> max(...) would return the maximum of resulting list +[22:23] <__m4ch1n3__> !py3 max([i for i in [1, 10, 100] if i < 20]) +[22:23] __m4ch1n3__: 10 +[22:23] <__m4ch1n3__> !py3 max([i for i in [1, 10, 100] if i < 242]) +[22:23] __m4ch1n3__: 100 +[22:23] == markb1 [~mbiggers@45.36.35.206] has quit [Ping timeout: 245 seconds] +[22:23] <__m4ch1n3__> !py3 max(i for i in [1, 10, 100] if i < 242) +''' + + + + +#buffer diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..edb257d --- /dev/null +++ b/setup.py @@ -0,0 +1,40 @@ +from os import path +import sys +from setuptools import setup + +BUILD = 0 +VERSION = '0.1' +RELEASE = VERSION + +if __name__ == '__main__': + readme = path.join(path.dirname(path.abspath(__file__)), 'README.md') + with open(readme, 'r') as f: + long_desc = f.read() + + setup( + name='pytiled_parser', + version=RELEASE, + description='Python Library for parsing Tiled Map Editor maps.', + long_description=long_desc, + author='Benjamin Kirkbride', + author_email='BenjaminKirkbride@gmail.com', + license='MIT', + url='https://github.com/Beefy-Swain/pytiled_parser', + download_url='https://github.com/Beefy-Swain/pytiled_parser', + install_requires=[ + 'dataclasses', + ], + packages=['pytiled_parser'], + classifiers=[ + 'Development Status :: 1 - Planning', + 'Intended Audience :: Developers', + 'License :: OSI Approved :: MIT License', + 'Operating System :: OS Independent', + 'Programming Language :: Python', + 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: Implementation :: CPython', + 'Topic :: Software Development :: Libraries :: Python Modules', + ], + test_suite='tests', + ) diff --git a/test/assets b/test/assets new file mode 120000 index 0000000..87a867e --- /dev/null +++ b/test/assets @@ -0,0 +1 @@ +../../../the_decay_factor_assets/the-decay-factor---assets/assets/ \ No newline at end of file diff --git a/test/collision_test.py b/test/collision_test.py new file mode 100644 index 0000000..3c8e0f0 --- /dev/null +++ b/test/collision_test.py @@ -0,0 +1,65 @@ +import os +import arcade +import arcade.tiled + +SCREEN_WIDTH = 800 +SCREEN_HEIGHT = 600 + +SPRITE_SCALING = 1 +GRAVITY = 1.1 + +class BasicTestWindow(arcade.Window): + + def __init__(self, width, height, title, map_name): + super().__init__(width, height, title) + file_path = os.path.dirname(os.path.abspath(__file__)) + os.chdir(file_path) + + self.layers = [] + my_map = arcade.tiled.read_tiled_map(map_name, 1) + for layer in my_map.layers: + self.layers.append(arcade.tiled.generate_sprites( + my_map, layer, 1, "../arcade/arcade/examples/")) + + def on_draw(self): + arcade.start_render() + for layer in self.layers: + layer.draw() + +class CollisionTestWindow(BasicTestWindow): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.frame_count = 0 + + self.player_list = arcade.SpriteList() + self.player_sprite = arcade.Sprite( + "../arcade/arcade/examples/images/character.png", SPRITE_SCALING) + self.player_sprite.center_x = 400 + self.player_sprite.center_y = 800 + self.player_list.append(self.player_sprite) + + self.physics_engine = arcade.PhysicsEnginePlatformer( + self.player_sprite, self.layers[0], gravity_constant=GRAVITY) + + def on_draw(self): + super().on_draw() + self.frame_count += 1 + + self.player_list.draw() + + if self.frame_count == 20: + print(self.player_sprite.center_x, self.player_sprite.center_y) + + def update(self, delta_time): + self.physics_engine.update() + + + + +window = CollisionTestWindow( + SCREEN_WIDTH, + SCREEN_HEIGHT, + "Test Text", + "../arcade/arcade/examples/map_polyline_collision.tmx" +) +arcade.run() diff --git a/test/config.py b/test/config.py new file mode 100644 index 0000000..4b85ec1 --- /dev/null +++ b/test/config.py @@ -0,0 +1 @@ +MAP_NAME = 'assets/tiled_test_4.tmx' diff --git a/test/foo.py b/test/foo.py new file mode 100644 index 0000000..d4b31ca --- /dev/null +++ b/test/foo.py @@ -0,0 +1 @@ +{'Coins': [[GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None)], [GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=Tile(gid='4', tilewidth=128, tileheight=128, source='images/coin_01.png', points=None), center_x=704, center_y=704), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=Tile(gid='4', tilewidth=128, tileheight=128, source='images/coin_01.png', points=None), center_x=4032, center_y=704), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None)], [GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=Tile(gid='4', tilewidth=128, tileheight=128, source='images/coin_01.png', points=None), center_x=5440, center_y=576), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None)], [GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=Tile(gid='4', tilewidth=128, tileheight=128, source='images/coin_01.png', points=None), center_x=3392, center_y=448), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None)], [GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None)], [GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None)], [GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None)]], 'Platforms': [[GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=Tile(gid='1', tilewidth=128, tileheight=128, source='images/grassLeft.png', points=None), center_x=3264, center_y=832), GridLocation(tile=Tile(gid='2', tilewidth=128, tileheight=128, source='images/grassMid.png', points=None), center_x=3392, center_y=832), GridLocation(tile=Tile(gid='3', tilewidth=128, tileheight=128, source='images/grassRight.png', points=None), center_x=3520, center_y=832), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None)], [GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None)], [GridLocation(tile=Tile(gid='0', tilewidth=128, tileheight=128, source='images/boxCrate_double.png', points=None), center_x=64, center_y=576), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=Tile(gid='1', tilewidth=128, tileheight=128, source='images/grassLeft.png', points=None), center_x=2624, center_y=576), GridLocation(tile=Tile(gid='2', tilewidth=128, tileheight=128, source='images/grassMid.png', points=None), center_x=2752, center_y=576), GridLocation(tile=Tile(gid='3', tilewidth=128, tileheight=128, source='images/grassRight.png', points=None), center_x=2880, center_y=576), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=Tile(gid='1', tilewidth=128, tileheight=128, source='images/grassLeft.png', points=None), center_x=3904, center_y=576), GridLocation(tile=Tile(gid='2', tilewidth=128, tileheight=128, source='images/grassMid.png', points=None), center_x=4032, center_y=576), GridLocation(tile=Tile(gid='3', tilewidth=128, tileheight=128, source='images/grassRight.png', points=None), center_x=4160, center_y=576), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=Tile(gid='0', tilewidth=128, tileheight=128, source='images/boxCrate_double.png', points=None), center_x=6336, center_y=576)], [GridLocation(tile=Tile(gid='0', tilewidth=128, tileheight=128, source='images/boxCrate_double.png', points=None), center_x=64, center_y=448), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=Tile(gid='1', tilewidth=128, tileheight=128, source='images/grassLeft.png', points=None), center_x=576, center_y=448), GridLocation(tile=Tile(gid='2', tilewidth=128, tileheight=128, source='images/grassMid.png', points=None), center_x=704, center_y=448), GridLocation(tile=Tile(gid='3', tilewidth=128, tileheight=128, source='images/grassRight.png', points=None), center_x=832, center_y=448), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=Tile(gid='0', tilewidth=128, tileheight=128, source='images/boxCrate_double.png', points=None), center_x=2112, center_y=448), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=Tile(gid='0', tilewidth=128, tileheight=128, source='images/boxCrate_double.png', points=None), center_x=4032, center_y=448), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=Tile(gid='0', tilewidth=128, tileheight=128, source='images/boxCrate_double.png', points=None), center_x=5440, center_y=448), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=Tile(gid='0', tilewidth=128, tileheight=128, source='images/boxCrate_double.png', points=None), center_x=6336, center_y=448)], [GridLocation(tile=Tile(gid='0', tilewidth=128, tileheight=128, source='images/boxCrate_double.png', points=None), center_x=64, center_y=320), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=Tile(gid='0', tilewidth=128, tileheight=128, source='images/boxCrate_double.png', points=None), center_x=2112, center_y=320), GridLocation(tile=Tile(gid='0', tilewidth=128, tileheight=128, source='images/boxCrate_double.png', points=None), center_x=2240, center_y=320), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=Tile(gid='1', tilewidth=128, tileheight=128, source='images/grassLeft.png', points=None), center_x=3264, center_y=320), GridLocation(tile=Tile(gid='2', tilewidth=128, tileheight=128, source='images/grassMid.png', points=None), center_x=3392, center_y=320), GridLocation(tile=Tile(gid='3', tilewidth=128, tileheight=128, source='images/grassRight.png', points=None), center_x=3520, center_y=320), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=Tile(gid='0', tilewidth=128, tileheight=128, source='images/boxCrate_double.png', points=None), center_x=4032, center_y=320), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=Tile(gid='0', tilewidth=128, tileheight=128, source='images/boxCrate_double.png', points=None), center_x=5312, center_y=320), GridLocation(tile=Tile(gid='0', tilewidth=128, tileheight=128, source='images/boxCrate_double.png', points=None), center_x=5440, center_y=320), GridLocation(tile=Tile(gid='0', tilewidth=128, tileheight=128, source='images/boxCrate_double.png', points=None), center_x=5568, center_y=320), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=Tile(gid='0', tilewidth=128, tileheight=128, source='images/boxCrate_double.png', points=None), center_x=6336, center_y=320)], [GridLocation(tile=Tile(gid='0', tilewidth=128, tileheight=128, source='images/boxCrate_double.png', points=None), center_x=64, center_y=192), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=Tile(gid='0', tilewidth=128, tileheight=128, source='images/boxCrate_double.png', points=None), center_x=1216, center_y=192), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=Tile(gid='0', tilewidth=128, tileheight=128, source='images/boxCrate_double.png', points=None), center_x=1984, center_y=192), GridLocation(tile=Tile(gid='0', tilewidth=128, tileheight=128, source='images/boxCrate_double.png', points=None), center_x=2112, center_y=192), GridLocation(tile=Tile(gid='0', tilewidth=128, tileheight=128, source='images/boxCrate_double.png', points=None), center_x=2240, center_y=192), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=Tile(gid='0', tilewidth=128, tileheight=128, source='images/boxCrate_double.png', points=None), center_x=4032, center_y=192), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=Tile(gid='0', tilewidth=128, tileheight=128, source='images/boxCrate_double.png', points=None), center_x=4416, center_y=192), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=Tile(gid='0', tilewidth=128, tileheight=128, source='images/boxCrate_double.png', points=None), center_x=5184, center_y=192), GridLocation(tile=Tile(gid='0', tilewidth=128, tileheight=128, source='images/boxCrate_double.png', points=None), center_x=5312, center_y=192), GridLocation(tile=Tile(gid='0', tilewidth=128, tileheight=128, source='images/boxCrate_double.png', points=None), center_x=5440, center_y=192), GridLocation(tile=Tile(gid='0', tilewidth=128, tileheight=128, source='images/boxCrate_double.png', points=None), center_x=5568, center_y=192), GridLocation(tile=Tile(gid='0', tilewidth=128, tileheight=128, source='images/boxCrate_double.png', points=None), center_x=5696, center_y=192), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=None, center_x=None, center_y=None), GridLocation(tile=Tile(gid='0', tilewidth=128, tileheight=128, source='images/boxCrate_double.png', points=None), center_x=6336, center_y=192)], [GridLocation(tile=Tile(gid='1', tilewidth=128, tileheight=128, source='images/grassLeft.png', points=None), center_x=64, center_y=64), GridLocation(tile=Tile(gid='2', tilewidth=128, tileheight=128, source='images/grassMid.png', points=None), center_x=192, center_y=64), GridLocation(tile=Tile(gid='2', tilewidth=128, tileheight=128, source='images/grassMid.png', points=None), center_x=320, center_y=64), GridLocation(tile=Tile(gid='2', tilewidth=128, tileheight=128, source='images/grassMid.png', points=None), center_x=448, center_y=64), GridLocation(tile=Tile(gid='2', tilewidth=128, tileheight=128, source='images/grassMid.png', points=None), center_x=576, center_y=64), GridLocation(tile=Tile(gid='2', tilewidth=128, tileheight=128, source='images/grassMid.png', points=None), center_x=704, center_y=64), GridLocation(tile=Tile(gid='2', tilewidth=128, tileheight=128, source='images/grassMid.png', points=None), center_x=832, center_y=64), GridLocation(tile=Tile(gid='2', tilewidth=128, tileheight=128, source='images/grassMid.png', points=None), center_x=960, center_y=64), GridLocation(tile=Tile(gid='2', tilewidth=128, tileheight=128, source='images/grassMid.png', points=None), center_x=1088, center_y=64), GridLocation(tile=Tile(gid='2', tilewidth=128, tileheight=128, source='images/grassMid.png', points=None), center_x=1216, center_y=64), GridLocation(tile=Tile(gid='2', tilewidth=128, tileheight=128, source='images/grassMid.png', points=None), center_x=1344, center_y=64), GridLocation(tile=Tile(gid='2', tilewidth=128, tileheight=128, source='images/grassMid.png', points=None), center_x=1472, center_y=64), GridLocation(tile=Tile(gid='2', tilewidth=128, tileheight=128, source='images/grassMid.png', points=None), center_x=1600, center_y=64), GridLocation(tile=Tile(gid='2', tilewidth=128, tileheight=128, source='images/grassMid.png', points=None), center_x=1728, center_y=64), GridLocation(tile=Tile(gid='2', tilewidth=128, tileheight=128, source='images/grassMid.png', points=None), center_x=1856, center_y=64), GridLocation(tile=Tile(gid='2', tilewidth=128, tileheight=128, source='images/grassMid.png', points=None), center_x=1984, center_y=64), GridLocation(tile=Tile(gid='2', tilewidth=128, tileheight=128, source='images/grassMid.png', points=None), center_x=2112, center_y=64), GridLocation(tile=Tile(gid='2', tilewidth=128, tileheight=128, source='images/grassMid.png', points=None), center_x=2240, center_y=64), GridLocation(tile=Tile(gid='2', tilewidth=128, tileheight=128, source='images/grassMid.png', points=None), center_x=2368, center_y=64), GridLocation(tile=Tile(gid='2', tilewidth=128, tileheight=128, source='images/grassMid.png', points=None), center_x=2496, center_y=64), GridLocation(tile=Tile(gid='2', tilewidth=128, tileheight=128, source='images/grassMid.png', points=None), center_x=2624, center_y=64), GridLocation(tile=Tile(gid='2', tilewidth=128, tileheight=128, source='images/grassMid.png', points=None), center_x=2752, center_y=64), GridLocation(tile=Tile(gid='2', tilewidth=128, tileheight=128, source='images/grassMid.png', points=None), center_x=2880, center_y=64), GridLocation(tile=Tile(gid='2', tilewidth=128, tileheight=128, source='images/grassMid.png', points=None), center_x=3008, center_y=64), GridLocation(tile=Tile(gid='2', tilewidth=128, tileheight=128, source='images/grassMid.png', points=None), center_x=3136, center_y=64), GridLocation(tile=Tile(gid='2', tilewidth=128, tileheight=128, source='images/grassMid.png', points=None), center_x=3264, center_y=64), GridLocation(tile=Tile(gid='2', tilewidth=128, tileheight=128, source='images/grassMid.png', points=None), center_x=3392, center_y=64), GridLocation(tile=Tile(gid='2', tilewidth=128, tileheight=128, source='images/grassMid.png', points=None), center_x=3520, center_y=64), GridLocation(tile=Tile(gid='2', tilewidth=128, tileheight=128, source='images/grassMid.png', points=None), center_x=3648, center_y=64), GridLocation(tile=Tile(gid='2', tilewidth=128, tileheight=128, source='images/grassMid.png', points=None), center_x=3776, center_y=64), GridLocation(tile=Tile(gid='2', tilewidth=128, tileheight=128, source='images/grassMid.png', points=None), center_x=3904, center_y=64), GridLocation(tile=Tile(gid='2', tilewidth=128, tileheight=128, source='images/grassMid.png', points=None), center_x=4032, center_y=64), GridLocation(tile=Tile(gid='2', tilewidth=128, tileheight=128, source='images/grassMid.png', points=None), center_x=4160, center_y=64), GridLocation(tile=Tile(gid='2', tilewidth=128, tileheight=128, source='images/grassMid.png', points=None), center_x=4288, center_y=64), GridLocation(tile=Tile(gid='2', tilewidth=128, tileheight=128, source='images/grassMid.png', points=None), center_x=4416, center_y=64), GridLocation(tile=Tile(gid='2', tilewidth=128, tileheight=128, source='images/grassMid.png', points=None), center_x=4544, center_y=64), GridLocation(tile=Tile(gid='2', tilewidth=128, tileheight=128, source='images/grassMid.png', points=None), center_x=4672, center_y=64), GridLocation(tile=Tile(gid='2', tilewidth=128, tileheight=128, source='images/grassMid.png', points=None), center_x=4800, center_y=64), GridLocation(tile=Tile(gid='2', tilewidth=128, tileheight=128, source='images/grassMid.png', points=None), center_x=4928, center_y=64), GridLocation(tile=Tile(gid='2', tilewidth=128, tileheight=128, source='images/grassMid.png', points=None), center_x=5056, center_y=64), GridLocation(tile=Tile(gid='2', tilewidth=128, tileheight=128, source='images/grassMid.png', points=None), center_x=5184, center_y=64), GridLocation(tile=Tile(gid='2', tilewidth=128, tileheight=128, source='images/grassMid.png', points=None), center_x=5312, center_y=64), GridLocation(tile=Tile(gid='2', tilewidth=128, tileheight=128, source='images/grassMid.png', points=None), center_x=5440, center_y=64), GridLocation(tile=Tile(gid='2', tilewidth=128, tileheight=128, source='images/grassMid.png', points=None), center_x=5568, center_y=64), GridLocation(tile=Tile(gid='2', tilewidth=128, tileheight=128, source='images/grassMid.png', points=None), center_x=5696, center_y=64), GridLocation(tile=Tile(gid='2', tilewidth=128, tileheight=128, source='images/grassMid.png', points=None), center_x=5824, center_y=64), GridLocation(tile=Tile(gid='2', tilewidth=128, tileheight=128, source='images/grassMid.png', points=None), center_x=5952, center_y=64), GridLocation(tile=Tile(gid='2', tilewidth=128, tileheight=128, source='images/grassMid.png', points=None), center_x=6080, center_y=64), GridLocation(tile=Tile(gid='2', tilewidth=128, tileheight=128, source='images/grassMid.png', points=None), center_x=6208, center_y=64), GridLocation(tile=Tile(gid='3', tilewidth=128, tileheight=128, source='images/grassRight.png', points=None), center_x=6336, center_y=64)]]} diff --git a/test/output.py b/test/output.py new file mode 100644 index 0000000..2eefe9e --- /dev/null +++ b/test/output.py @@ -0,0 +1,23 @@ +hi +{ 'background_color': None, + 'height': 10, + 'hex_side_length': None, + 'infinite': False, + 'layers': [ Layer(width=10, height=10, data=[[9, 10, 11, 12, 13, 14, 15, 16, 30, 30], [17, 18, 19, 20, 21, 22, 23, 24, 30, 30], [25, 26, 27, 28, 29, 30, 31, 32, 30, 30], [33, 34, 35, 36, 37, 38, 39, 40, 30, 30], [41, 42, 43, 44, 45, 46, 47, 48, 30, 30], [30, 30, 30, 30, 30, 30, 30, 30, 30, 30], [30, 30, 30, 30, 30, 30, 30, 30, 30, 30], [30, 30, 30, 30, 30, 30, 30, 30, 30, 30]], id=1, name='Tile Layer 1', offset=OrderedPair(x=0, y=0), opacity=255, properties=None), + Layer(width=10, height=10, data=[[0, 0, 0, 0, 0, 0, 46, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 6, 7, 7, 7, 7, 7, 8, 0], [0, 0, 14, 15, 15, 15, 15, 15, 16, 0]], id=2, name='Tile Layer 2', offset=OrderedPair(x=0, y=0), opacity=128, properties=None), + None, + ObjectGroup(objects=[Object(id=1, location=OrderedPair(x=200.25, y=210.75), size=OrderedPair(x=47.25, y=25.0), rotation=15, opacity=255, name='rectangle 1', type=None, properties=None, template=None), Object(id=2, location=OrderedPair(x=252.5, y=87.75), size=OrderedPair(x=0, y=0), rotation=-21, opacity=255, name='polygon 1', type=None, properties=None, template=None), Object(id=3, location=OrderedPair(x=198.75, y=102.5), size=OrderedPair(x=17.75, y=14.25), rotation=0, opacity=255, name='elipse 1', type=None, properties=None, template=None), Object(id=4, location=OrderedPair(x=174.25, y=186.0), size=OrderedPair(x=0, y=0), rotation=0, opacity=255, name='point 1', type=None, properties=None, template=None), Object(id=7, location=OrderedPair(x=11.3958, y=48.5833), size=OrderedPair(x=107.625, y=27.25), rotation=0, opacity=255, name='insert text 1', type=None, properties=None, template=None), Object(id=6, location=OrderedPair(x=47.25, y=72.5), size=OrderedPair(x=47.0, y=53.0), rotation=31, opacity=255, name='inserted tile 1', type=None, properties={'tile property bool': True}, template=None), Object(id=8, location=OrderedPair(x=144.667, y=112.0), size=OrderedPair(x=0, y=0), rotation=0, opacity=255, name='polyline 1', type=None, properties=None, template=None), Object(id=9, location=OrderedPair(x=69.8333, y=168.333), size=OrderedPair(x=0, y=0), rotation=0, opacity=255, name='polygon 2', type=None, properties=None, template=None)], id=6, name='Object Layer 1', offset=OrderedPair(x=0, y=0), opacity=230, properties=None, color=None, draworder='topdown')], + 'next_layer_id': 16, + 'next_object_id': 10, + 'orientation': 'orthogonal', + 'parent_dir': PosixPath('/home/ben/Projects/arcade/arcade-venv/arcade/tests/test_data'), + 'properties': None, + 'render_order': 'right-down', + 'stagger_axis': None, + 'stagger_index': None, + 'tile_height': 32, + 'tile_sets': { 1: TileSet(name='tile_set_image', max_tile_size=OrderedPair(x=32, y=32), spacing=1, margin=1, tile_count=48, columns=8, tile_offset=None, grid=None, properties=None, image=Image(source='images/tmw_desert_spacing.png', trans=None, width=265, height=199), terrain_types=None, tiles={})}, + 'tile_width': 32, + 'tiled_version': '1.2.3', + 'version': '1.2', + 'width': 10} diff --git a/test/test2.py b/test/test2.py new file mode 100644 index 0000000..5a55bc6 --- /dev/null +++ b/test/test2.py @@ -0,0 +1,27 @@ +import arcade + +import config + +from arcade.tiled import read_tiled_map + +class MyTestWindow(arcade.Window): + def __init__(self, width, height, title): + super().__init__(width, height, title) + self.map = arcade.generate_sprites(my_map, "Tile Layer 1", 1, "./assets/") + + def on_draw(self): + arcade.start_render() + self.map.draw() + + +MAP_NAME = 'assets/tiled_test_4.tmx' +my_map = arcade.read_tiled_map(config.MAP_NAME, 1) + +for row in my_map.layers["Tile Layer 1"]: + for grid_object in row: + print(grid_object) + print( + f"{grid_object.tile.local_id}, {grid_object.center_x}, {grid_object.center_y}") +test = MyTestWindow(640,800,"meme") +test.test() +test.close() diff --git a/test/test_tiled-bak.py b/test/test_tiled-bak.py new file mode 100644 index 0000000..aceb83c --- /dev/null +++ b/test/test_tiled-bak.py @@ -0,0 +1,28 @@ +import arcade +import arcade.tiled + +import pprint + +pp = pprint.PrettyPrinter(indent=4, compact=True, width=200) + +class MyTestWindow(arcade.Window): + def __init__(self, width, height, title, map_name): + super().__init__(width, height, title) + + self.layers = [] + my_map = arcade.tiled.read_tiled_map(map_name, 1) + pp.pprint(my_map.layers_int_data) + for layer in my_map.layers_int_data: + self.layers.append(arcade.tiled.generate_sprites( + my_map, layer, 1, "../arcade/arcade/examples/")) + + def on_draw(self): + arcade.start_render() + for layer in self.layers: + layer.draw() + + +MAP_NAME = '../arcade/arcade/examples/map_base64_gzip.tmx' + +test = MyTestWindow(640, 800, "meme", MAP_NAME) +arcade.run() diff --git a/test/test_tiled.py b/test/test_tiled.py new file mode 100644 index 0000000..4a3cccb --- /dev/null +++ b/test/test_tiled.py @@ -0,0 +1,18 @@ +import pprint +import pickle + +from io import StringIO + +import arcade +import arcade.tiled + + +pp = pprint.PrettyPrinter(indent=4, compact=True, width=100) + +pp = pp.pprint + +MAP_NAME = '/home/ben/Projects/arcade/arcade-venv/arcade/tests/test_data/test_map_image_tile_set.tmx' + +map = arcade.tiled.parse_tile_map(MAP_NAME) + +pp(map.__dict__) diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..0bb7ca5 --- /dev/null +++ b/tests/__init__.py @@ -0,0 +1,3 @@ +import pytest + +pytest.main(["-x", "tests/unit2"]) diff --git a/tests/test_data/images/tmw_desert_spacing.png b/tests/test_data/images/tmw_desert_spacing.png new file mode 100644 index 0000000000000000000000000000000000000000..4e9995c0d95bc53d3dcc4bca2e7ee0b9aa531550 GIT binary patch literal 37830 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 IM6N<$f^W5A%>V!Z literal 0 HcmV?d00001 diff --git a/tests/test_data/test_map_image_tile_set.tmx b/tests/test_data/test_map_image_tile_set.tmx new file mode 100644 index 0000000..63e60f8 --- /dev/null +++ b/tests/test_data/test_map_image_tile_set.tmx @@ -0,0 +1,91 @@ + + + + + +1,2,3,4,5,6,7,8,30,30, +9,10,11,12,13,14,15,16,30,30, +17,18,19,20,21,22,23,24,30,30, +25,26,27,28,29,30,31,32,30,30, +33,34,35,36,37,38,39,40,30,30, +41,42,43,44,45,46,47,48,30,30, +30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30 + + + + +0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,46,0,0,0, +0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0, +0,0,6,7,7,7,7,7,8,0, +0,0,14,15,15,15,15,15,16,0, +0,0,22,23,23,23,23,23,24,0 + + + + + + + + +0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0, +0,31,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0 + + + + +0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0, +1,2,3,0,0,0,0,0,0,0, +9,10,11,0,0,0,0,0,0,0, +17,18,19,0,0,0,0,0,0,0 + + + + + + + + + + + + + + + + Hello World + + + + + + + + + + + + + + diff --git a/tests/test_data/test_map_infinite.tmx b/tests/test_data/test_map_infinite.tmx new file mode 100644 index 0000000..d117bcf --- /dev/null +++ b/tests/test_data/test_map_infinite.tmx @@ -0,0 +1,390 @@ + + + + + + +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30 + + +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30 + + +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30 + + +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30 + + +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30 + + +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,1,2,3, +30,30,30,30,30,30,30,30,30,30,30,30,30,9,10,11, +30,30,30,30,30,30,30,30,30,30,30,30,30,17,18,19 + + +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +4,5,6,7,8,30,30,30,30,30,30,30,30,30,30,30, +12,13,14,15,16,30,30,30,30,30,30,30,30,30,30,30, +20,21,22,23,24,30,30,30,30,30,30,30,30,30,30,30 + + +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30 + + +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30 + + +30,30,30,30,30,30,30,30,30,30,30,30,30,25,26,27, +30,30,30,30,30,30,30,30,30,30,30,30,30,33,34,35, +30,30,30,30,30,30,30,30,30,30,30,30,30,41,42,43, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30 + + +28,29,30,31,32,30,30,30,30,30,30,30,30,30,30,30, +36,37,38,39,40,30,30,30,30,30,30,30,30,30,30,30, +44,45,46,47,48,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30 + + +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30 + + +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30 + + +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30 + + +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30 + + +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30, +30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30 + + + + + + +0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,21, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,28,29, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 + + +0,0,20,21,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,28,29,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 + + +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +20,21,0,0,0,0,0,0,0,0,0,0,0,0,0,0 + + +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +20,21,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +28,29,0,0,0,0,0,0,0,0,0,0,0,0,0,0 + + +28,29,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 + + + + diff --git a/tests/test_data/test_map_simple.tmx b/tests/test_data/test_map_simple.tmx new file mode 100644 index 0000000..f5e2a0d --- /dev/null +++ b/tests/test_data/test_map_simple.tmx @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + +1,2,3,4,5,6,7,8, +9,10,11,12,13,14,15,16, +17,18,19,20,21,22,23,24, +25,26,27,28,29,30,31,32, +33,34,35,36,37,38,39,40, +41,42,43,44,45,46,47,48 + + + diff --git a/tests/test_data/tile_set_image.tsx b/tests/test_data/tile_set_image.tsx new file mode 100644 index 0000000..2f6ef20 --- /dev/null +++ b/tests/test_data/tile_set_image.tsx @@ -0,0 +1,4 @@ + + + + diff --git a/tests/unit2/test_pytiled_parser.py b/tests/unit2/test_pytiled_parser.py new file mode 100644 index 0000000..7bb51a3 --- /dev/null +++ b/tests/unit2/test_pytiled_parser.py @@ -0,0 +1,83 @@ +import os + +from pathlib import Path + +import pytest + +import pytiled_parser + +print(os.path.dirname(os.path.abspath(__file__))) + +os.chdir(os.path.dirname(os.path.abspath(__file__))) + + +def test_map_simple(): + """ + TMX with a very simple tileset and some properties. + """ + map = pytiled_parser.parse_tile_map(Path("../test_data/test_map_simple.tmx")) + + properties = { + "bool property - false": False, + "bool property - true": True, + "color property": (0x49, 0xfc, 0xff, 0xff), + "file property": Path("/var/log/syslog"), + "float property": 1.23456789, + "int property": 13, + "string property": "Hello, World!!" + } + + assert map.version == "1.2" + assert map.tiled_version == "1.2.3" + assert map.orientation == "orthogonal" + assert map.render_order == "right-down" + assert map.width == 8 + assert map.height == 6 + assert map.tile_width == 32 + assert map.tile_height == 32 + assert map.infinite == False + assert map.hex_side_length == None + assert map.stagger_axis == None + assert map.stagger_index == None + assert map.background_color == None + assert map.next_layer_id == 2 + assert map.next_object_id == 1 + assert map.properties == properties + + assert map.tile_sets[1].name == "tile_set_image" + assert map.tile_sets[1].tilewidth == 32 + assert map.tile_sets[1].tileheight == 32 + assert map.tile_sets[1].spacing == 1 + assert map.tile_sets[1].margin == 1 + assert map.tile_sets[1].tilecount == 48 + assert map.tile_sets[1].columns == 8 + assert map.tile_sets[1].tileoffset == None + assert map.tile_sets[1].grid == None + assert map.tile_sets[1].properties == None + assert map.tile_sets[1].terraintypes == None + assert map.tile_sets[1].tiles == {} + + # unsure how to get paths to compare propperly + assert str(map.tile_sets[1].image.source) == ( + "images/tmw_desert_spacing.png") + assert map.tile_sets[1].image.trans == None + assert map.tile_sets[1].image.width == 265 + assert map.tile_sets[1].image.height == 199 + + # assert map.layers == + + +@pytest.mark.parametrize( + "test_input,expected", [ + ("#001122", (0x00, 0x11, 0x22, 0xff)), + ("001122", (0x00, 0x11, 0x22, 0xff)), + ("#FF001122", (0x00, 0x11, 0x22, 0xff)), + ("FF001122", (0x00, 0x11, 0x22, 0xff)), + ("FF001122", (0x00, 0x11, 0x22, 0xff)), + ] +) +def test_color_parsing(test_input, expected): + """ + Tiled has a few different types of color representations. + """ + assert pytiled_parser._parse_color(test_input) == expected