Rationale
Why did I start this project?
- I wanted to apply, in some demonstrable, tangible, way as much of everything I have learnt thus far about software engineering and engineering software as I could.
- I wanted to learn about and apply tools and techniques that I felt I needed to know more about in order to progress my abilities.
- To become a better software engineer.
- To use it as a vehicle for advancing the development of my longer-term game project.
Assessing each point, I’d say I actually achieved what I intended to with this project.
I wanted to apply my skills and experience I had already acquired to a software engineering project. This applied to the all aspects of the project and not just the design or implementation. I wanted to complete a project exactly as I might if I was doing it ‘properly’ (using my understanding and interpretation of best-practice wherever I could) for someone else. The second reason was to learn about and use some processes and techniques I had always wanted to but hadn’t yet touched in a meaningful way. As with any project, I wanted to use it as a vehicle to become a better, and a better software engineer in particular.
I’d like to emphasis that my focus for the development was very much the former three points rather than the last. In fact, I’d actually completed a ‘quick & dirty’ implementation of the game six months earlier over a two week period. Initially, I had wanted to perform an iteration to re-factor the existing implementation using a more formal methodology. It became apparent fairly early on that, apart from the GUI (which was fairly well encapsulated and decoupled from the rest of the program), I was better off starting almost completely from scratch.
Architecture & Design
The design effort was object-oriented and intended to result in a modular solution. In a major improvement to the architecture of the existing implementation, all functionality deemed to belong to the game engine was encapsulated within the Engine namespace/module. As an exercise, an effort was made to ensure the non-GUI (Graphical User Interface) components could be packaged as a library, thus leaving the possiblity of creating and using a range of interfaces, graphical or otherwise, with it. The resulting high-level architecture of the program is that of the Model-View-Controller (MVC) pattern. In fact, depending whether it is a human or Artificial Intelligence (AI) player’s turn, the program operates in an MVC or Model-View mode. While the AI has control, it manipulates the controller, causing the model (the engine) to change which stimulates an event to be sent to the view (the GUI) that causes it to update the view of the game. The AI also recieves the same message although it currently does not act on it. During a human player turn, the GUI (practically) takes on the role of both controller and view.
A custom event system was developed to manage this communication. Originally I thought I could probably extract it somehow and develop it as a separate C++ library for my toolkit but it’s not a particularly elegant design (cumbersome even). Despite this, it’s effective in its application here.
One part of the design that is not well documented, and largely escaped the model-driven approach, was the design of the protocols for messaging between components using the event system. Due to the flexibility of the event system, it is quite easy to send messages to the rest of the program and register listeners to catch them anywhere they are needed. Althought this flexibility is useful, it’s quite at odds with a model-driven approach. It also immediately makes the program difficult to follow and understand. The use of such messaging schemes has its place, but because of the confusion it can cause, it is desirable to have a detailed corresponding model or some documentation of its use. There’s definitely a lot to think about and improve in this area for future projects.
Programming to an Interface
As this program is one that I intended (and still intend) to expand and develop right from the outset, I was always conscious of how to make that path an easier one. For me, a key part of this is the practice of programing to interfaces. More recent languages encourage this practice by the inclusion of specific features and keywords just for the purpose of supporting the concept of interfaces. My feeling is that it is not a practice as wide-spread in the C++ world, yet the language does include the features to support it (even if they are not as refined, comprehensive or sophisticated). Programming to an interface encourages more flexible, looser, coupling of program entities. By designing my program up-front to include these points of flexibility, I allow the possibility of improvement of the program module by module, component by component, class by class.
Programming to an interface is not the last word in software flexibility. It also introduces it’s own risks, such as the possibility of wasted time on unnecessary code infrastructure for entities that aren’t likely to change. In this case, I know my intention is to grow the program, its suite of functionality, incrementally. Therefore, the more flexibility I can build in at certain points and layers (which I hopefully identify early-on during design-time), the more chance I reduce the need for significant refactoring of code for existing features as new ones are realised.
Verification & Validation
Originally, when first conceiving of and starting the project, I didn’t really think about testing. Once I started to really understand my objectives for the project and was getting near to writing code, I knew, as a minimum, I had to explore using a C++ unit testing framework for unit testing.
After having seen what was possible in the Java world of unit testing, I wondered what was out there. After some study and browsing, I selected the Boost.Test library. This was primarily because the documentation was decent, I warmed to the philosophy of the framework, the actual mechanics and syntax of writing test cases and suites, and I wanted to learn more about, and use, the Boost library more generally.
My main verification activities were to check that all the work I had done on the model was actually being represented in the code. Conversely, I had to be disciplined in reflecting design changes made during implementation back onto the model. One also has to be careful when writing unit tests, especially using TDD, that the required level of unit test coverage is being achieved.
It quickly became apparent once I started writing unit test cases that my plan for writing fake classes and stub methods to support unit testing was not an efficient one. There had to be something better and what I found was the Google C++ Mocking Framework.
This has been my only experience of using a mocking framework. The concepts and syntax are somewhat challenging at first, but the pay-off is large once you gain a command of it. It really does allow a great deal of flexibility and probably saves a huge amount of time, when constructing a thorough set of unit test suites. In fact, the amount of time needed to created the unit test code without a mocking framework would probably have prohibited comprehensive unit testing altogether.
As the GUI is quite simple, testing there was performed on an ad-hoc basis.