Hello again! This is post 3 on the development progress of my Saoirse (pronounced “Seer-Sha”) game.

Here’s a quick summary of what I’ve done since the last post:

1. I fixed the issue with the invalid check and the infinite recusive function call.

2. I removed the invalid and blank value system altogether, which is how I achieved the fix in change one.

The removal of the invalid and blank value system should simplify a lot of code and shouldn’t be much of a loss.

Here’s the current code:

# This file is:
# src/python/saoirse_server.py

from dataclasses import dataclass

@dataclass(frozen=True)
class Logger():
    log_level_info="info"
    log_level_warn="warn"
    log_level_error="error"

    def log_msg(sender="Nobody", msg="This is a test message, please ignore it!", level=log_level_info, should_print=True):
        formatted_msg = f"Log: Sender = {sender}: Level = {level}: Message =\n{msg}\n"
        if should_print:
            print(formatted_msg)
        return formatted_msg


class VarHolder():
    def __init__(self):
        pass


class Identifier():
    def __init__(self, path="__blank__", delimiter="/"):
        self.set_delimiter(delimiter)
        self.set_path(path)

    def set_path(self, new_path_in, update_self=True):
        new_path = new_path_in
        if isinstance(new_path_in, list):
            for i, part in enumerate(new_path):
                if isinstance(part, str):
                    if self.get_delimiter() in part:
                        fixed = part.split(self.get_delimiter())
                        new_path.pop(i)
                        fixed.reverse()
                        for fixed_part in fixed:
                            new_path.insert(i, fixed_part)
        elif isinstance(new_path_in, str):
            new_path = new_path_in.split(self.get_delimiter())

        if update_self:
            self.path = new_path
            return self
        return Identifier(new_path)

    def set_delimiter(self, new_delimiter_in, update_self=True):
        if isinstance(new_delimiter_in, str):
            new_delimiter = new_delimiter_in
        else:
            new_delimiter = "/"

        if update_self:
            self.delimiter = new_delimiter
            return self
        return Identifier(delimiter=new_delimiter)

    def get_path(self):
        return self.path

    def get_delimiter(self):
        return self.delimiter

    def get_path_str(self):
        return self.get_delimiter().join(self.get_path())

    def is_equal(self, other_in):
        return isinstance(other_in, Identifier) and other_in.get_path() == self.get_path()

    def append(self, other_path_in, update_self=True):
        if isinstance(other_path_in, list):
            new_path = self.path.copy()
            new_path.extend(other_path_in)
            if update_self:
                self.set_path(new_path)
                return self
            return Identifier(new_path)

    def extend(self, other_in, update_self=True):
        if isinstance(other_in, Identifier):
                new_path = self.path.copy()
                new_path.extend(other_in.get_path())
                if update_self:
                    self.set_path(new_path)
                    return self
                return Identifier(new_path)

    def copy(self):
        return Identifier(self.get_path(), self.get_delimiter())

    def get_id_from_str_or_id(ide):
        if isinstance(ide, str) or isinstance(ide, list):
            return Identifier(ide)
        return ide


class IdentifierObjGetter():
    def __init__(self, id_in=Identifier(), obj_getter_in=None):
        self.set_id(id_in)

        self.obj_getter = obj_getter_in

    def is_equal(self, other_in):
        return isinstance(other_in, IdentifierObjGetter) and self.get_id().is_equal(other_in.get_id()) and self.get_obj() == other_in.get_obj()

    def copy(self):
        return IdentifierObjGetter(self.get_obj(), self.get_id().copy())

    def set_id(self, id_in):
        self.id = Identifier.get_id_from_str_or_id(id_in)

    def get_id(self):
        return self.id

    def get_obj(self):
        if callable(self.obj_getter):
            return self.obj_getter()
        return None

    def __str__(self):
        return f"{self.id.get_path_str()} : {self.obj_getter}"


class BaseRegistry():
    def __init__(self):
        self.entries = {}

    def get_entries(self):
        return self.entries

    def get_entry(self, id_in):
        ide = Identifier.get_id_from_str_or_id(id_in)

        if self.contains_id(ide):
            return self.entries[ide.get_path_str()]
        return None

    def contains_id(self, id_in):
        ide = Identifier.get_id_from_str_or_id(id_in)
        return ide.get_path_str() in self.get_entries().keys()

    def register_id_obj(self, id_in, obj_getter_in):
        if isinstance(id_in, Identifier):
            self.register_id_obj_pair(IdentifierObjGetter(id_in, obj_getter_in))

    def register_id_obj_pair(self, id_obj_pair_in):
        if isinstance(id_obj_pair_in, IdentifierObjGetter):
            ide = id_obj_pair_in.get_id()
            if isinstance(ide, Identifier):
                id_str = ide.get_path_str()
                if id_str in self.get_entries().keys():
                    Logger.log_msg(sender="Registry", msg=f"Failed to register {id_obj_pair_in} as its id of {id_str} is alread registered!", level=Logger.log_level_warn)
                else:
                    self.entries[id_str] = id_obj_pair_in
        else:
            Logger.log_msg(sender="Registry", msg=f"Failed to register {id_obj_pair_in} as it is not an IdentifierObjectGetter!", level=Logger.log_level_warn)


class BaseCategorizedRegistry(BaseRegistry):
    def __init__(self):
        super().__init__()

    def register_category(self, category_id_in):
        category_id = Identifier.get_id_from_str_or_id(category_id_in)
        self.register_id_obj(category_id, BaseRegistry())

    def get_categories(self):
        return super().get_entries()

    def get_entries(self):
        entries_out = {}
        for key in self.get_categories().keys():
            category = self.get_categories()[key]
            if isinstance(category, BaseRegistry):
                for entry in category.get_entries():
                    if isinstance(entry, IdentifierObjGetter):
                        categorized_entry = entry.copy()
                        entry_id = categorized_entry.get_id().copy()
                        entry_id_path = entry_id.get_path()
                        entry_id_path.insert(0, key)
                        categorized_entry.set_id(Identifier(entry_id_path, entry_id.get_delimiter()))
                        entries_out[categorized_entry.get_id().get_path_str()] = categorized_entry
        return entries_out

    def get_category(self, category_id_in):
        category_id = Identifier.get_id_from_str_or_id(category_id_in)
        if self.contains_id(category_id):
            return self.categories[category_id_in]
        return None

    def register_id_obj_getter_under_category(self, id_obj_pair_in, category_id_in):
        if isinstance(id_obj_pair_in, IdentifierObjGetter):
            if category_id_in not in self.get_categories().keys():
                self.register_category(category_id_in)
            self.register_id_obj_pair(IdentifierObjGetter(id_obj_pair_in, Identifier([category_id_in].extend(id_obj_pair_in.get_id().get_path()))))

    def register_id_obj_under_category(self, id_in, category_id_in, obj_getter_in):
        self.register_id_obj_getter_under_category(IdentifierObjGetter(id_in, obj_getter_in), category_id_in)

    def get_id_obj_pair_under_category(self, category_id_in, obj_id_in):
        category_id = Identifier.get_id_from_str_or_id(category_id_in)
        obj_id = Identifier.get_id_from_str_or_id(obj_id_in)
        return self.get_entry(category_id.extend(obj_id_in, False))


class ThreeDimensionalPosition():
    def __init__(self, x, y, z):
        self.x = x
        self.y = y
        self.z = z

    def get_origin():
        return ThreeDimensionalPosition(0, 0, 0)

    def get_x(self):
        return self.x

    def get_y(self):
        return self.y

    def get_z(self):
        return self.z

    @dataclass(frozen=True)
    class Directions():
        up="up"
        down="down"
        north="north"
        south="south"
        east="east"
        west="west"

    def offset(self, direction, distance):
        if direction == ThreeDimensionalPosition.Directions.up:
            return ThreeDimensionalPosition(self.get_x(), self.get_y() + distance, self.get_z())

        if direction == ThreeDimensionalPosition.Directions.down:
            return ThreeDimensionalPosition(self.get_x(), self.get_y() - distance, self.get_z())

        if direction == ThreeDimensionalPosition.Directions.north:
            return ThreeDimensionalPosition(self.get_x() + distance, self.get_y(), self.get_z())

        if direction == ThreeDimensionalPosition.Directions.south:
            return ThreeDimensionalPosition(self.get_x() - distance, self.get_y(), self.get_z())

        if direction == ThreeDimensionalPosition.Directions.east:
            return ThreeDimensionalPosition(self.get_x(), self.get_y(), self.get_z() + distance)

        if direction == ThreeDimensionalPosition.Directions.west:
            return ThreeDimensionalPosition(self.get_x(), self.get_y(), self.get_z() - distance)

        return self

    def get_dict(self):
        return {"x": self.get_x(), "y": self.get_y(), "z": self.get_z()}

    def get_str(self):
        return str(self.get_dict())

    def from_dict(pos_dict):
        return ThreeDimensionalPosition(pos_dict.get("x"), pos_dict.get("y"), pos_dict.get("z"))

    def from_str(pos_str):
        return ThreeDimensionalPosition.from_dict(dict(pos_str))

    def __str__(self):
        return self.get_str()

    def __eq__(self, other):
        return self.get_x() == other.get_x() and self.get_y() == other.get_y() and self.get_z() == other.get_z()


class GameObject():
    def __init__(self, ide, dimension=None):
        self.ide = ide
        self.dimension = dimension

    def get_id(self):
        return self.ide

    def get_dimension(self):
        return self.dimension

    def tick(self):
        pass


class SpaceGameObject(GameObject):
    def __init__(self, ide, pos=ThreeDimensionalPosition.get_origin(), dimension=None):
        super().__init__(ide, dimension=dimension)
        self.pos = pos

    def set_pos(self, pos):
        self.pos = pos

    def get_pos(self):
        return self.pos

    def has_gravity(self):
        return True


class Tile(SpaceGameObject):
    def __init__(self, ide, pos=ThreeDimensionalPosition.get_origin(), dimension=None):
        super().__init__(ide, pos, dimension)


class Entity(SpaceGameObject):
    def __init__(self, ide, pos=ThreeDimensionalPosition.get_origin(), dimension=None):
        super().__init__(ide, pos, dimension)


class Item(GameObject):
    def __init__(self, ide, dimension=None):
        super().__init__(ide, dimension)


class ThreeDimensionalSpace():
    def __init__(self, dimension):
        self.tiles = []
        self.gravity_speed = 1
        self.dimension = dimension

    def get_tiles(self):
        return self.tiles

    def get_gravity_speed(self):
        return self.gravity_speed

    def get_dimension(self):
        return self.dimension

    def add_tile_at_pos(self, pos, tile):
        tile.set_pos(pos)
        tile.set_world(self.get_dimension())
        self.tiles.append(tile)

    def remove_tile_at_pos(self, pos, check_tile=None):
        for tile in self.get_tiles_at_pos(pos, check_tile):
            self.tiles.remove(tile)

    def set_tile_at_pos(self, pos, tile):
        self.remove_tile_at_pos(pos, tile)
        self.add_tile_at_pos(pos, tile)

    def get_tiles_at_pos(self, pos, check_tile=None):
        tiles = []
        for tile in self.get_tiles():
            if tile.get_pos() == pos and (check_tile == tile or check_tile is None):
                tiles.append(tile)
        return tiles

    def tick_tile_gravity(self, tile):
        if tile.has_gravity():
            tile.set_pos(tile.get_pos().offset(ThreeDimensionalPosition.Directions.down, self.get_gravity_speed()))

    def tick(self):
        for tile in self.get_tiles():
            tile.tick()
            self.tick_tile_gravity(tile)
# This file is:
# src/python/saoirse_server.py

from saoirse_base import VarHolder, Identifier, IdentifierObjGetter, BaseCategorizedRegistry, Logger, GameObject, Item, Tile, Entity
from dataclasses import dataclass

saoirse_id = "saoirse"

class SaoirseRegistry(BaseCategorizedRegistry):
    def __init__(self):
        super().__init__()

        self.category_tags = self.SaoirseRegistryCategories()

        self.items = VarHolder()
        self.items.hi = ""

        self.register_items()
        self.register_tiles()
        self.register_entities()

    def register_game_obj_under_category(self, ide, category_ide, game_obj_getter):
        if isinstance(game_obj_getter, GameObject):
            self.register_id_obj_under_category(ide, category_ide, game_obj_getter)
        return ide

    def register_items(self):
        self.register_category(self.category_tags.items)

        for ide_str in ["dirt", "stone", "gravel", "oak_log"]:
            ide = Identifier([saoirse_id, ide_str])
            self.register_item(ide, lambda: Item(ide))

    def register_item(self, ide, item_obj_getter=None):
        if item_obj_getter is None:
            item_obj_getter = lambda: Item(ide)
        self.register_id_obj_under_category(ide, self.category_tags.items, item_obj_getter)

    def register_tiles(self):
        self.register_category(self.category_tags.tiles)

    def register_tile(self, ide, tile_obj):
        self.register_id_obj_under_category(ide, self.category_tags.tiles, tile_obj)

    def register_entities(self):
        self.register_category(self.category_tags.entities)

    def register_entity(self, ide, entity_obj):
        self.register_id_obj_under_category(ide, self.category_tags.entities, entity_obj)

    @dataclass(frozen=True)
    class SaoirseRegistryCategories():
        items = "items"
        tiles = "tiles"
        entities = "entities"


registry = SaoirseRegistry()

Logger.log_msg(saoirse_id, registry.get_categories())
Logger.log_msg(saoirse_id, registry.get_entries())

class SaoriseDimension():
    def __init__(self):
        pass