OpenGo MFC Architecture

Adapting OpenGo to Microsoft Windows and MFC

Copyright © 1998-99 Jeffrey Greenberg

Version 1.1, January 8, 1999

Jeffrey Greenberg
 
 

Table of Contents

Changes from Previous Versions: *

Introduction *

User Interface Requirements *

OpenGo User Interface under Windows MFC *

OpenGo & MFC Overview *

Initializing the Application *

Starting a New Game *

What's in View? *

Plugging OpenGo Data Classes into MFC * OpenGo Classes Overview *

Abstracting the Console, Dialogs, and other I/O. *

Reconciling Windows Events and OpenGo Data Flow *

Coordinating Referee & Player Multithreading with Views * Threads, Views, and I/O *
 
 

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

Changed all instances of Circlet to OpenGo.

Grammar and minor corrections.

Note: this version remains slightly behind the actual implementation

V1.0 January 1998 Original document Introduction

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:

  1. Multiple games can be played at once.
  2. New Games can be started at any time.
  3. Games can be ended at any time.
  4. A game display contains a pane for each player and for the board display and game control.
  5. Each kind of player has an appropriate interface. Modem players can monitor and debug the modem. Internet players can control their connection parameters, local players can see the game history, etc.
Generically, the game display looks something like this: (Please see the actual program – this is only a

sketch)

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.

COpenGoApp::InitInstance()

AfxWinMain()

WinMain()

WinMainCRTStartup() // bottom of stack

COpenGoApp:InitInstance() will go on in the remaining steps to:
    1. create the main frame window which will create the status and toolbar windows,
    2. create a CmultiDocTemplate with CruntimeClass information for creating Doc/View/Frame triples, which are where the action occurs as you will see below.
Starting a New Game

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.

COpenGoDoc::COpenGoDoc() // The OnFileNew event is resolved through CmultiDocTemplate that was created by the application with the particular frame/document/view. In this case, the COpenGoDoc is found and it's constructor will be called. COpenGoDoc::CreateObject() // note this static is used to construct the class!

CRuntimeClass::CreateObject()

CDocTemplate::CreateNewDocument()

CMultiDocTemplate::OpenDocumentFile() // will use CRunTime objects to create doc/view/frames.

CDocManager::OnFileNew()

CWinApp::OnFileNew()

DispatchCmdMsg

// The steps below route a mouse-click message on the menu window attached to the main frame window, determine that it is a New command selection, and call the applications OnFileNew handler. CWinApp::OnFileNew

CCmdTarget::OnCmdMsg

CFrameWnd::OnCmdMsg

CMDIFrameWnd::OnCmdMsg

CWnd::OnCommand

CFrameWnd::OnCommand

CMDIFrameWnd::OnCommand

CWnd::OnWndMsg

CWnd::WindowProc

AfxCallWndProc

AfxWndProc // bottom of stack

The remaining steps create the ChildFrame and make it a child of the MainFrame, create a splitter window in the ChildFrame with multiple panes, and finally to create a view in each pane. Note that Views are connected to Documents via their base classes.

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 functions:
 
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:

s

  1. The mouse up event that is generated on the screen is really being generated on the display of the Referee's official board. The BoardView converts the click into a move by a white or black at certain position. It calls OpenGoDocument::Move with this information. This function first locates the proper PlayerProxy based on black or white moving, then determines whether this PlayerProxy can accept local input, and if so posts a MoveResponse to it.
  2. Sometime later, the PlayerProxy processes the MoveRsp, which entails posting a further MoveRsp on to the Referee. The player has made his move.
  3. Sometime later, the Referee processes the MovesRsp. If the player should not have moved, or if the move is somehow illegal, a MoveOK(false) is posted back to the PlayerProxy, otherwise the move is accepted, the Views updated, and the a MoveRequest posted to the opposing player.
  4. The opposing PlayerProxy receives the MoveRequest with a move to respond to. At this point, the cycle is repeating. Once the response is determined, a MoveResponse is posted back to the referee.
  5. The referee validates the response, updates the board, and posts a MoveRequest back to the first player.
Note that at any time after the mouse event in step 1, the controlling user might select the menu:End_Game item. This would lead to the Referee_Window being posted an EndGame event. Once the Referee_Window's event handler thread runs with the event, it can end the game and go about closing the ChildFrame window and it's Views.

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:

The only multithreading issue is between the Referee and the Views because they are running independently of each other -- the Referee takes care of Players. We can expect coordination problems between the View and the Referee which may need to be solved with the appropriate use of resource locks (e.g. semaphores, mutex, etc.).

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.

Notes: