DEV Community 👩‍💻👨‍💻

Cover image for LGTM Devlog 19: Game data/quest storage
Yuan Gao
Yuan Gao

Posted on

LGTM Devlog 19: Game data/quest storage

Not a lot of progress since last time, I'm still mulling over the mechanics of how data will be stored. One thing on my mind is how to handle updates - when I inevitably have to update or patch the quest data when people are already playing it.

I figured I should use semver as a convention, and manually update the version. At least that will tell us when there are version problems. Perhaps it could be used to notify me/the player, perhaps we can restart the quest, or maintain multiple copies of the quest as needed.

Semver

To do this I will use the package python-semver to easily handle it. I'll define a function that I can run on the current save data version, and the new code data version, to say whether this upgrade is safe or not:

def semver_safe(start: VersionInfo, dest: VersionInfo) -> bool:
    """ whether semver loading is going to be safe """
    if start.major != dest.major:
        return False

    # check it's not a downgrade of minor version
    if start.minor > dest.minor:
        return False

    return True
Enter fullscreen mode Exit fullscreen mode

So, my rules are simply: major version numbers must match. An upgrade in minor version number is also ok (but not downgrade, but this should be rare)

Test are accordingly written:

@pytest.mark.parametrize("start, dest", [
    ("1.1.1", "1.1.2"), # patch increment is ok
    ("1.1.2", "1.1.1"), # patch decrement is ok
    ("1.1.1", "1.2.1"), # minor increment is ok
    ("1.1.1", "1.2.2"), # minor+patch bump is ok
    ("1.1.2", "1.2.1"), # minor+patch bump is ok
])
def test_semver_safe(start, dest):
    """ Tests semver safe loading"""

    start = VersionInfo.parse(start)
    dest = VersionInfo.parse(dest)
    assert semver_safe(start, dest) == True

@pytest.mark.parametrize("start, dest", [
    ("2.1.1", "1.1.1"), # major increment not safe
    ("1.1.1", "2.1.1"), # major decement not safe
    ("1.2.1", "1.1.1"), # minor decrement not safe (no forward compatibility guarantee)
])
def test_semver_unsafe(start, dest):
    """ Tests semver unsafe loading"""

    start = VersionInfo.parse(start)
    dest = VersionInfo.parse(dest)
    assert semver_safe(start, dest) == False
Enter fullscreen mode Exit fullscreen mode

Quest data storage

I think I will have each quest as a python object, eventually it'll have each of the stages of the quest in it, which when executed allows the game to load the player's game data, find the player's current position in the quest, and trigger the next action as appropriate.

So I need each quest object (and how we select and invoke the quest object I'll figure out later) to be able to load the player's data, check the save data version against the code version using the semver_safe function above, and update the data. So far, it looks like this:

class Quest:
    def __init__(self, version: str, quest_data: dict):
        self.semver = VersionInfo.parse(version)
        self.quest_data = quest_data

    def load(self, save_data: dict) -> None:
        """ Load save data back into structure """

        # check save version is safe before upgrading
        save_semver = VersionInfo.parse(save_data[VERSION_KEY])
        if not semver_safe(save_semver, self.semver):
            raise QuestLoadError(f"Unsafe version mismatch! {save_semver} -> {self.semver}")

        self.quest_data.update(save_data)

    def update_save_data(self) -> dict:
        """ Updates save data with new version and output """

        self.quest_data[VERSION_KEY] = self.semver
        return self.quest_data
Enter fullscreen mode Exit fullscreen mode

With the corresponding tests:

@pytest.fixture
def quest():
    return Quest("2.2.2", {"a": 1})

def test_quest_load_fail(quest):
    """ Tests a quest load fail due to semver mismatch """

    bad_version_quest_data = {
        VERSION_KEY: "1.2.2",
        "a": 2
    }
    with pytest.raises(QuestLoadError):
        quest.load(bad_version_quest_data)

def test_quest_load_save(quest):
    """ Tests a quest load fail due to semver mismatch """

    quest_data = {
        VERSION_KEY: "2.1.1",
        "a": 2
    }

    quest.load(quest_data)
    assert quest.update_save_data()["a"] == quest_data["a"]

Enter fullscreen mode Exit fullscreen mode

Not very exciting, I know.


Next, I need to figure out how these quests are stored and triggered

Top comments (0)

Take a look at this:

Settings

Go to your customization settings to nudge your home feed to show content more relevant to your developer experience level. 🛠