Homunculus Devlog 2018-05-26
In this post:
- I rewrote the save and load code a little.
- I then started pondering the path forward I need to make GameStates more flexible.
- I extended the Visitor code I wrote, in... somewhat strange ways.
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.
- The "modal" states must be instances of some form of class.
- This means that the visitor code that's keying off of GameStates right now needs to be more like a typical visitor implementation, and look at the class.
- This means that the "game flow" states need their own classes.
- But this is aesthetically problematic, because they have no associated state.
- Then again, neither does the basic AI.
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.