Adapting OpenGo to Microsoft Windows and MFC
Copyright © 1998-99 Jeffrey Greenberg
Version 1.1, January 8, 1999
Table of Contents
Changes from Previous Versions: *
User Interface Requirements *
OpenGo User Interface under Windows MFC *
Initializing the Application *
Starting a New Game *
What's in View? *
Abstracting the Console, Dialogs, and other I/O. *
Reconciling Windows Events and OpenGo Data Flow *
Table of Figures
Figure 1: Sketch of a user interface style to be supported. *
Figure 2: View and Controller Overview *
Figure 3: MFC View Hierarchy Miscellany *
Figure 4: Data Model Overview *
Figure 5: Relations between the Model, View and Controller classes *
Figure 6: MFC View & Document Initialization Timing Problem (simplified) *
Figure 7: Abstracting GUI and I/O with IO Classes *
Figure 8: Using IO objects to connect the referee and players with their views. *
Figure 9: Creating a new game. *
Changes from Previous Versions:
V1.1 January 1999
Grammar and minor corrections.
Note: this version remains slightly behind the actual implementation
OpenGo is a set of classes (a framework) for producing software that plays Go. Programming Go is
one of a set of world-class software problems. The framework is a general purpose infrastructure for
two person games. It supports playing simultaneous games against local players at the keyboard &
mouse, and remote ones over the Internet or via modem protocols. In addition, it supports a GUI and
non-GUI (console) interface on a variety of operating systems: UNIX, AIX, Solaris, SunOS, Windows
95, and Windows NT.
The general data model design support multiple simultaneous games being played, each containing a
referee and two players. The referee owns the official, displayed state of the game, and mediates
between the players who are making moves and receiving the moves of their opponent. The referee
enforces legal moves.
The source code is available with working implementations of a portable, non-windowing interface,
and a Microsoft Windows specific interface. This document describes the architectural requirements
caused by supporting Microsoft Windows using the Microsoft Foundation Classes (MFC) -- the issues
and architecture for OpenGo under Microsoft Windows using MFC.
User Interface Requirements
Some especially Important properties of the program are:
Figure 1: Sketch of a user interface style to be supported.
The display under Windows will be MDI style where multiple games can be run simultaneously, each
in their own window. Each MDI window contains a view of the game made up of a board view,
referee controls, and player controls. Each type of player will have some common controls and some
specific to its type. Additional views on each game can be obtained detailing the work of each player
e.g. the modem protocol transcription, search diagnostics, debugging information, etc..
OpenGo User Interface under Windows MFC
OpenGo & MFC Overview
Figure 2: View and Controller Overview
The architecture of the GUI under MFC is shown in Figure 2 in Booch notation. It is quite a tangle of classes and it is not obvious how and when classes are created and managed. The relationships depicted above are detailed more fully in various MFC texts. The figure also captures two important scenarios: How the application is created and initialized, and How a new game is created. I will detail these below.
The implementation uses two splitter windows, one within the other. The first has one pane for the board, and the other for the second splitter. The second splitter contains two panes, each with a player display in it.
Initializing the Application
In order for MFC to operate, a number of steps occur when the application is launched.
This is the call stack for step 0: InitInstance in figure 2 above. Many classes have been removed in the figure. Applications in MFC are always put in global space and initialized first.
WinMainCRTStartup() // bottom of stack
Once the program is initialized, the user can select File:New to create a new game. The user is presented with a dialog box that offers choices for what kind of player will play and white, the size of the board, rules, handicaps, and other game setup needs. Once decided, a child MDI windows appears as shown in Figure 1 and play can begin. Exactly how this is implemented is noted below.
This is the call stack for step 0: OnNew in figure 2 above. Many classes have been removed in the figure.
CMultiDocTemplate::OpenDocumentFile() // will use CRunTime objects to create doc/view/frames.
AfxWndProc // bottom of stack
What's in View?
The three views that are created in the ChildFrame's splitter window are of two sorts, CFormView and CView. FormViews look like dialogs and are created using the dialog resource editor but are a part of regular windows rather than launched like a dialog. They are particularly handy of creating windows with buttons, edit fields and other standard dialog controls. In addition, because FormViews are derived from ScrollViews, if a FormView is too big for the window containing it, scrollbars automatically appear and will automatically scroll the form around. The downside of FormViews is that they don't provide printing, which a regular CView does provide. BoardView is a CView and thus the board can be conveniently printed with very little work.
Some other Views and controls provided by MFC are shown below.
Figure 3: MFC View Hierarchy Miscellany
Now that you have a picture of the OpenGo Data Model and the MFC View and Document classes, how can the two be connected?
Plugging OpenGo Data Classes into MFC
Now that you see the architecture MFC put into place, how can the pre-existing, portable OpenGo classes be plugged in? First let's survey the OpenGo classes.
OpenGo Classes Overview
A birds-eye view of the classes in OpenGo is shown in below. Details of the Referee, AsyncMsgHandler, PlayerProxy and portability classes are discussed elsewhere [Greenberg 96, 97], and a portable, non-windowing version has been implemented. These classes well encapsulate the various functions, data, GUI and operating system specifics .
Figure 4: Data Model Overview
MFC provides a coordinated albeit inelegant complex of classes that support an impure variant of the Model/View/Controller pattern. In MFC, the CDocument and the CView classes provide the controller and view parts of the pattern. The OpenGo Go Engine will supply the data Model classes to complete the pattern. The MFC CDocument class actually provides a number of services that are tightly tied to Views and to Microsoft's user model. Nonetheless, Microsoft wants us to stick the controller/model functionality in or through there, and it is probably best not to argue. So, CDocument will be subclassed to implement the desired Microsoft functionality, and to provide the services of the Controller. The subclassed CDocument, OpenGoDoc, will proxy the actual controller class GoGame_CTL. This will keep MFC contained so that the engine can also be connected to other windowing systems. The diagram below shows the top-level connections between MFC and the OpenGo engine.
Figure 5: Relations between the Model, View and Controller classes
The key idea is that OpenGo will provide the data via the Referee to the MFC CDocument which will really become the controller in the smalltalk model-view-controller sense. OpenGo then becomes the actual data model. OpenGo classes will have to be modified to expose their I/O functions so that they are available through this single interface.
In the non-Windowing version, a Referee_Console is implemented. When Referee_Console is constructed it sends a message to itself to prompt for a new game. It contains the routines to prompt the user. Once the game parameters and Player types have been selected, the appropriate Players are instantiated and game objects created. For Referee_Window, this prompting is controlled in the main frame window: Menu:New,Load/open,quit etc replaces this functionality. Once a child window is up, it's menu takes over this Game start/end/save/quit function. The menus can obtain from Referee_Window game state information such as whether a game is in-progress or not, and thus allow it/them to change the menus accordingly.
One might imagine that MFC is constructed such that one prompt the user for game information, create and initialize a Document and Referee for the game and then launch the corresponding Views. Unfortunately, this is not the case. The difficulty is that while it is the Referee_Window that selects and instantiates the two PlayerProxys, in MFC the splitter window requires that the PlayerView be known before it can be instantiated. (Examine Figure 5 below and Figure 2, step 4: On New initializes the Document/Referee after the views have been created, but it is the Document/Referee that knows which players/views need to be created!)
Figure 6: MFC View & Document Initialization Timing Problem (simplified)
Something must be done to adapt to the process by MFC creates and initialized Documents and Views. The solution taken here is to add a new step to the Document initialization. The idea is to have the initial views that are created in the splitter panes be blank, and that when the document is initialized and the user prompted for game/player/view choices to replace the blank views with the selected ones. There are really very few places that can be changed to achieve the desired affect. For instance creating a user prompt dialog during ChildFrame's initialization (MDIChildFrame::OnCreateClient()) and letting the user cancel there, means the view failed to be created and causes MFC to complain with a MessageBox (at least in debug mode). This is intolerable for the user. Another possible place would be to intercept the menu event someplace (where?), prompt the user or cancel, and then pass on the parameters through the main frame, childframe and then views. But there is no way to pass parameters to View that instantiated by splitter windows (as far as I can tell), let alone pass parameters easily down through the menu events and frame windows. MFC shows itself to be rather awkwardly designed and poorly documented -- this knowledge was gained through a debugger….
Abstracting the Console, Dialogs, and other I/O.
OpenGo contains some classes that are quite a bit removed from MFC, classes that are intended to be GUI independent. These classes have three kinds of i/o needs: display, logging, and errors. An example is the modem classes used to implement a general modem and the Go Modem protocol on top of the modem class. These classes have need of display I/O for diagnostic purposes and well as to display transmitted text. In the current implementation, use of FILE and streamio has been made (stdin, stdout, stderr, cin, cout, and cerr). Additionally, there is code incorporated from the Wally computer opponent [ref x] which also has routines that take FILE * for the purpose of logging files as well as stdout for user display. The Error member of I/O concerns program Assert and Fatal errors as well. This output should perhaps go to a different window than the output that is related to the playing of a particular game. All three types of these I/O classes need to represented separately and expressed as appropriate for a given GUI.
In the OpenGo data model, output related to a particular game is channeled to a particular referee, since it is the central point and connection between the data model and the view. But at some level, code that controls a modem say, shouldn't need to know about referees and players, so that the i/o code it uses should be more generic, not bound to a referee. One way to do this is via an abstract IO class which can be implemented, instantiated and used at the level of a referee and then passed on down to the lower code. This class would be generic and abstract such that deeper code, like a modem, can use it in any GUI context.
This abstract class should contain the following member
|ScrollingInfo( fmt, …)||informational message is displayed, not guranteed, high performance.|
|ScrollingDebug( debug, fmt, … )||diagnostic info is displayed, not guaranteed, high performance.|
|Error( fmt, …);||display is guaranteed, low performance|
|Fatal(fmt, …);||display is guaranteed, low performance, then program exits|
|Log( fmt, …)||Display to a logging subsystem is guaranteed, high performance.|
Referees have two PlayerProxys in them. The referee might make use of two different IOObject implementations, one for each kind of PlayerProxy (human, modem, computer) passing the appropriate one to each.
Figure 7: Abstracting GUI and I/O with IO Classes
This scheme tries to take advantage of commonality and consistency within a spec for a particular GUI. The IO object is the locus of IO commonality. In the picture above, all that has been abstracted is general, global I/O, not the I/O that is specific to a particular function, such as the modem. While there probably is commonality a that level of functionality, I will not go further here and investigate it. For my purposes, the above scheme is sufficient.
Reconciling Windows Events and OpenGo Data Flow
Once the MFC classes and Referee classes are instantiated, how does data flow? Let's work through a scenario: the user clicks the mouse on the board to place the next move, which will be responded to by the computer opponent. What exactly happens? Well it is actually quite complicated because the OpenGo data model is organized to support both local and remote players. Remote players over the Internet or modem operate asynchronously, so that the OpenGo data model is also asynchronous. OpenGo uses PlayerProxys to communicate directly with players whether over the modem, Internet, or via the keyboard, mouse and monitor. The Referee talks to the PlayerProxys, not the players, and to the user who controls the game and system. So the PlayerProxys have their own thread to constantly communicate with their players and to receive messages from the Referee, and the Referee has it's own thread to receive message from the PlayerProxys and the controlling user. All sorts of messages are flying around asynchronously. This player wants to move here, the controller wants to end the game, that player wants to take back a move, the referee wants to tell a player that it's not his turn to move. To achieve this fluid effect, the Referee and PlayerProxys are all derived from AsyncMessageHandler which support the "posting" of a message to an object and then returning. Sometime later, the message is received and processed by a "handler" thread. The PlayerProxys themselves use asnychronous i/o for communicating over the modem, and, of course, Windows itself is event driven. There's a lot of stuff flying around. So with that said, here is the actual flow of message between objects:
Coordinating Referee & Player Multithreading with Views
Players are working asynchronously, playing the game. The Referee coordinates the player actions into a coherent, official game. Referee and Players are all each in their own thread. At any moment, each might have text to display or a dialog to update or present to the user. How is all this coordinated?
These facts pertain:
Threads, Views, and I/O
With this point in mind, perhaps the Referee is the best place to deal with Player I/O issues mentioned above?
If the Referee provides Players a way to launch dialogs and updates Views on the Player's behalf, then both I/O abstraction and multithreading issues can be addressed together. As it is, the Referee controls which Players play. Suppose a referee is subclasses for a specific GUI so that it can give IO resources when it creates Players, and coordinate the use of IO during the game. Thus, I imagine there are a Human_Console_Player and Human_Console_GUI pai,r and a Human_Windows_Player and Human_Windows_GUI pair. The console referee uses the console players, while the windows referee uses the windows players and io.
On the other hand, tightly integrating the GUI in with the Player simplifies the problem of separating the GUI from the Player. But then this returns us to the problem of coordinating the multi-threaded player from the view
Figure 8: Using IO objects to connect the referee and players with their views.
The document contains the referee which contains players and player i/o objects. The io objects connect the players with their views.
How would this get setup? The sequence diagram below shows how these objects would get created.
Figure 9: Creating a new game.