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
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
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
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"]
Not very exciting, I know.
Next, I need to figure out how these quests are stored and triggered
Top comments (0)