Realpolitik Technical Manual
by Jim Van Verth

The purpose of this document is to provide an overview for the source code for Realpolitik. I admit that it is not anywhere complete. I’ll be working on it over the next few months, and I am hoping that others can also contribute, making a fuller picture of Realpolitik.

History

Realpolitik began as a Unix program, written to moderate games amongst my friends. While the games ended (sorry Gus), I continued working on the program. Originally I intended it to be a mail server, but gave that up after being introduced to the Judge. However, seeing MapIt gave me the idea of giving it a graphical interface and running it on the Macintosh. MacDip was born. I continued working on MacDip for a few years until circumstances led me to pull it from distribution. But after I saw other programs continue, I brought MacDip back, in a new incarnation: Realpolitik. The following years saw a port of Realpolitik to Windows, and then a cross platform version with shared data files and common code.

This rather checkered history has led to some rather checkered code. The oldest code is the resolution code, and it has a definite vanilla C feel to it. There are some #defines which act like C++ methods, but it's basically C. The newest parts are the platform specific classes, which are fairly object-oriented, but there are, admittedly, some hacks.

 

File Manifest

Common

DrawMap.cpp - all the code to draw the various parts of the map, other than orders. This also includes the code to load and unload the icons for the pieces.

DrawOrders.cpp - code to draw just orders, of all types

EdgeList.{cpp,h} - manages lists of polygon edges along scanlines for the Rgn class

EditMap.{cpp,h} - generic interface to edit map functions -- turns edit map mode on and off

Fails.{cpp,h} - order failure codes and associated strings

Game.{cpp,h} - game file loading and saving. Contains globals for season, year, adjustments, winner and various game states

Global.h - common global variables defined in platform specific Main.cpp

Graph.{cpp,h} - graph data structure used to adjudicate orders

History.{cpp,h} - data structure used to store and manage history information

Initialize.{cpp,h} - common initialization routines

MapContent.{cpp,h} - routines for handling clicks in the map window

MapData.{cpp,h} - data structures and loading routines for map, country and region info

MapDisplay.{cpp,h} - routines for creation, destruction and management of the map window

Neighbor.{cpp,h} - data structure for managing connections between spaces on the map

Order.{cpp,h} - data structure and routines for all order types

OrderPalette.{cpp.h} - routines to manage display and clicking in order palette

OrdersDisplay.{cpp,h} - creation, deletion and management of orders and errors windows

Parse.{cpp,h} - useful routines for parsing lines of text

ReadOrders.{cpp,h} - a poor name for routines for the functions in the Order menu. Also includes PreviousHistory() and NextHistory(), which are similar.

Resolve.{cpp,h} - heart of the resolution engine

Unit.{cpp,h} - data structures and routines for managing units

Variants.{cpp.h} - data structure and routines for changing and loading variant info

Platform-Specific (MacOS/Win32)

COffscreen.{cpp,h} (MacOS only) - routines for creating and managing offscreen graphics buffers. Should probably be replaced by GWorlds.

Dibfile.{cpp,h} - routines for loading and saving BMP files

Dibapi.h (Win32) only - extra header for BMP routines

EditMenu.{cpp,h} - creation, deletion and management of pop-up edit menu

EventUtil.{cpp,h} - filter routines for various incoming events, other than menu events

FileFinder.{cpp,h} - class to search through directories for files

Image.{cpp,h} - wrapper class for platform-specific image types (Picture and HBITMAP)

InfoDisplay.{cpp,h} - creation, deletion and management routines for info dialog

ListUtil.{cpp,h} - core routines for managing lists in windows

LWindows.{cpp,h} - core routines for managing windows with lists in them

MacTypes.{cpp,h} - wrapper routines for simulating QuickDraw (empty on the Mac)

Main.cpp - starting point for all the fun. Contains platform-specific globals.

MenuUtil.{cpp,h} - routines for handling menu events

Offscreen.{cpp,h} - wrapper for offscreen buffers

OldGame.{cpp.h} - routine for handling old save game format (only on Mac)

PieceIcon.{cpp,h} - wrapper class for loading and displaying piece icons

Preferences.{cpp,h} - routines for loading and saving preference info

Print.{cpp,h} - printer initialization and map printing

Resource file and Resource.h - main application resources

Rgn.{cpp,h} - this class represents an area on the map -- used to represent where spaces are on the map

ScrollableWindow.{cpp,h} - creation, deletion and management routines for basic scrollable window (used for map window).

StatusDisplay.{cpp,h} - creation, deletion and management routines for status dialog

Strings.{cpp,h} - in theory, string table management. This is actually only true on the Mac. Under Windows, this is a list of strings and constants with a wrapper routine to simulate a string table.

TextFile.{cpp,h} - class for loading from and saving to text files

Util.{cpp,h} - generic, useful, platform-specific routines. Includes GetFile, PutFile routines,

WindowUtil.{cpp,h} - useful routines for managing windows and bringing up specific small windows (like the About Box and Winner dialogs).

 

Initialization

Because Realpolitik is a cross-platform application, initialization gets a little wonky. There are certain platform-specific things that need to be done for initial startup, then some common initialization routines are called. Finally, Mac and Windows have completely different ways for handling drag-and-drop, so a second platform-specific split must occur.

Realpolitik begins the action in the file Main.cpp. This contains the main startup routine (either main() or WinMain()) as well as the main event loop. There are also a number of platform-dependant globals defined here. The main startup routine begins by doing some platform-dependant initializations, then calls the routine InitializeCommon(), found in Initialize.cpp. This does some basic setup for Realpolitik: loading preferences, allocating memory for data structures, loading the list of variants, and creating the text windows. If this succeeds, Realpolitik moves on to either creating a new game or loading one from drag-and-drop.

Macintosh drag-and-drop works by sending an AppleEvent event to the application. So Mac Realpolitik finishes calling InitializeCommon() and drops right into the main event loop. The AppleEvent is caught (in EventUtil.cpp), and the appropriate routine, either InitNewGame() or OpenFinderGame() is called.

Windows drag-and-drop works by putting a file name in the cmdline for the application. If there is a filename there, Realpolitik calls OpenFinderGame(). Otherwise it calls InitNewGame(). Only then is the main event loop started up.

InitNewGame() and OpenFinderGame() are pretty similar (in fact, in writing this document I realized that InitNewGame() should really just call OpenFinderGame(), using the .gam file for the variant). InitNewGame() works by setting the current variant to the last one used, initializing the map window (which also loads the map data for the variant) and then creating a new game, which reads the information from the .gam file. OpenFinderGame() opens the given file, sets the variant based on the file info, and then does the same initialization of the map window and loading game information, but this time from the .dpy file.

When either InitNewGame() or OpenFinderGame() are done, control returns to the main event loop, and Realpolitk is ready to use.

 

Movement Resolution Algorithm

As mentioned before, the resolution algorithm in Realpolitik is some of the oldest code in the program. It runs off of a directed graph data structure into which orders are added. Probably at some point this should be made into a C++ class, but I wasn’t eager to do something that might break the code for this release. Because of its obtuse nature this code is also fairly well commented. Note that this algorithm attempts to be as true to the spirit of the Avalon Hill/Hasbro rules as possible, so you can have multiple convoys for a single unit.

The first stage in resolution is adding orders to the graph. First (unless Overwrite Units is set) all the units are added to the graph and assumed to hold. Then each order is added. Orders can be rejected through simple tests at this point — for instance, there is no unit at that location, or support is provided to a space that the unit can’t reach, or a unit is moving to a space that it can’t reach and it’s not being convoyed, etc.

To test if a army can be convoyed to a space it can’t normally get to, Realpolitik uses a depth-first search, using the starting and ending spaces for the army, and any convoying fleets that match the order. Note that this does not find the shortest path — it merely guarantees that there is a path.

Once all the orders have been added to the graph, the algorithm begins. The first step is to check for cut supports. Because of the oddness of convoyed units and dislodged fleets, this happens in a series of stages:

  1. Cut all supports using non-convoyed units
  2. Mark convoys that could be dislodged as dubious
  3. Mark convoyed armies by those convoys as dubious
  4. Cut supports from convoyed armies that are guaranteed to get there
  5. Clear the ‘dubious’ marks
  6. Perform any dislodges on convoys
  7. Mark convoyed armies by those convoys as failed
  8. Cut any remaining supports by convoyed armies
  9. Finally, cut any supports against a stronger attacker

The basic idea is that first we cut supports by all moves that we know will get there, then dislodge any convoying fleets, then cut via any remaining moves.

The next step is to check moves that have supports from one country that may dislodge a unit of the same country. This will clear those supports, or just enough to bounce an opposing force, not to dislodge.

The next step is to check all other move orders. This is done using a routine called follow(). The concept is that we see if a move succeeds by following its path through the graph. This may depend on another move succeeding (i.e. moving out of the way) so we follow that move. And so on, until either a move definitely succeeds or fails, or we end up with a cycle, in which case all moves succeed.

The stages in the follow function are:

  1. Make sure this is a non-resolved move order
  2. If there’s a cycle, return failure if you’re trying to switch with your neighbor (unless one of you is convoyed), otherwise return success
  3. If your destination is moving towards you and has greater support than you, mark as failed and return (assuming neither of you are convoyed).
  4. If you are not the strongest unit vying for the destination, return failure
  5. If there’s no unit there, mark success and return
  6. If there is a unit and it moves (test by calling follow() for that unit), mark success and return
  7. Remove any supports for this move that would lead to a self-dislodge (rule IX.3)
  8. If we’re still stronger in support than our destination and we wouldn’t self-dislodge, then mark success and return.
  9. If we’re still stronger in support then our destination and we would self-dislodge, return failure
  10. Otherwise mark failed and return.

Note a couple of fine points. There is a difference between marking a move as failed, and returning failure. Marking a move as failure means that it will not succeed under any circumstances. Returning failure without marking it means that it won’t succeed, but it may be used to bounce another move. Also note that areas where bounces occur are marked so that they will be unavailable for retreats.

The final step of resolution is to check for self-dislodges. Any move that would dislodge a unit of the same country automatically fails. This is done after the main follow algorithm so that self-dislodges can still be used to bounce enemy moves.

The remainder is just clean-up: adding dislodged units to the dislodge list, and copying the resolution from the graph back to the order.

Retreat and Adjustment Resolution

Retreats are resolved very simply. First check that the unit can retreat to the space it’s retreating to, and then add to the graph like a normal move order. Any bounces are changed to disbands. Any retreating unit that has no order is automatically disbanded.

Adjustments are also pretty simple. They need to be added to the graph to check for duplicate orders and set up things for civil disorder, but then it’s just a case of going through order by order and making sure that there aren’t too many builds or removals for a particular country. The one wrinkle is when a country has given to few removals — in this case civil disorder takes over and units are automatically removed. This is done using a breadth-first search to determine the distance between the country’s home centers and each of the country’s units. Armies are slightly different in that sea spaces with fleets are included in the search, since they can be used to convoy the armies.

 

Miscellaneous

As mentioned above, this document is not nearly complete. The problem is that the time I spend writing documentation, is time where others don’t have access to code — so I’m releasing it in this unfinished state and hope the code, such as it is, will speak for itself until I have a chance to finish writing. Until then, here are some miscellaneous notes on some of the trickier bits of code.

InitMapDisplay() calls ReadMapData() and LoadInfoText(). I’m sure there was a historical reason for this (I blame the Gauls), but the reason I left it is because every time I’m loading a new variant I want to initialize the map window, then read map data, then the info text. This would probably be better wrapped in a differently named initialization routine.

Spaces on the map are called sectors in the code. I once mis-heard supply centers as supply sectors, so thought all the spaces were called sectors, and the term stuck. The Judge calls them provinces, which I think is a better name.

Certain "classes", such as Unit and Neighbor, are stored in linked lists. Creating and destroying these lists was taking up a lot of time, so I pre-allocate memory for them. If a variant is particularly large, you can run out of pre-allocated data, which could cause problems. Similarly, there are a maximum number of spaces and players, which are set in MapData.h.

©Copyright 2001 Jim Van Verth
License Agreement