Home
   Home  |  Preliminary Docs  |  Design Diagrams  |  Reference  |  Development Docs  |  Download  |  Source Code  |  Final Reports  |
Design Reflection

Overall, I feel that my program's architecture is very robust, flexible, and relatively easy to understand. However, there are several aspects of my program that I hadn't thought out very well prior to the start of the coding phase. This document will describe some of the ways that my design worked out particularly well, then the ways that didn't work too well, and finally things that I would have done differently.

Things That Worked Well

Because of my experience using the Component Object Model (COM), basing my program upon this foundation worked very well. If I wanted to, I could rewrite the game engine to use a totally different user interface, and the game library modules would work unchanged. Likewise, I could expand the game library to include a number of additional types of Solitaire, and the engine would not need to be changed. Using COM has allowed me to create a very flexible application.

In addition, my choice to use the Internet Explorer Web Browser control to display the Solitaire Pack user interface worked out excellent. Because IE has built-in support for so many of the things I would need to do, such as displaying images, capturing mouse events, and handling positioning issues, I was able to take advantage of these capabilities and focus on the logic of a Solitaire game and not on how to render the games.

One aspect of my design that was not specifically described in my design diagrams was the means by which I communicate between objects in my program. For instance, the main window object creates a game menu object, so it has the direct ability to call functions of the game menu. However, when the game menu needs to notify the main window of something, for instance of when the user selects a game to play, I do not simply have the game menu store a pointer to the main window and call a function directly. Rather, the way I design my class interactions is via Site Interfaces. In this case, CMainWindow implements the IGameMenuSite interface, containing just the functions that the game menu would use. The game menu object calls these functions, and has no idea what object will actually be responding to these functions. I have found that this type of designing makes my programs much more flexible and object-oriented.

Implementing engine functionality incrementally was a difficult thing to do, namely because a game library that would use this functionality would have to have a lot of it there in order to be able to do anything. So, I designed a means through which I would be able to execute engine commands at runtime. Because the Solitaire Pack engine object is an Automation-compatible COM object, I was able to simply pass a pointer to it to the Web Browser control. Then, I added a few lines to the HTML document (containing the user interface) that would simply read text from a text box and execute it as JavaScript code. This way, I could fire up Solitaire Pack, type commands like "mypile = engine.CreatePile(2);" and "mypile.x = 100;", and the engine would perform just as if a game module had called the methods. Being able to test in this manner was a tremendous benefit of designing the program based on COM.

Another thing that worked very well was using a state machine to control mouse event handling in the engine. This is described in more detail in the document Usage of State Machines.

Things Didn't Work Well

One of the first major issues that I had was having to redo the animation algorithm. The prototype I had developed worked great when moving two cards at a time. However, when testing interactively as described above, I found that the algorithm was terribly slow when animating a large number of cards over varying distances. So, I had to come up with a new algorithm that involved each card taking a different number of steps.

The primary thing that didn't go as well as it should have was the Undo/Redo architecture. In the old version of Solitaire Pack, it was a complete afterthought and was a pretty big headache. One of the goals of designing before coding this time around was to avoid this headache. Unfortunately, I did not design the Undo mechanisms thoroughly at all.

My programming log lists "totally redid undo architecture" on April 11th. The original plan was to have all undoable actions grouped into turns, and the undo manager would, when undoing a move, look at the type of action and perform the appropriate steps to undo the action. After starting to write the code, I realized that this approach would not work very well at all. For instance, when changing a pile's X coordinate, the pile would first move itself and then tell the undo manager to add a "pile x changed" action, with the corresponding offset value. Then, when undoing, the undo manager would check the type of action, see that it was a "pile x changed" action, and proceed to tell the pile to change its x value.

In other words, the actual logic for performing an action would be stored in two places. So, I decided that the obviously best way to do it would be for the object (pile, card, etc) to be the only one to be able to actually carry out an action, either forward (initial time/redo) or backward (undo). The final mechanism I came up with works basically as follows (using the CPile::put_X example):

  • Inside CPile::put_X, the pile tells the undo manager to record an action: its type is ACTION_PILEX, its data is the change in x value as the action's data, and the owner (site) of the action is the pile itself.
    • RecordAction first tells the action object to redo itself.
      • The action tells its site (in this case, the pile) to do/redo the action.
        • The pile moves its X coordinate appropriately.
    • RecordAction then actually records the action in the undo stack.

After getting this new undo architecture to work, I found that adding undo/redo support for new actions was very easy. In fact, this architecture works so well that I hopefully will be able to easily adapt it for other programs without too much work.

However, there was one nasty effect of the new undo architecture: I ended up having to totally revamp the inter-pile card transfer mechanisms. This took a good three days (April 16-18). During this time I was wishing I had spent more time designing the card transfer mechanisms, because they, like the undo mechanism, were not designed in any detail before starting to program.

From a design standpoint, these were the only major problems that I encountered. Had I put more effort into designing these aspects of the program, I surely could have reduced the difficulty in implementing them.

What I Would Have Done Differently

In general, the major problems I had can be traced back to not designing thoroughly enough. Toward the end of the design phase, I was becoming anxious to start coding, so I reduced the detail of the design. If I could go back, I would take the time to analyze the exact means that cards are transferred between piles: what functions are called at what point, and how do they interact with the undo manager.

After further contemplation, one major way that I think I would have designed the program differently is in the concept of the fragment. Currently, the fragment is a special pile that is used when moving cards between piles. In fact, it is the only means exposed to game objects by which they can move cards at all. The way I would now design this transfer mechanism is quite different. Instead of having a fragment, I would have the game objects create a "transfer manager" object. They would be able to add individual card transfers to the transfer manager, and then execute them all at once. Thus, they could potentially have cards from multiple piles each going to different piles, all in one transfer animation. This mechanism would fit much easier into the undo architecture.

Another thing that I would have done differently is to create a means to implement an entire game in JavaScript. While the interactive JavaScript-based execution mechanism I described above worked great for simple things, such as creating piles and moving cards around, there was no way for me to test things such as accepting or rejecting card drops. Having a way to implement games in JavaScript would be good for testing as well as for quick and easy development of games.

  May 9, 2002 jmhoersc@mtu.edu