Disclaimer: All code discussed in this post is covered by the GPL. It can be browsed through this link (to the latest release).
About a month ago, I decided to move away from a strict interpretation of "roguelike" for Ighalsk, and add a window with buttons that can be pressed to carry out actions. A friend had pointed out that it was hard to remember which keys did what - something which I've found true for many of the roguelikes that I've played over the years. I'll say a bit about my solution, but first some background.
In Ighalsk, I've taken a perhaps overly literal interpretation of the "Model-View-Controller" pattern. The source code is organised into directories called "Model", "View" and "Controller" (there's also a "Common" directory for classes used by both Model and Controller classes). For each screen of Ighalsk (each state of the controller), there are corresponding View and Controller classes. For example, there are ShopViewer and ShopController classes which handle viewing items on display in a shop and buying an item. (There's also a Shop class, in the "Model" directory, which holds item names and prices and can return an item if it is bought.)
Which View class is active (displaying text on the screen) at any given time is handled by a high-level class called IghalskViewer; similarly, which Controller class is called is handled by IghalskController.
Back to my recent changes. Stating the obvious, I called the class implementing the window with the buttons "ListOfButtons". All it really does is display a list of labeled buttons, and call methods when the buttons are pressed; but what the buttons say and do changes as the controller state changes throughout the game.
Each Controller class now has a "getEvents" method which returns a list of strings. The ListOfButtons class takes this list and adds a button to its window for each string. Each button is configured so that when it is pressed, the "sendEvent" method of the currently active Controller will be called, with that string as a parameter to the method.
(Rather than add the full functionality to each Controller class at once, in a huge, big-bang mountain of coding, I did it step by step. I started by creating an AbstractController class and made each Controller class a subclass of it: the AbstractController has a default getEvents method which returns an empty list, and a sendEvent method which does nothing. Thus at this stage, there was a window for the list of buttons, but there were no buttons on it. I then filled in the details of getEvents and sendEvent one class at a time, so that gradually more and more of them supported the list of buttons, and the list of buttons was populated more and more often while playing the game. As I went, I added a unit test for the class I was modifying.)
Each Controller also needs to respond to keys being pressed, by implementing a "processKey" method. This method now calls "sendEvent" with an appropriate event description. (This is to reduce duplication, following the "Don't repeat yourself" principle - see the Ighalsk Coding Guidelines.)
The game should be able to change screens (display a different view of the character, their possessions, or the world around them) as requested by the player, either through pressing a key or clicking on a button. In the source code I've called this "changing the state" of the controller and the view, and it happens through calling the "changeState" method of IghalskController. (I'm of the view that it's impossible to be too obvious with names.) This is done through a call-back type thing.
So there's a connection between events and states, in that for some events (buttons), the game (controller) should change to a different state. There's also a connection between keys and events, in that pressing a key is equivalent to sending a particular event.
Again exercising my gift for stating the obvious, I wrote a class called "KeyEventStateDict" to hold these connections, available to all controller classes. It's initialised with a list of tuples [(key, event, state)] and stores these internally. It then provides a "getEvent" method which is passed a key, and a "getState" method which is passed an event. It also provides a "getEvents" method, which returns a list of events in the same order as the initial list of tuples. (Internally it holds two dicts, hence the "Dict" suffix to its name.)
These methods abstract away a lot of the code needed to handle both pressing keys and clicking on buttons. But not all of it: several of the controller classes still need some code to handle special cases - to take actions, move, change what the character is wearing and so on. Otherwise, all the player would be able to do would be to change from one screen to another.
Adding a list of buttons to each state (screen) was not only adding a useful piece of functionality; it also gave me the chance to simplify the controller classes and to add unit tests for each, thus setting up infrastructure to make refactoring easier in future.
This was the biggest change between release 0.1.3 and 0.1.4. Next will come a Palace holding a Queen who asks our Hero to perform Quests ...
No comments:
Post a Comment