Skip to content

Commit

Permalink
gid and tests
Browse files Browse the repository at this point in the history
  • Loading branch information
JaskRendix committed Aug 26, 2024
1 parent 7af805b commit b06264c
Show file tree
Hide file tree
Showing 2 changed files with 185 additions and 9 deletions.
24 changes: 16 additions & 8 deletions pytmx/pytmx.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
License along with pytmx. If not, see <https://www.gnu.org/licenses/>.
"""

from __future__ import annotations

import gzip
Expand Down Expand Up @@ -54,6 +55,8 @@
"convert_to_bool",
"resolve_to_class",
"parse_properties",
"decode_gid",
"unpack_gids",
)

logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -85,6 +88,7 @@
TiledLayer = Union[
"TiledTileLayer", "TiledImageLayer", "TiledGroupLayer", "TiledObjectGroup"
]
flag_cache: dict[int, TileFlags] = {}

# need a more graceful way to handle annotations for optional dependencies
if pygame:
Expand Down Expand Up @@ -125,15 +129,19 @@ def decode_gid(raw_gid: int) -> tuple[int, TileFlags]:
"""
if raw_gid < GID_TRANS_ROT:
return raw_gid, empty_flags
return (
raw_gid & ~GID_MASK,
# TODO: cache all combinations of flags
TileFlags(
raw_gid & GID_TRANS_FLIPX == GID_TRANS_FLIPX,
raw_gid & GID_TRANS_FLIPY == GID_TRANS_FLIPY,
raw_gid & GID_TRANS_ROT == GID_TRANS_ROT,
),

# Check if the GID is already in the cache
if raw_gid in flag_cache:
return raw_gid & ~GID_MASK, flag_cache[raw_gid]

# Calculate and cache the flags
flags = TileFlags(
raw_gid & GID_TRANS_FLIPX == GID_TRANS_FLIPX,
raw_gid & GID_TRANS_FLIPY == GID_TRANS_FLIPY,
raw_gid & GID_TRANS_ROT == GID_TRANS_ROT,
)
flag_cache[raw_gid] = flags
return raw_gid & ~GID_MASK, flags


def reshape_data(
Expand Down
170 changes: 169 additions & 1 deletion tests/pytmx/test_pytmx.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,24 @@
import unittest

import pytmx
from pytmx import TiledElement, convert_to_bool
import base64
import gzip
import zlib
import struct
from pytmx import (
TiledElement,
TiledMap,
convert_to_bool,
TileFlags,
decode_gid,
unpack_gids,
)

# Tiled gid flags
GID_TRANS_FLIPX = 1 << 31
GID_TRANS_FLIPY = 1 << 30
GID_TRANS_ROT = 1 << 29
GID_MASK = GID_TRANS_FLIPX | GID_TRANS_FLIPY | GID_TRANS_ROT


class TestConvertToBool(unittest.TestCase):
Expand Down Expand Up @@ -64,6 +81,15 @@ def test_non_boolean_number_raises_error(self) -> None:
with self.assertRaises(ValueError):
convert_to_bool("200")

def test_edge_cases(self):
# Whitespace
self.assertTrue(convert_to_bool(" t "))
self.assertFalse(convert_to_bool(" f "))

# Numeric edge cases
self.assertTrue(convert_to_bool(1e-10)) # Very small positive number
self.assertFalse(convert_to_bool(-1e-10)) # Very small negative number


class TiledMapTest(unittest.TestCase):
filename = "tests/resources/test01.tmx"
Expand Down Expand Up @@ -170,3 +196,145 @@ def test_reserved_names_check_disabled_with_option(self) -> None:
def test_repr(self) -> None:
self.element.name = "foo"
self.assertEqual('<TiledElement: "foo">', self.element.__repr__())


class TestDecodeGid(unittest.TestCase):
def test_no_flags(self):
raw_gid = 100
expected_gid, expected_flags = 100, TileFlags(False, False, False)
self.assertEqual(decode_gid(raw_gid), (expected_gid, expected_flags))

def test_individual_flags(self):
# Test for each flag individually
test_cases = [
(GID_TRANS_FLIPX + 1, 1, TileFlags(True, False, False)),
(GID_TRANS_FLIPY + 1, 1, TileFlags(False, True, False)),
(GID_TRANS_ROT + 1, 1, TileFlags(False, False, True)),
]
for raw_gid, expected_gid, expected_flags in test_cases:
self.assertEqual(decode_gid(raw_gid), (expected_gid, expected_flags))

def test_combinations_of_flags(self):
# Test combinations of flags
test_cases = [
(GID_TRANS_FLIPX + GID_TRANS_FLIPY + 1, 1, TileFlags(True, True, False)),
(GID_TRANS_FLIPX + GID_TRANS_ROT + 1, 1, TileFlags(True, False, True)),
(GID_TRANS_FLIPY + GID_TRANS_ROT + 1, 1, TileFlags(False, True, True)),
(
GID_TRANS_FLIPX + GID_TRANS_FLIPY + GID_TRANS_ROT + 1,
1,
TileFlags(True, True, True),
),
]
for raw_gid, expected_gid, expected_flags in test_cases:
self.assertEqual(decode_gid(raw_gid), (expected_gid, expected_flags))

def test_edge_cases(self):
# Maximum GID
# max_gid = 2**32 - 1
# self.assertEqual(decode_gid(max_gid), (max_gid & ~GID_MASK, TileFlags(False, False, False)))

# Minimum GID
min_gid = 0
self.assertEqual(decode_gid(min_gid), (min_gid, TileFlags(False, False, False)))

# GID with all flags set
gid_all_flags = GID_TRANS_FLIPX + GID_TRANS_FLIPY + GID_TRANS_ROT + 1
self.assertEqual(decode_gid(gid_all_flags), (1, TileFlags(True, True, True)))

# GID with flags in different orders
test_cases = [
(GID_TRANS_FLIPX + GID_TRANS_FLIPY + 1, 1, TileFlags(True, True, False)),
(GID_TRANS_FLIPY + GID_TRANS_FLIPX + 1, 1, TileFlags(True, True, False)),
(GID_TRANS_FLIPX + GID_TRANS_ROT + 1, 1, TileFlags(True, False, True)),
(GID_TRANS_ROT + GID_TRANS_FLIPX + 1, 1, TileFlags(True, False, True)),
]
for raw_gid, expected_gid, expected_flags in test_cases:
self.assertEqual(decode_gid(raw_gid), (expected_gid, expected_flags))


class TestRegisterGid(unittest.TestCase):
def setUp(self):
self.tmx_map = TiledMap()

def test_register_gid_with_valid_tiled_gid(self):
gid = self.tmx_map.register_gid(123)
self.assertIsNotNone(gid)

def test_register_gid_with_flags(self):
flags = TileFlags(1, 0, 1)
gid = self.tmx_map.register_gid(456, flags)
self.assertIsNotNone(gid)

def test_register_gid_zero(self):
gid = self.tmx_map.register_gid(0)
self.assertEqual(gid, 0)

def test_register_gid_max_gid(self):
max_gid = self.tmx_map.maxgid
self.tmx_map.register_gid(max_gid)
self.assertEqual(self.tmx_map.maxgid, max_gid + 1)

def test_register_gid_duplicate_gid(self):
gid1 = self.tmx_map.register_gid(123)
gid2 = self.tmx_map.register_gid(123)
self.assertEqual(gid1, gid2)

def test_register_gid_duplicate_gid_different_flags(self):
gid1 = self.tmx_map.register_gid(123, TileFlags(1, 0, 0))
gid2 = self.tmx_map.register_gid(123, TileFlags(0, 1, 0))
self.assertNotEqual(gid1, gid2)

def test_register_gid_empty_flags(self):
gid = self.tmx_map.register_gid(123, TileFlags(0, 0, 0))
self.assertIsNotNone(gid)

def test_register_gid_all_flags_set(self):
gid = self.tmx_map.register_gid(123, TileFlags(1, 1, 1))
self.assertIsNotNone(gid)

def test_register_gid_repeated_registration(self):
gid1 = self.tmx_map.register_gid(123)
gid2 = self.tmx_map.register_gid(123)
self.assertEqual(gid1, gid2)


class TestUnpackGids(unittest.TestCase):
def test_base64_no_compression(self):
gids = [123, 456, 789]
data = struct.pack("<LLL", *gids)
text = base64.b64encode(data).decode("utf-8")
result = unpack_gids(text, encoding="base64")
self.assertEqual(result, gids)

def test_base64_gzip_compression(self):
gids = [123, 456, 789]
data = struct.pack("<LLL", *gids)
compressed_data = gzip.compress(data)
text = base64.b64encode(compressed_data).decode("utf-8")
result = unpack_gids(text, encoding="base64", compression="gzip")
self.assertEqual(result, gids)

def test_base64_zlib_compression(self):
gids = [123, 456, 789]
data = struct.pack("<LLL", *gids)
compressed_data = zlib.compress(data)
text = base64.b64encode(compressed_data).decode("utf-8")
result = unpack_gids(text, encoding="base64", compression="zlib")
self.assertEqual(result, gids)

def test_base64_unsupported_compression(self):
text = "some_base64_data"
with self.assertRaises(ValueError):
unpack_gids(text, encoding="base64", compression="unsupported")

def test_csv(self):
gids = [123, 456, 789]
text = ",".join(map(str, gids))
result = unpack_gids(text, encoding="csv")
self.assertEqual(result, gids)

def test_unsupported_encoding(self):
text = "some_data"
with self.assertRaises(ValueError):
unpack_gids(text, encoding="unsupported")

0 comments on commit b06264c

Please sign in to comment.