Tuesday, July 28, 2009

Designing A Hockey Power-Play System: Part 3

I finally found some time to sit down and revisit this problem. I made the decision a few days ago that I would wake up and devote my entire day to solving this problem once and for all, and I did just that. I realized that I had been going about the problem in the wrong fashion, which is why I never seemed to be able to come up with a solid solution.

If you haven't read the other 2 posts on this topic (or if you haven't read them recently), I suggest you do that now in order to get a good grasp on the evolution of this design.

Part 1
http://zachelko-gamedev.blogspot.com/2008/07/designing-hockey-power-play-system.html

Part 2
http://zachelko-gamedev.blogspot.com/2008/07/designing-hockey-power-play-system-part.html

To summarize though, my initial thoughts were to design some sort of power-play "system" which would encapsulate all of the tricky details of managing complex penalty situations. At first thought, this seems logical. In software engineering we commonly encapsulate complex things into easy to use interfaces. However, something about this particular problem just wasn't meshing with this approach. You see, the most complicated part of a power-play situation (or set of power-play situations) isn't managing the players who commit the penalties (taking them off the ice, the length of the penalty, placing them back on the ice, etc...). Instead, the complex part is informing the user of the situation via the HUD (heads up display). The behind the scenes work is simple because you simply set a timer for each player who commits a penalty, draw him in the penalty box for that duration, and when it expires you place him back on the ice. With the HUD, you have to verbalize to the user the exact situation that is currently in effect (5 on 4, 5 on 3, 4 on 4, or 4 on 3), and the duration that situation will last. To do this requires knowledge of all other currently running penalties. When you start to think of all of the permutations, you'll get a headache.

So, how did I alter the design to negate these issues? Simple: I solved the problem procedurally. Instead of using an object-oriented "system" approach to nicely encapsulate away all of the nastiness, I simply broke the problem set down into very simple functions. It took some detailed doodling on paper to get things right, but if you can't make an algorithm work on paper, you'll never get it working in code.

Here is the algorithm I came up with:

  1. Maintain a list of times that represent penalties that are currently being served. Tick each of the times in the list once every second (elapse time)
  2. When a penalty occurs, remove this player from the ice and add his time to the list of penalty minutes.
  3. Count the players on the ice for each team (should be a simple call to a size() function on the container they are held in for each team)
  4. Set the situation timer (5 on 4, 5 on 3, etc...) equal to the smallest remaining time in the penalty times list. We use this time because as soon as a penalty expires, the situation will change, so we choose the time that expires soonest.
  5. When a penalty expires, place the player back on the ice and repeat the process of counting players and setting the situation timer.
If the situation is 5 on 5, no display needs to be shown. Also, whichever team has the advantage, the situation label will appear under their label on the screen. For example, if the Penguins have a 5 on 4 power-play advantage, under the team label "Penguins" on the screen, the text "5 on 4" would be displayed. If it is an even strength situation (but not full strength), the display can be shown in the middle of the 2 labels. This all depends on your interface and isn't really important as far as the algorithm is concerned, but it does have to be accounted for.

The problem seems so much simpler once it is broken down into 3 or 4 very basic functions. At first I felt it odd and maybe even "hacky" to count the players in this fashion in order to display the situation, but then I realized that the serving of the penalties and the HUD are two totally different issues, so trying to put them into one solution was pointless and just led to more problems. I also believe this solution is very fast and light-weight. Consider the fact that you are adding the penalty times to a container of some sort. If you always insert them at the back, and tick each one every second, you can guarantee that the penalty which will expire soonest will always be in the first position. No searching, no sorting. Just grab the first element and you can set the situation timer. And as I mentioned before, chances are the players on each team are in some sort of container themselves, so "counting" the number of players simply means calling a size() function on that container which should be as fast of an operation as possible being that it is a standard library function (not to mention there are < 20 players on a hockey team... =p ).

Hopefully this has been helpful to someone out there. I'm putting the finishing touches on the code, so I'll post some screenshots of the demo in action once I complete it.