Homunculus Devlog 2018-05-26

By Max Woerner Chase

In this post:


All right. I've just converted the save format into being a single versioned object. This took some adjustment and fiddly stuff to maintain the save file I'm testing stuff in. (I cheated a little, I think, by writing an upgrade path, then stripping it out.) This means that, once I have test data for all of the things the YAML can represent, I can then get coverage on this, and iterate on the structure.

*Cue Benny Hill chase scene as I get chased by a horde of orcs and trolls, while looking for a confusion scroll.*

Okay, I've got a confused monster and a test using the save file. Next step is to look into messing with the GameStates structure. GameStates can be divided into "game flow" (PLAYERS_TURN, ENEMY_TURN, PLAYER_DEAD) and "modal" (SHOW_INVENTORY, DROP_INVENTORY, TARGETING). However I change the representation of this stuff, it's going to get a little weird. So, I'll list some goals, to see if I can get a handle on it.

Okay, step one in this next bit is to put together a conventional Visitor implementation. Step two is to convert the GameStates from an enum into a bunch of classes. That will be somewhat problematic, because I need to make sure that I don't break the serialization code.

As an aside, it's possible that my extensive use of generators gave the impression that I'm just crowbarring in fancy stuff for the sake of fanciness. In that light, it should seem odd that I'm removing a usage of Enum that the tutorial explicitly put in, since Enum is a fancy new 3.4 feature. The truth is, I've just cultivated a strong sense of when a design demands a certain feature, and the design I'm iterating towards just does not work with Python enums in this context. Rust enums, maybe...

I never give myself enough time for these coding posts. That might change in the future, but for now, I'm going to work out the Visitor code I want, then call it a night.

Okay, I've got some kind of horrible desire to be quite generic, so I'm going to try to extend the current visitor with the idea of creating a "match generator" that produces multiple keys from a single input, in order from most to least specific.

 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
def identity(state):
    yield state


def classes(state):
    yield from state.__class__.__mro__


class Visitor:

    def __init__(self, default=None, matches=identity):
        self.state_methods = {}
        self.default = default or self._default
        self.matches = matches

    def __call__(self, func):
        return Visitor(func, self.matches)

    def _default(self, state, *args, **kwargs):
        raise NotImplementedError(f'Not implemented for {state}')

    def bind(self, state):
        def inner_bind(function):
            self.state_methods[state] = function
            return function
        return inner_bind

    def visit(self, state, *args, **kwargs):
        for match in self.matches(state):
            if match in self.state_methods:
                visitor = self.state_methods[match]
                break
        else:
            visitor = self.default
        return visitor(state, *args, **kwargs)

Well. That kind of gives me the heebie-jeebies. I should take a break here, just to reflect on what I just wrote.

(It occurs to me that I could make this even worse, like by constructing the full list of matches, and replicating super() in some way to delegate between them, but I'll hold off for now, since I arguably don't even need some of the stuff I've written here already.)


Next week, I dig deeper into what's bothering me about that code, and then try to tease apart the GameStates enum into a class hierarchy.

I've also got some ideas I want to just let bounce around in my head. Like, what if the dumper code lived next to the associated class, and the loader functions handled their associated imports? I'm interested in reorganizing the imports somehow, and I think something along those lines has potential, in terms of eliminating top-level imports in the serialization code, so it can be imported freely.