During my month off for the baby, I managed to go for two-and-a-half weeks without programming. But the bug bit, so here's my first post about it. It's a
Roguelike, which is an ASCII character game, initially a way to play an obsessive single player D&D-style game on mainframes in computer labs. My animated LJ icon is an example of gameplay.
I decided to apply my professional standards of programming to my hobby project, with awesome results so far.
I'm not a fan of hacking code, so I've been doing a lot of planning on paper before I code things up. Don't get me wrong, I'm actually coding! I've implemented everything below except the Request/Action system. Nerd-speak ahoy!
Input/Output
The input and output are handled via
Singletons, CInput and CDisplay, which actually instantiate a child class. This is so I can switch out different input and output libraries. Both are currently using the
Curses library, so CInputCurses and CDisplayCurses call on the CCursesInterface singleton to perform a few things (like initializing the terminal twice).
All the "graphics" are done via a CGlyph data type, which is currently a character (char data type) and a color (enum). So a large rat would be a light gray 'r', a skeleton a white 'z', and so on. If I moved to a 2D display, I could instead display a rat sprite and a skeleton sprite when given the char/color combo.
The drawback to the Glyph system is representing two different entities with the same character/color combo. This maps fine from Glyph to colored ASCII, but if you have a light gray 'r' meaning both "large rat" and "skeletal roc", there's no way for a graphical (sprite, 3d model) engine to discern based on a Glyph alone.
If I ever need to fix this, a Glyph will simply use a vast enum of glyph types, from tiles to entities. And I can keep the GetCharacter and GetColor accessors to remain compatible with a text display.
The CInput singleton holds a queue of CInput::KEY_ID values. So you press 'F' and KEY_F gets placed in the queue. This would make a purely-joystick driven interface difficult, but go figure. If I ever want to go a non-keyboard route, the entire game interface would have to change.
Real-time code, Turn-based gameplay
I made an early design decision to have the code run as a loop (throttled at a whatever fps you want), even though the player inputs one turn at a time. This is so I can have neat animated effects and non-player movement that runs at a framerate, rather than the typical "As fast as your CPU". I've seen effects in other Roguelikes that zip by so fast you'd miss it if you weren't paying attention.
UI using Composite design pattern
This is probably old-hat to any solid C++ programmers, but I'm relatively new to this stuff. I've created a CUIObject class that exists as a simple
Composite Pattern, meaning it has virtual methods (Draw, Input, Update) and a list of CUIObject pointer children.
This means I can create different CUIObject subclasses, and they can all contain each other as children. The base Draw method (for example) calls the Draw method for all its children. I've already written a ton of CUIObject subclasses, which is a breeze using this Composite pattern:
- CUIWindow: In addition to CUIObject children, a CUIWindow has a specific CUIWindow child. I'm not sure how well this will work out, but I'm hoping to use it for modal windowing (i.e., the Inventory window displays over the Play window and disallows input to the Play window).
- CUIBorder, CUITitle: Simple embellishment classes. You have to make sure they're in the right order, so the CUIBorder doesn't draw over CUITitle!
- CUICommandHandler: Invisible UI component which reads the input buffer's keys and sends appropriate commands. You initialize it with a set of key, command pairs.
- CUIAnyKeyCommandHandler: Simply takes the first valid key in the buffer and sends a single command. Used mostly for "Press any key to continue" situations.
- CUIMenu: Set it up with text, key, command sets and it displays a menu. This pretty much just prints text and holds a CUICommandHandler chid.
- CUISpinner: A simple animation test, it's a little animated Glyph.
When I start implementing the game world, I can create a CUIMapView that takes an map and displays it, focusing on a single entity. A CUIStatusBar for showing the player stats. A CUIMessageBar for in-game messages ("You hit the orc."). And so on. And other neat things, like optional CUIMiniMap, a CUIMover class that can slide in its children from offscreen (for fancy UI transition), and CUIDebugInfo for development.
Anyhow, using this Composite pattern is making UI implementation a total breeze. Totally recommended!!
Input Keys to Game Commands
I want to separate the keyboard input from the game commands. The CInput singleton (in the current case, the CInputCurses subclass) takes device input, converts it to a CInput::KEY_ID data type, and adds it to a key buffer. This input buffer can then be read by anyone as desired. Currently, the CUICommandHandler object converts the input buffer to a command. So you can set KEY_N and KEY_n to place CMD_NO into the command buffer. Other UI elements will then handle commands in their Update () method. But there's nothing stopping ANYONE from adding commands, whether a key has been read or not.
I'm still wondering whether to make each command a single value (the current approach) or an instance of a CCommand object. With possible commands like CMD_MOVE_NORTH, CMD_MOVE_NORTHWEST, etc., creating a CCommandMove subclass that holds directional data may be a better approach.