ADVERTISEMENT
|
|
| Volume Number: | | 2
| | Issue Number: | | 12
| | Column Tag: | | MacApp Objects
|
Dots Game Introduces MacApp 
By Larry Rosenstein, Product Engineer, MacApp, Apple Computer, Inc.
An Introduction to MacApp
Suppose you just got a great idea for a new Macintosh application. Regardless of whether it deals with music, animation, or (even) word processing, one task you face is developing the particular features of your program. After all, these features are what you -- and it is hoped your users -- are most interested in seeing on the screen.
On the Macintosh, users also judge applications by their interfaces. In order for your program to be accepted, it should follow the user interface guidelines as closely as possible. Unfortunately, the more time you spend implementing the user interface, the less time you can devote to the exciting (musical, graphical, etc.) parts of the application.
To help solve this problem, a group at Apple®, which included me, developed an application framework called MacApp. MacApp automatically implements the standard features of a Macintosh application, which allows you to concentrate on the unique parts of your program.
For example, MacApp completely handles moving and resizing windows, scrolling, printing, and displaying error alerts. MacApp also provides a general design for other features such as Undo and document filing that make it much easier to implement these features.
In this article I want to first describe the basics of object-oriented programming, which helps make MacApp possible, and the overall MacApp architecture. Then I will describe a small game program written using MacApp.
Object-Oriented Programming
It is difficult to explain object-oriented programming in only a few paragraphs. Our experience with brand new MacApp programmers is that it takes a couple of weeks for them to understand object-oriented programming and feel comfortable using it. So, dont worry if you dont grasp the concepts right away. What is object-oriented programming anyway?
Object-oriented programming is a way of writing programs. Conventional (that is, non object-oriented) programs are organized around a set of data structures, and procedures or functions that manipulate those data structures. For example, MacDraw represents graphical shapes by a variant RECORD type in Pascal, and implements routines to draw, stretch, save, etc. these records. Each of these routines uses a CASE statement to distinguish the different kinds of shapes and perform the appropriate action for each.

Fig. 1 Complete Mac Game in Two Days
Object-oriented programs, however, are organized around a set of object types. Each object type defines both data structures, or fields, and methods that operate on the fields. An object definition, therefore, is much like a RECORD definition in standard Pascal.
The methods implemented for an object are the only routines that modify the objects fields. (Although Object Pascal does not enforce this restriction.) The process of calling a method is often called message passing. The programmer creates a set of objects and sends them messages. When an object receives a message, it decides what action to take (i.e., what method to execute). Different objects can respond to the same message in different ways.
Object-oriented programming also involves the concept of inheritance. This means that one object type can be derived from an existing object type. A derived type inherits all the behavior (fields and methods) of the base type. It can, however, override any method it chooses.
For example, I could define a Rectangle object type that implements 2 methods: Draw, which calls FrameRect to outline the rectangle, and ComputeArea, which returns the rectangles area.
Later, someone else could define a subtype called ShadedRectangle that is derived from my Rectangle object. ShadedRectangle would override the Draw method and call FillRect with a particular pattern. Since the area of a shaded rectangle is the same as that of an empty rectangle, the ShadedRectangle object type can inherit my ComputeArea method. (In Figure 2, you see that both Rectangle and ShadedRectangle share the implementation of ComputeArea.)
Because ShadedRectangle was derived from Rectangle, it inherits all the behavior of Rectangle. Any piece of code that deals with Rectangle objects can also deal with ShadedRectangle objects without change.

Figure 2.
If MacDraw were written using object-oriented programming, it might define a generic Shape object type. Shape objects would define the methods that all shapes must implement, for example Draw, Stretch, Save, etc. There would also be specific object types, derived from Shape, such as Rectangle, Oval, Text, etc. (The Oval object type could also be derived from the Rectangle type, since ovals and rectangles differ only in the Quickdraw procedures used to draw them.)
The value of object-oriented programming is evident when you start modifying a program. Suppose you wanted to add triangle objects to MacDraw. In a conventional programming methodology, you would have to add a new variant to the MacDraw shape RECORD type, and modify each of the routines that deal with shapes so that they handle triangles.
With object-oriented programming, adding a triangle shape would involve creating a Triangle object type, which would be derived from the generic Shape object type, and implementing the methods Draw, Stretch, Save, etc. The main part of the program, which just sends messages to shape objects, does not have to change at all.
Notice that all these changes are localized in the definition of Triangle, rather than scattered throughout the program. Also, since the main part of the program does not change at all, it is possible to add new kinds of shapes without recompiling the rest of the application. In fact, you dont even need the sources; you can simply link in the modules containing the new object types.
MacApp Basics
MacApp consists of a set of object types. The MacApp objects themselves implement most of the standard features of Macintosh applications, such as window resizing. You can compile MacApp right out of the box and get a fully-functional Macintosh application that doesnt do anything.
To write your application you define object types that are derived from the standard MacApp objects and override any methods you choose. To use MacApp, you generally deal with the following five object types:
(1)TApplication, which handles the top-level control structure and main event loop of your program. It also handles global commands (Open , Quit, etc.) and creates TDocument objects.
(2)TDocument, which contains the data used in your program, and provides methods for manipulating the data. It is also responsible for reading and writing the data to disk, and creating TView and TWindow objects.
(3)TView, which draws your data on the screen, and processes mouse clicks.
(4)TWindow, which represents a Macintosh window that can move, resize, and scroll.
(5)TCommand, which represents an undoable action in your program. It is responsible for performing an action as well as undoing it.
When you write a program using MacApp, you generally define new object types derived from TApplication, TDocument, TView, and TCommand. In most applications, the standard TWindow type defined in MacApp is sufficient.
Dots
The best way to understand how MacApp works is to look at a sample program. Rather than choosing one of the samples that comes with MacApp, I wrote a simple game program from scratch. (This game took about 2 days to implement and debug.)
The game of Dots begins with a matrix of dots. Two players take turns drawing horizontal or vertical lines to connect any two adjacent dots. Whenever a player draws a line that completes a square, that player claims the square - in the paper version, by writing his initials inside. The one with the most squares is the winner.
A screen shot of the game is shown in Figure 1. In my version of the game, the players take turn clicking on a line with the mouse. Each player is represented by a different pattern, and the program takes care of filling in squares with the appropriate pattern as they are completed. It also keeps track of the score and inverts the score of the player whose turn it is.
The first thing I did to implement the Dots program was think about the object types I needed and the methods each needed to implement. Because of the MacApp architecture, I knew immediately that I needed subtypes of TApplication, TDocument, and TView (called TDotApplication, TDotDocument, and TDotView). To display the current score, I needed another view object, called TScoreView. Finally, I needed a TDotCommand object, so that a players move could be undoable.
Several of the methods I needed to implement were required by the architecture of MacApp. For example, TDotView and TScoreView both needed a Draw method to draw the game board and score respectively, and TDotDocument needed a DoRead method to read saved games from the disk.
Each object type also needed methods specific to my particular application. For example, TDotDocument implements methods for marking and erasing lines in the grid. TDotView and TScoreView implement methods for invalidating parts of the views, which are called by the TDotDocument.
Next, I needed a way to represent the state of the game and refer to lines and boxes on the grid. I decided to name each line in the grid according to: (1) the dot at its left or top end and (2) its orientation (horizontal or vertical). Whether or not the line is drawn or erased is recorded in the fLine field of the TDotDocument, which is simply a 3-level array of Booleans. Similarly, the state of each box is represented in the fBoxes field of my document object.
The only methods that access these fields directly are in TDotDocument (GetLineState, SetLineState, and ChangeBoxState). Hiding the representation of the grid in this way allows me to easily change the implementation of TDotDocument without affecting the rest of the program.
Once I defined the internal representation of the game, I then defined the appearance of the game on the screen. This is done in the Draw method of TDotView. The Draw method simply loops for each dot and calls another method, DrawCorner. DrawCorner is responsible for drawing a dot, any lines leading from the dot to the right or bottom, and the box to its bottom right. The important point to note here is that DrawCorner uses the methods of TDotDocument to find out the state of the lines and box.
The other main function of TDotView is to handle mouse clicks. This is done by its DoMouseCommand method. DoMouseCommand simply creates a TDotCommand object and returns it to MacApp. (What happens to the commands object is described below.)
You will notice from the listing that TDotView also implements several methods that deal with visual representation of the game. For, example, the method Pt2Line converts a point within the view into a specification for the line at that point.
The TScoreView object provides a different view of the game. It simply summarizes the current score and indicates whose turn it is. In MacApp, it is possible to have several views of the same document.
TDotCommand serves 2 purposes. First, it tracks the mouse while the player is choosing a line. Recall that DoMouseCommand creates a TDotCommand object and returns it to MacApp. MacApp then enters a loop that calls methods of TDotCommand as long as the user holds the button down.
There are three such methods. First is TrackFeedback, which simply highlights the line at which the user is currently pointing.
The second method is TrackConstrain. This method is called to give the application a chance to modify the actual mouse coordinates. In a graphical application, for example, you would implement gridding using this method. In my case, I use this to eliminate flashing when the user moves the mouse but still points to the same line.
The final method is TrackMouse. TrackMouse is called each time through MacApps loop to report the current mouse position. MacApp also tells the command object if the mouse has moved, and automatically scrolls the document if the user moves the pointer outside the window.
In my program, TrackMouse simply waits until the user releases the button. Then, it figures out which line, if any, the user selected. If the user doesnt select any line, TrackMouse returns the special command object gNoChanges, which tells MacApp that nothing happened. In other cases, however, it returns itself, which tells MacApp what action to perform.
After TrackMouse exits, MacApp takes the return value and (assuming it is not gNoChanges) calls the DoIt method of that command. The command object responds to DoIt by drawing a line in the grid by calling TDotDocument.SetLineState.
MacApp also saves the command object away, in case the user picks Undo from the menu. If that happens, MacApp calls the UndoIt method of the same command object. The command object then erases the line with another call to SetLineState. (If the user picks Undo again, MacApp calls the RedoIt method of the command.)
There are a couple of things to note about this program.
First, it has many features that are not obvious from looking at the listing, because they are implemented in MacApp itself. You can move, resize, scroll, and print the game window. If you click the mouse in the grid and drag outside the window, the grid automatically scrolls (assuming that the entire grid is not already showing). You can open multiple game windows at once. The game implements Save, Save As , Save a Copy , and Revert command.
Second, this simple program does some things that some commercial programs dont do at all. For example, it displays detailed error messages describing what went wrong. For example, if you try to save a game to a locked disk, you will see the message Could not save the document because the disk is locked. Eject the disk and move the write-protect tab.
The important thing to realize is that I did not write a single line of code to implement these features: they were all inherited from the standard MacApp code. The 2 days it took to implement this application is typical of the time needed to get an application up and running (once you know the basics of MacApp, of course).
There are tradeoffs in using MacApp. If you want to implement a non-standard user interface, you may have to override several MacApp methods. In general, however, the code you would write is the same as if you were to implement everything from scratch.
My Dots program ended up being 63,996 bytes after I compiled it without debugging code and with the optimizer tool. That space broke down as follows (approximately):
1044 method tables
3722 application code
3768 program jump table
3834 MacApp initialization code
5550 other resources
21404 MacApp resident code
24674 MacApp non-resident code
Remember that this application has more features and better error handling than the typical sample program. Also, the space required by MacApp is essentially constant: in a commercial application it would be a small fraction of the total application size.
I am sure that had I written Dots from scratch, it would have been smaller. On the other hand, it would not have as many features and I would not have finished it in time for MacTutors editorial deadline!
Had I not used MacApp and Object Pascal to implement the Dots game, I probably would have used a standard RECORD structure to save the game state, and would have implemented most of the same utility routines to manipulate the game. Since MacApp imposes no restrictions on the internals of your program, it can be written in standard Pascal, C, or assembler. (It is possible to write and debug the internal algorithms in a system such as Lightspeed Pascal, for example, and convert the Pascal code into MPW Pascal once it works completely.)
The performance of a MacApp application is comparable to that of an application written from scratch. There is a slight performance penalty for the method calls, but MacApp comes with a method call optimizer that removes this penalty from many method calls. We have made a lot of performance improvements between the beta releases and the final release, especially in the area of text handling. (I was able to write a MacPaint-like application in MacApp that was very close to the real MacPaint in speed.)
MacApp does not relieve the programmer of the task of writing efficient code. For example, my Dots progam draws the entire grid every time the Draw method is called. I could speed up scrolling significantly by changing TDotView.Draw to only draw the elements that need to be updated. The area parameter to Draw indicates the bounding box of the area that needs to be refreshed. MacApp includes performance measurement tools so that you can find out where your applications inefficiencies are.
Apple doesnt expect everyone to use MacApp. Most programmers who have already implemented a Macintosh application have probably developed their own personal framework, similar to MacApp, and will continue to use it. MacApp is intended primarily for the programmers who have not yet implemented an application, and dont want to learn all the intimate details of Inside Macintosh.
One significant benefit of using MacApp is that you can take advantage of the expertise and work that went into its design. In developing MacApp, we have tried to find the best way to accomplish a particular task (e.g., memory and segment management) and incorporate it into MacApp. Even before you write a line of code, you are starting with a library that has 2 years of testing and development behind it.
Just remember one of the mottos of the MacApp team: We do the programming, so you dont have to!
Apple is a registered trademark of Apple Computer, Inc. Macintosh, MacApp, MacDraw are trademarks of Apple Computer, Inc. Lightspeed Pascal is a trademark of Think Technologies.
PROGRAM Dots;
(*Main program of the Dots game.
Written by Larry Rosenstein, Apple Computer
CSNet: lsr@Apple.CSNET
UUCP: {sun, nsc}!apple!lsr
*)
USES
{$LOAD MacIntf.LOAD}
MemTypes, QuickDraw,
OSIntf, ToolIntf, PackIntf,
{$LOAD UMacApp.LOAD}
UObject, UList, UMacApp,
{$LOAD}
UPrinting, UDots;
VAR
gDotApplication: TDotApplication;
BEGIN
{Initialize the ToolBox; MacApp will call MoreMasters 8 times.}
InitToolbox(8);
{Initialize the printing unit} InitPrinting;
{Allocate the application object}
New(gDotApplication); FailNIL(gDotApplication);
{Initialize it}
gDotApplication.IDotApplication;
{Run it}
gDotApplication.Run;
END.
/* This is the Dots resource file in MPW Rez format. */
#ifdef Debugging
include MacAppRFiles"Debug.rsrc";
#endif
include MacAppRFiles"MacApp.rsrc";
include MacAppRFiles"Printing.rsrc";
include "Dots" 'CODE';
type 'dots' as 'STR ';
resource 'dots' (0) { "Dots game V1.0" };
data 'ICN#' (129) {
$"0000 0000 0000 0000 0000 0000 0000 0000"
$"0000 0000 0000 0000 0000 0000 0000 0000"
$"0000 0000 07FF FFF0 0400 0010 0400 0010"
$"0400 0010 0471 C710 0471 C710 0471 C710"
$"0420 0010 0420 0010 0420 0010 0471 C710"
$"047F C710 0471 C710 0400 0210 0400 0210"
$"0400 0210 0471 C710 0471 FF10 0471 C710"
$"0400 0010 0400 0010 0400 0010 07FF FFF0"
$"0000 0000 0000 0000 0000 0000 0000 0000"
$"0000 0000 0000 0000 0000 0000 0000 0000"
$"0000 0000 07FF FFF0 07FF FFF0 07FF FFF0"
$"07FF FFF0 07FF FFF0 07FF FFF0 07FF FFF0"
$"07FF FFF0 07FF FFF0 07FF FFF0 07FF FFF0"
$"07FF FFF0 07FF FFF0 07FF FFF0 07FF FFF0"
$"07FF FFF0 07FF FFF0 07FF FFF0 07FF FFF0"
$"07FF FFF0 07FF FFF0 07FF FFF0 07FF FFF0" };
data 'ICN#' (128) {
$"0000 0000 FFFF FF00 8000 0100 8000 0100"
$"8E38 E100 8E38 E100 8E38 E100 8010 0100"
$"8010 0100 8010 0100 8E38 E100 8FF8 E100"
$"8E38 E100 8000 0100 8000 3F00 8000 4080"
$"8E38 8040 8FF9 3020 8E39 C810 840E 7F8F"
$"8402 3007 8401 0007 8E38 8007 8E38 6007"
$"8E38 3FE7 8000 011F 8000 0107 FFFF FF00"
$"0000 0000 0000 0000 0000 0000 0000 0000"
$"0000 0000 FFFF FF00 FFFF FF00 FFFF FF00"
$"FFFF FF00 FFFF FF00 FFFF FF00 FFFF FF00"
$"FFFF FF00 FFFF FF00 FFFF FF00 FFFF FF00"
$"FFFF FF00 FFFF FF00 FFFF FF00 FFFF FF80"
$"FFFF FFC0 FFFF FFE0 FFFF FFF0 FFFF FFFF"
$"FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF"
$"FFFF FFFF FFFF FF1F FFFF FF07 FFFF FF00"
$"0000 0000 0000 0000 0000 0000 0000 0000" };
resource 'FREF' (128) { 'APPL', 0, "" };
resource 'FREF' (129) { 'dots', 1, "" };
resource 'BNDL' (128) {
'dots', 0, {
'ICN#', {0, 128; 1, 129};
'FREF', {0, 128;1, 129}
} };
/* This describes the initial position of a window. MacApp automatically
replaces the funny title with the name of the current document. */
resource 'WIND' (128, purgeable) {
{50, 10, 304, 222},
zoomDocProc, invisible, goaway, 0x0,
"<<<>>>" };
/* This is the alert that MacApp displayes in response to the About
menu item */
resource 'DITL' (201, purgeable) {
{{130, 182, 150, 262},
button {enabled, "OK"};
{10, 80, 40, 270},
statictext {disabled,
"Dots game."};
{50, 80, 110, 270},
statictext {disabled,
"Written using MacApp by Larry Rosenstein, "
"Apple Computer. 10/14/86"};
{10, 20, 42, 52},
icon {disabled, 1}
} };
resource 'ALRT' (201, purgeable) {
{90, 100, 250, 412},
201,
{ OK, visible, silent;
OK, visible, silent;
OK, visible, silent;
OK, visible, silent } };
/* Here are the menus used by the application. The cmnu resource type
is like MENU but contains a command number for on each item. The PostRez
MPW tool converts each cmnu resource into a MENU resource and creates
a sorted table of command numbers */
resource 'cmnu' (1) {
1, textMenuProc, 0x7FFFFFFD, enabled,
apple,
{"About Dots ", noicon, nokey, nomark, plain, 1;
"-", noicon, nokey, nomark, plain, nocommand
} };
resource 'cmnu' (2) {
2, textMenuProc, 0x7FFFEEFB, enabled,
"File",
{"New", noicon, "N", nomark, plain, 10;
"Open ", noicon, "O", nomark, plain, 20;
"-", noicon, nokey, nomark, plain, nocommand;
"Close", noicon, nokey, nomark, plain, 31;
"Save", noicon, "S", nomark, plain, 30;
"Save As ", noicon, nokey, nomark, plain, 32;
"Save a Copy In ", noicon, nokey, nomark,
plain, 33;
"Revert", noicon, nokey, nomark, plain, 34;
"-", noicon, nokey, nomark, plain, nocommand;
"Page Setup ", noicon, nokey, nomark,
plain, 176;
"Print One", noicon, nokey, nomark, plain, 177;
"Print ", noicon, nokey, nomark, plain, 178;
"-", noicon, nokey, nomark, plain, nocommand;
"Quit", noicon, "Q", nomark, plain, 36 } };
resource 'cmnu' (3) {
3, textMenuProc, 0x7FFFFEBD, enabled,
"Edit",
{"Undo", noicon, "Z", nomark, plain, 101;
"-", noicon, nokey, nomark, plain, nocommand;
"Cut", noicon, "X", nomark, plain, 103;
"Copy", noicon, "C", nomark, plain, 104;
"Paste", noicon, "V", nomark, plain, 105;
"Clear", noicon, noKey, nomark, plain, 106;
"-", noicon, nokey, nomark, plain, nocommand;
"Show Clipboard", noicon, nokey, nomark,
plain, 35 } };
/* The buzzwords menu contains strings describing commands that are not
executed from the menu */
resource 'cmnu' (128) {
128, textMenuProc, allenabled, enabled,
"Buzzwords",
{"Last Move", noicon, nokey, nomark,
plain, 2001 } };
/* This resource describes what menus should be loaded and displayed
when the application starts up */ resource 'MBAR' (128) { {1; 2; 3} };
UNIT UDots;
(*Main part of a MacApp program that plays a simple game.
Written by Larry Rosenstein, Apple Computer
UUCP: {nsc, sun}!apple!lsr
CSNET: lsr@Apple.CSNET
AppleLink: Rosenstein1 *)
INTERFACE
USES
{These statements read the interface files for the Macintosh ROM
routines and for MacApp. The $LOAD is used to store the largest
interface files in binary format for speed. }
{$LOAD MacIntf.LOAD}
MemTypes, QuickDraw, OSIntf, ToolIntf, PackIntf,
{$LOAD UMacApp.LOAD}
UObject, UList, UMacApp,
{$LOAD}
UPrinting;
CONST
{ size of dot grid; should be even so that there will be an odd number
of boxes and no tie games }
kDotsPerRow = 6;
{ constants representing the state of a box }
kBoxFree = 0;
kBoxPlayer1 = 4;
kBoxPlayer2 = 5;
TYPE
{ Number of sides completed; 5 = completed by player2 }
BoxState = kBoxFree..kBoxPlayer2;
{ Used to specify the direction of a line. }
LineDir = (hLine, vLine);
{ Arrays used to store the state of the game. }
LineArray = ARRAY[LineDir,
1..kDotsPerRow, 1..kDotsPerRow] OF BOOLEAN;
BoxArray = ARRAY[1..kDotsPerRow,
1..kDotsPerRow] OF BoxState;
ScoreArray = ARRAY[BOOLEAN] OF INTEGER;
{ ******************** }
{ This is the application object type. It is responsible for:
creating new documents }
TDotApplication = OBJECT(TApplication)
PROCEDURE TDotApplication.IDotApplication;
{ Every object type has an initialization method such as this. }
FUNCTION TDotApplication.DoMakeDocument( itsCmdNumber: CmdNumber): TDocument;
OVERRIDE;
END;
{ ******************** }
{ This is the document object type. It is responsible for:
creating new views and windows
reading and writing documents
manipulating the state of the game }
TDotDocument = OBJECT(TDocument)
fDotView: TDotView; { The view of the game }
fScoreView: TScoreView; { The view of the score }
fPlayer1Turn: BOOLEAN; { TRUE if player 1's turn }
fLines: LineArray; { State of evey line }
fBoxes: BoxArray;{ And box }
PROCEDURE TDotDocument.IDotDocument;
{ Methods to create views and windows. }
PROCEDURE TDotDocument.DoMakeViews(forPrinting:
BOOLEAN); OVERRIDE;
PROCEDURE TDotDocument.DoMakeWindows; OVERRIDE;
{ Methods to read/write document and create new documents.
DoInitialState is called for new documents;
DoRead for opening a document ;
DoNeedDiskSpace is called when MacApp saves a document. MacApp will
check to see that there is enough disk space available.}
PROCEDURE TDotDocument.DoInitialState; OVERRIDE;
PROCEDURE TDotDocument.DoNeedDiskSpace(VAR dataForkBytes, rsrcForkBytes:
LONGINT); OVERRIDE;
PROCEDURE TDotDocument.DoRead(aRefnum: INTEGER;
rsrcExists, forPrinting: BOOLEAN); OVERRIDE;
PROCEDURE TDotDocument.DoWrite(aRefnum: INTEGER; makingCopy: BOOLEAN);
OVERRIDE;
{ Methods to manipulate the game state. These can be called by anyone.
CalculateScore gives you the current score.
GetBoxState returns the state of a box.
GetLineState and SetLineState manipulate the state of each line. }
PROCEDURE TDotDocument.CalculateScore(
VAR theScore: ScoreArray);
FUNCTION TDotDocument.GetBoxState(
row, col: INTEGER): BoxState;
FUNCTION TDotDocument.GetLineState(direction: LineDir;
row, col: INTEGER): BOOLEAN;
PROCEDURE TDotDocument.SetLineState(direction: LineDir; row, col: INTEGER;
forPlayer1, newState: BOOLEAN);
{ Private method. }
PROCEDURE TDotDocument.ChangeBoxState(
row, col: INTEGER;
forPlayer1, addingLine: BOOLEAN);
END;
{ ******************** }
{ This is the main view object type. It is responsible for:
drawing on the screen
handling mouse clicks }
TDotView = OBJECT(TView)
fDotDocument: TDotDocument;
PROCEDURE TDotView.IDotView(
itsDocument: TDotDocument);
{ Drawing methods }
PROCEDURE TDotView.Draw(area: Rect); OVERRIDE;
PROCEDURE TDotView.DrawCorner(row, col: INTEGER);
PROCEDURE TDotView.InvalBox(row, col: INTEGER);
PROCEDURE TDotView.InvalLine(direction: LineDir;
row, col: INTEGER);
{ Mouse handling method }
FUNCTION TDotView.DoMouseCommand(
VAR downLocalPoint: Point; VAR info: EventInfo;
VAR hysteresis: Point): TCommand; OVERRIDE;
{ Utility methods that convert between the visual representation of the
game and the internal data structures. For example, Box2Rect returns
the rectangle on the screen that corresponds to a particular box. }
PROCEDURE TDotView.Box2Rect(row, col: INTEGER;
VAR r: Rect);
PROCEDURE TDotView.Dot2Rect(row, col: INTEGER;
VAR r: Rect);
PROCEDURE TDotView.DotCenter(row, col: INTEGER;
VAR center: Point);
PROCEDURE TDotView.Line2Pt(direction: LineDir;
row, col: INTEGER; VAR pt: Point);
PROCEDURE TDotView.Line2Rect(direction: LineDir;
row, col: INTEGER; VAR r: Rect);
FUNCTION TDotView.Pt2Line(pt: Point;
VAR direction: LineDir;
VAR row, col: INTEGER): BOOLEAN;
END;
{ ******************** }
{ This is the score view object type. It is responsible for:
drawing the score }
TScoreView = OBJECT(TView)
fDotDocument: TDotDocument;
fPlayer1Turn: BOOLEAN; {which player is currently
highlighted (not necessarily whose turn it is }
PROCEDURE TScoreView.IScoreView(
itsDocument: TDotDocument);
{ Drawing methods }
PROCEDURE TScoreView.Draw(area: Rect); OVERRIDE;
PROCEDURE TScoreView.InvalScore(
forPlayer1: BOOLEAN);
PROCEDURE TScoreView.SetTurn(player1Turn: BOOLEAN);
{ Utility method }
PROCEDURE TScoreView.GetPlayerRect(
forPlayer1: BOOLEAN; VAR r: Rect);
END;
{ ******************** }
{ This is the command object type. It is responsible for: tracking
the mouse
doing and undoing a move}
TDotCommand = OBJECT(TCommand)
fDotDocument: TDotDocument;
fDotView: TDotView;
fPlayer1: BOOLEAN; { who made move }
{ these fields record the line chosen by the user }
fDirection: LineDir;
fRow: INTEGER;
fCol: INTEGER;
PROCEDURE TDotCommand.IDotCommand(
itsDotView: TDotView);
{ Mouse tracking methods }
PROCEDURE TDotCommand.TrackConstrain(anchorPoint, previousPoint: Point;
VAR nextPoint: Point); OVERRIDE;
PROCEDURE TDotCommand.TrackFeedback(
anchorPoint, nextPoint: Point;
turnItOn, mouseDidMove: BOOLEAN); OVERRIDE;
FUNCTION TDotCommand.TrackMouse(
aTrackPhase: TrackPhase;
VAR anchorPoint, previousPoint, nextPoint: Point;
mouseDidMove: BOOLEAN): TCommand; OVERRIDE;
{ Command processing methods }
PROCEDURE TDotCommand.DoIt; OVERRIDE;
PROCEDURE TDotCommand.RedoIt; OVERRIDE;
PROCEDURE TDotCommand.UndoIt; OVERRIDE;
END;
IMPLEMENTATION
{$I UDots.inc1.p}
END.
CONST
kTypeCreator = 'dots';{ my doctype and creator }
kWINDid = 128; { rsrc ID of my WIND resource }
kLastMoveCommand = 2001;
{ command number for the Move command; in MacApp every menu item is
assigned an INTEGER command number, which makes it easier to deal with
commands }
kPlayer1 = TRUE;{ indices for gPlayerPats }
kPlayer2 = FALSE;
kStaggerAmount = 10; { amount to offset each window}
kHalfDotSize= 3;{ half the width of a dot }
kFullDotSize= 2*kHalfDotSize + 1;
kHalfLineSize = 1;{ half width of occupied line }
kFullLineSize = 2*kHalfLineSize + 1;
kLineOffset = (kFullDotSize - kFullLineSize) DIV 2;
kDotSpacing = 4*kFullDotSize;{ spacing }
kHalfSpacing= kDotSpacing DIV 2;
kScoreSpacing = 3*kDotSpacing;
kScoreInset = 6;
VAR
gStaggerCount: INTEGER; { used to stagger windows }
gPlayerPats:ARRAY[BOOLEAN] OF Pattern;
{ the patterns representing the two players }
{$S Dots} { everything in 1 segment }
{ Here are all the method implementations. I have ordered
these to make it easier to read though the code. }
{ The first thing that happens in the main program is to initialize the
Toolbox and create & initialize the application object. This method just
sets up the global variables above.}
PROCEDURE TDotApplication.IDotApplication;
BEGIN
{ Specify the main file type of my documents }
IApplication(kTypeCreator);
gStaggerCount := 0;
gPlayerPats[kPlayer1] := ltGray;
gPlayerPats[kPlayer2] := dkGray;
END;
{ Next, the application is told to create a document object,
either one that will be blank or one that will be read from disk. In
this program I have only one kind of document, so I ignore the parameter.
MacApp does support multiple document
kinds in the same application, however. }
FUNCTION TDotApplication.DoMakeDocument(
itsCmdNumber: CmdNumber): TDocument; OVERRIDE;
VARdotDoc:TDotDocument;
BEGIN
{ This is how you create an object. }
New(dotDoc);
{ This signals a failure if the object is NIL; i.e., if we ran out of
memory. MacApp will catch the failure and display an
appropriate error message automatically. }
FailNil(dotDoc);
dotDoc.IDotDocument;
DoMakeDocument := dotDoc;
END;
{ This initializes the document object. I have this method only for
consistency with other object types. I could have called IDocument
directly in the method above. The parameters to IDocument specify the
document type and creator, whether the document uses the data and/or
resource fork of the file, and whether to leave the data and/or resource
fork(s) open (toimplement disk-based documents). }
PROCEDURE TDotDocument.IDotDocument;
BEGIN
IDocument(kTypeCreator, kTypeCreator,
kUsesDataFork, NOT kUsesRsrcFork,
NOT kDataOpen, NOT kRsrcOpen);
END;
{ Once we have an initialized document MacApp either calls
DoInitialState to set it up as a blank document, or DoRead to
read it from the disk. These methods are also called in
response to a Revert command. }
PROCEDURE TDotDocument.DoInitialState; OVERRIDE;
VARdirection: LineDir;
row: INTEGER;
col: INTEGER;
BEGIN
{ Erase all the lines in the grid , and clear the boxes}
FOR row := 1 TO kDotsPerRow DO
FOR col := 1 TO kDotsPerRow DO BEGIN
FOR direction := hLine TO vLine DO
fLines[direction, row, col] := FALSE;
fBoxes[row, col] := kBoxFree;
END;
{ Have player 1 go first }
fPlayer1Turn := TRUE;
END;
{ When this method is called, MacApp has already opened the
file for us. forPrinting is TRUE if the user chose Print from the Finder;
in some cases you can optimize your reading if you
know that you are only printing. }
PROCEDURE TDotDocument.DoRead(aRefnum: INTEGER;
rsrcExists, forPrinting: BOOLEAN); OVERRIDE;
VAR count:LONGINT;
x:INTEGER;
BEGIN
{ This reads the print state saved in the file }
INHERITED DoRead(aRefnum, rsrcExists, forPrinting);
{ Read whose turn it is. }
count := 2;
FailOSErr(FSRead(aRefnum, count, @x));
IF x = 1
THEN fPlayer1Turn := TRUE
ELSE fPlayer1Turn := FALSE;
{ Read the line states }
count := SIZEOF(LineArray);
FailOSErr(FSRead(aRefnum, count, @fLines));
{ and box states. }
count := SIZEOF(BoxArray);
FailOSErr(FSRead(aRefnum, count, @fBoxes));
END;
{ Now we have the document read in, so we make views that
display the document on the screen. Since we don't print the
score, we don't need a score view if we are just printing. }
PROCEDURE TDotDocument.DoMakeViews(
forPrinting: BOOLEAN); OVERRIDE;
VARdotView: TDotView;
scoreView: TScoreView;
BEGIN
New(dotView);
FailNIL(dotView);
dotView.IDotView(SELF);
fDotView := dotView;
IF forPrinting
THEN fScoreView := NIL
ELSE BEGIN
New(scoreView);
FailNIL(scoreView);
scoreView.IScoreView(SELF);
fScoreView := scoreView;
END;
END;
{ Here are the methods for initializing the views. }
PROCEDURE TDotView.IDotView(
itsDocument: TDotDocument);
VARside:INTEGER;
extent:Rect;
aHandler:TStdPrintHandler;
BEGIN
side := (kDotsPerRow+1)*kDotSpacing;
SetRect(extent, 0, 0, side, side);
fDotDocument := itsDocument;
{ sizeFixed tells MacApp that the view changes size only when
you set it; you can also have MacApp automatically make the
view an integral number of pages, or exactly 1 page. }
IView(NIL, itsDocument, extent, sizeFixed, sizeFixed, TRUE, hlOff);
{ creating a TStdPrintHandler is all you need to do to make a
view printable. }
New(aHandler);
FailNIL(aHandler);
aHandler.IStdPrintHandler(SELF, FALSE);
END;
PROCEDURE TScoreView.IScoreView(
itsDocument: TDotDocument);
BEGIN
fDotDocument := itsDocument;
fPlayer1Turn := itsDocument.fPlayer1Turn;
IView(NIL, itsDocument, gZeroRect, sizeFrame,
sizeFrame, TRUE, hlOff);
END;
{ If we aren't printing, then MacApp calls this to create one or more
window to display the views. }
PROCEDURE TDotDocument.DoMakeWindows; OVERRIDE;
VARaWindow: TWindow;
BEGIN
{ NewPaletteWindow is a utility provided by MacApp that
creates a window containing 2 views. One is a main view that
can scroll and the other is a palette/status view that does not scroll.
There is also a routine called NewSimpleWindow that creates a window
with only 1 scrollable view. }
aWindow := NewPaletteWindow(kWINDid,
NOT kDialogWindow, kWantHScrollBar, kWantVScrollBar, fDotView, fScoreView,
kDotSpacing+kHalfSpacing, kTopPalette);
{ This is another utility that staggers windows on screen. }
SimpleStagger(aWindow, kStaggerAmount, kStaggerAmount, gStaggerCount);
END;
{ The first event the we will receive is one to update the
windows. This results in calling the Draw methods of the 2
views. }
{ This method draws the grid. If we wanted to optimize it, we
could examine the area parameter and determine the minimum
number of rows and columns that need to be drawn. }
PROCEDURE TDotView.Draw(area: Rect); OVERRIDE;
VARrow, col:INTEGER;
BEGIN
FOR row := 1 TO kDotsPerRow DO
FOR col := 1 TO kDotsPerRow DO
DrawCorner(row, col);
END;
{ This does all the work. }
PROCEDURE TDotView.DrawCorner(row, col: INTEGER);
VARr: Rect;
boxState:BoxState;
lineState: BOOLEAN;
BEGIN
IF (row < kDotsPerRow) AND
(col < kDotsPerRow)
THEN BEGIN
PenNormal;
Box2Rect(row, col, r);
{ First draw the box if it needs to be shaded in. }
boxState := fDotDocument.GetBoxState(row, col);
IF boxState = kBoxPlayer1
THEN BEGIN
FillRect(r, gPlayerPats[kPlayer1]);
FrameRect(r);
END
ELSE IF boxState = kBoxPlayer2
THEN BEGIN
FillRect(r, gPlayerPats[kPlayer2]);
FrameRect(r);
END;
END;
Dot2Rect(row, col, r);
PenNormal;
PenSize(kFullLineSize, kFullLineSize);
{ Then draw the line down from the dot (if any) }
IF (row < kDotsPerRow) AND
fDotDocument.GetLineState(vLine, row, col)
THEN BEGIN
MoveTo(r.left+kLineOffset, r.top+kLineOffset);
Line(0, kDotSpacing);
END;
{ And the line to its right (if any) }
IF (col < kDotsPerRow) AND
fDotDocument.GetLineState(hLine, row, col)
THEN BEGIN
MoveTo(r.left+kLineOffset, r.top+kLineOffset);
Line(kDotSpacing, 0);
END;
{ Finally draw the dot itself }
FillOval(r, black);
END;
{ Utilities used in drawing the grid.}
{ This gets the state of a box. }
FUNCTION TDotDocument.GetBoxState(
row, col: INTEGER): BoxState;
BEGIN
GetBoxState := fBoxes[row, col];
END;
{ This gets the state of a line. }
FUNCTION TDotDocument.GetLineState(direction: LineDir;
row, col: INTEGER): BOOLEAN;
BEGIN
GetLineState := fLines[direction, row, col];
END;
{ This gets the rectangle defining a box. }
PROCEDURE TDotView.Box2Rect(row, col: INTEGER;
VAR r: Rect);
VARcenter:Point;
BEGIN
DotCenter(row, col, center);
SetRect(r, 0, 0, kDotSpacing+1, kDotSpacing+1);
OffsetRect(r, center.h, center.v);
InsetRect(r, kHalfLineSize+4, kHalfLineSize+4);
END;
{ This returns the rectangle that defines a particular dot. }
PROCEDURE TDotView.Dot2Rect(row, col: INTEGER;
VAR r: Rect);
VARcenter:Point;
BEGIN
DotCenter(row, col, center);
SetRect(r, -kHalfDotSize, -kHalfDotSize,
kHalfDotSize+1, kHalfDotSize+1);
OffsetRect(r, center.h, center.v);
END;
{ Here is the Draw method for the score view. }
PROCEDURE TScoreView.Draw(area: Rect); OVERRIDE;
VARscore: ScoreArray;
player:BOOLEAN;
r:Rect;
s:Str255;
offset:INTEGER;
BEGIN
TextFont(monaco);
TextFace([]);
TextSize(9);
PenNormal;
PenSize(kScoreInset, kScoreInset);
fDotDocument.CalculateScore(score);
FOR player := kPlayer1 DOWNTO kPlayer2 DO BEGIN
NumToString(score[player], s);
{ center score in the player's rectangle }
GetPlayerRect(player, r);
offset := (r.right - r.left - StringWidth(s)) DIV 2;
MoveTo(r.left + offset, r.bottom-3);
DrawString(s);
InsetRect(r, -kScoreInset, -kScoreInset);
PenPat(gPlayerPats[player]);
FrameRect(r);
END;
GetPlayerRect(fPlayer1Turn, r);
InvertRect(r);
END;
{ This computes the current score. We could have stored the
score in the document object and updated it on the fly, but this was
easier.}
PROCEDURE TDotDocument.CalculateScore(
VAR theScore: ScoreArray);
VARrow: INTEGER;
col: INTEGER;
BEGIN
theScore[kPlayer1] := 0;
theScore[kPlayer2] := 0;
FOR row := 1 TO kDotsPerRow-1 DO
FOR col := 1 TO kDotsPerRow-1 DO
CASE fBoxes[row, col] OF
kBoxPlayer1:
theScore[kPlayer1] := theScore[kPlayer1] + 1;
kBoxPlayer2:
theScore[kPlayer2] := theScore[kPlayer2] + 1;
END;
END;
{ This gets the rectangle in which to draw player's score. }
PROCEDURE TScoreView.GetPlayerRect(
forPlayer1: BOOLEAN; VAR r: Rect);
BEGIN
SetRect(r, kDotSpacing, kHalfSpacing,
2*kDotSpacing, kDotSpacing);
IF NOT forPlayer1
THEN OffsetRect(r, kScoreSpacing, 0);
END;
{ The next part of the program deals with handling a mouse
click in the grid. }
{ When the user clicks the mouse MacApp calls the
DoMouseCommand method. This returns an appropriate
command object, which then tracks the mouse. }
FUNCTION TDotView.DoMouseCommand(
VAR downLocalPoint: Point;
VAR info: EventInfo;
VAR hysteresis: Point): TCommand; OVERRIDE;
VARdotCmd:TDotCommand;
BEGIN
{ In this case, we have only one kind of mouse command. }
New(dotCmd);
FailNIL(dotCmd);
dotCmd.IDotCommand(SELF);
DoMouseCommand := dotCmd;
END;
{ This initializes the command object. }
PROCEDURE TDotCommand.IDotCommand(
itsDotView: TDotView);
BEGIN
ICommand(kLastMoveCommand);
fConstrainsMouse := TRUE;
fDotView := itsDotView;
fDotDocument := itsDotView.fDotDocument;
fPlayer1 := fDotDocument.fPlayer1Turn;
fDirection := hLine;
fRow := 0;
fCol := 0;
END;
{ Once MacApp gets the command object, it runs a loop as
long as the user holds the button down. In the loop, MacApp
continually calls the following tracking methods. }
{ This is used to give the command object a chance to modify
the actual mouse point before it is handled. }
PROCEDURE TDotCommand.TrackConstrain(
anchorPoint, previousPoint: Point;
VAR nextPoint: Point); OVERRIDE;
VARdirection: LineDir;
row: INTEGER;
col: INTEGER;
BEGIN
{ In this case, if the mouse is over a line, and the line is
unoccupied, we change the mouse point into a canonical point
for that line. This means that if the user points to the same line,
the TrackMouse method (below) will receive the same coordinate even
if the mouse moves slightly. This prevents the highlighting from flickering.
}
IF fDotView.Pt2Line(nextPoint, direction, row, col)
THEN BEGIN
IF fDotDocument.GetLineState(direction, row, col)
THEN BEGIN
{ the line is already occupied }
row := 0;
col := 0;
END;
fDotView.Line2Pt(direction, row, col, nextPoint);
END;
END;
{ This method provides highlighting while tracking. The value of nextPoint
is that returned by TrackConstrain. }
PROCEDURE TDotCommand.TrackFeedback(
anchorPoint, nextPoint: Point;
turnItOn, mouseDidMove: BOOLEAN); OVERRIDE;
VARdirection: LineDir;
row: INTEGER;
col: INTEGER;
r:Rect;
BEGIN
{ Don't do anything if the mouse doesn't move, to prevent
flicker. Because of the constraining above, mouseDidMove
will be FALSE until the user moves completely off the previous
line, even if the mouse actually does move. }
IF mouseDidMove
THEN BEGIN
{ if the mouse is over a line, invert it; TrackConstrain already
made sure that the line is unoccupied }
IF fDotView.Pt2Line(nextPoint, direction, row, col)
THEN BEGIN
fDotView.Line2Rect(direction, row, col, r);
InvertRect(r);
END;
END;
END;
{ TrackMouse actually receives the current position of the
mouse, possibly modified by TrackConstrain. }
FUNCTION TDotCommand.TrackMouse(
aTrackPhase: TrackPhase;
VAR anchorPoint, previousPoint, nextPoint: Point;
mouseDidMove: BOOLEAN): TCommand; OVERRIDE;
VARdirection: LineDir;
row: INTEGER;
col: INTEGER;
BEGIN
{ The default is to return the same command, to continue
tracking. }
TrackMouse := SELF;
{ We don't care what happens until the user releases the
button. }
IF aTrackPhase = trackRelease
THEN BEGIN
{ Make sure that the mouse is over a line, and record the user's choice
if so }
IF fDotView.Pt2Line(previousPoint,
direction, row, col)
THEN BEGIN
fDirection := direction;
fRow := row;
fCol := col;
END
ELSE
{ Tell MacApp that nothing really happened }
TrackMouse := gNoChanges;
END;
END;
{ Here are the utilities used in the mouse tracking.}
{ This returns the center of a given dot. }
PROCEDURE TDotView.DotCenter(row, col: INTEGER;
VAR center: Point);
BEGIN
SetPt(center, kDotSpacing*col, kDotSpacing*row);
END;
{ This converts coordinates of a line to a point on grid.}
PROCEDURE TDotView.Line2Pt(direction: LineDir;
row, col: INTEGER; VAR pt: Point);
BEGIN
{ Set pt to center of given dot. }
DotCenter(row, col, pt);
{ Offset it to be on the requested line. }
IF direction = hLine
THEN pt.h := pt.h + kHalfDotSize + 1
ELSE pt.v := pt.v + kHalfDotSize + 1;
END;
{ This converts the same coordinates to a rectangle. This is
used for highlighting while tracking and for invalidating when
the line is chosen. }
PROCEDURE TDotView.Line2Rect(direction: LineDir;
row, col: INTEGER; VAR r: Rect);
VARcenter:Point;
BEGIN
DotCenter(row, col, center);
IF direction = hLine
THEN SetRect(r, 0, -kHalfDotSize,
kDotSpacing+1, kHalfDotSize+1)
ELSE SetRect(r, -kHalfDotSize, 0,
kHalfDotSize+1, kDotSpacing+1);
OffsetRect(r, center.h, center.v);
END;
{ This converts a point in grid to the coordinates of a line.
It returns FALSE if the point is not on a line. }
FUNCTION TDotView.Pt2Line(pt: Point;
VAR direction: LineDir;
VAR row, col: INTEGER): BOOLEAN;
VARok: BOOLEAN;
modH: INTEGER;
modV: INTEGER;
BEGIN
ok := FALSE; { assume the worst }
{ Offset pt by slop allowed before line }
pt.h := pt.h + kFullDotSize;
pt.v := pt.v + kFullDotSize;
{ Determine values of row and col. }
row := pt.v DIV kDotSpacing;
col := pt.h DIV kDotSpacing;
ok := (row >= 1) AND (row <= kDotsPerRow) AND
(col >= 1) AND (col <= kDotsPerRow);
IF ok
THEN BEGIN { so far so good }
{ Consider taking pt.v MOD kDotSpacing. In order to be on a horizontal
line, this must be in the range 0..2*kFullDotSize] }
modH := pt.h MOD kDotSpacing;
modV := pt.v MOD kDotSpacing;
{ Pick the closest line to the point. }
IF modH < modV
THEN BEGIN
direction := vLine;
ok := (row < kDotsPerRow) AND
(modH <= 2*kFullDotSize);
END
ELSE BEGIN
direction := hLine;
ok := (col < kDotsPerRow) AND
(modV <= 2*kFullDotSize);
END;
END;
IF NOT ok
THEN BEGIN
row := 0;
col := 0;
END;
Pt2Line := ok;
END;
{ If the user actually does choose a line. MacApp will call the DoIt
method of the same command object. If the user
chooses Undo from the menu, MacApp calls the UndoIt
method. If the user chooses Undo again, MacApp calls
RedoIt. }
PROCEDURE TDotCommand.DoIt; OVERRIDE;
BEGIN
fDotDocument.SetLineState(fDirection, fRow, fCol,
fPlayer1, TRUE);
END;
PROCEDURE TDotCommand.UndoIt; OVERRIDE;
BEGIN
fDotDocument.SetLineState(fDirection, fRow, fCol,
fPlayer1, FALSE);
END;
PROCEDURE TDotCommand.RedoIt; OVERRIDE;
BEGIN
fDotDocument.SetLineState(fDirection, fRow, fCol,
fPlayer1, TRUE);
END;
{ This is the method for changing the state of a line. }
PROCEDURE TDotDocument.SetLineState(direction: LineDir;
row, col: INTEGER;
forPlayer1, newState: BOOLEAN);
VARoldState:BOOLEAN;
BEGIN
oldState := GetLineState(direction, row, col);
IF oldState <> newState
THEN BEGIN { the state actually changed }
{ modify the document data structures }
fLines[direction, row, col] := newState;
{ make sure the line is redrawn }
fDotView.InvalLine(direction, row, col);
{ change state of box to right or under line (if any) }
IF (row < kDotsPerRow) AND (col < kDotsPerRow)
THEN ChangeBoxState(row, col,
forPlayer1, newState);
{ change state of box toleft or above line (if any)}
IF direction = hLine
THEN BEGIN
IF row > 1
THEN ChangeBoxState(row-1, col, forPlayer1, newState);
END
ELSE BEGIN
IF col > 1
THEN ChangeBoxState(row, col-1, forPlayer1, newState);
END;
END;
{ let the other player have a turn; and make sure the score
view shows whose turn it is }
fPlayer1Turn := NOT fPlayer1Turn;
fScoreView.SetTurn(fPlayer1Turn);
END;
{ This changes the state of a box. }
PROCEDURE TDotDocument.ChangeBoxState(
row, col: INTEGER; forPlayer1, addingLine: BOOLEAN);
VARboxState:BoxState;
delta: INTEGER;
BEGIN
{ get the old state }
boxState := fBoxes[row, col];
IF addingLine
THEN BEGIN
IF boxState < kBoxPlayer1
THEN BEGIN
{ the box is not already completed }
boxState := boxState + 1;
IF boxState = kBoxPlayer1
THEN BEGIN
{ if this was player 2's turn, record that fact }
IF NOT forPlayer1
THEN boxState := kBoxPlayer2;
{ the box was just completed, so change the box ... }
fDotView.InvalBox(row, col);
{ ... and score }
fScoreView.InvalScore(forPlayer1);
END;
END;
END
ELSE BEGIN { removing a line}
IF boxState >= kBoxPlayer1
THEN BEGIN
{ used to be completed, make sure box is redrawn ... }
fDotView.InvalBox(row, col);
{ ... and score is updated }
fScoreView.InvalScore(forPlayer1);
END;
{ update the state of the box appropriately }
IF boxState = kBoxPlayer2
THEN boxState := 3
ELSE boxState := boxState - 1;
END;
{ finally change the data structure }
fBoxes[row, col] := boxState;
END;
{ These 4 methods make sure that the views are updated }
PROCEDURE TDotView.InvalLine(direction: LineDir;
row, col: INTEGER);
VARr: Rect;
BEGIN
Line2Rect(direction, row, col, r);
InvalidRect(r);
END;
PROCEDURE TDotView.InvalBox(row, col: INTEGER);
VARr: Rect;
BEGIN
Box2Rect(row, col, r);
InvalidRect(r);
END;
PROCEDURE TScoreView.InvalScore(
forPlayer1: BOOLEAN);
VARr: Rect;
BEGIN
GetPlayerRect(forPlayer1, r);
InvalidRect(r);
END;
PROCEDURE TScoreView.SetTurn(player1Turn: BOOLEAN);
VARr: Rect;
BEGIN
{ This method actually draws on the screen, since it is easy
to update the turn indication }
IF player1Turn <> fPlayer1Turn
THEN BEGIN
{This sets up current port to draw in score view.}
fFrame.Focus;
GetPlayerRect(fPlayer1Turn, r);
InvertRect(r);
GetPlayerRect(player1Turn, r);
InvertRect(r);
fPlayer1Turn := player1Turn;
END;
END;
{ Last but not least, here are the 2 methods needed in order to save
games on the disk. }
{ This computes the disk space required by the document.
MacApp will make sure that there is enough disk space
available to save a brand new copy of the document, before
deleting the original version. }
PROCEDURE TDotDocument.DoNeedDiskSpace(
VAR dataForkBytes,
rsrcForkBytes: LONGINT); OVERRIDE;
BEGIN
INHERITED DoNeedDiskSpace(
dataForkBytes, rsrcForkBytes);
dataForkBytes := SIZEOF(LineArray) + SIZEOF(BoxArray) +
2 {for recording whose turn it is};
END;
{ This actually does the file output. Notice that this parallels the
structure of the DoRead method above. }
PROCEDURE TDotDocument.DoWrite(aRefnum: INTEGER;
makingCopy: BOOLEAN); OVERRIDE;
VAR count: LONGINT;
x:INTEGER;
BEGIN
INHERITED DoWrite(aRefnum, makingCopy);
IF fPlayer1Turn
THEN x := 1
ELSE x := 2;
count := 2;
FailOSErr(FSWrite(aRefnum, count, @x));
count := SIZEOF(LineArray);
FailOSErr(FSWrite(aRefnum, count, @fLines));
count := SIZEOF(BoxArray);
FailOSErr(FSWrite(aRefnum, count, @fBoxes));
END;
{
THE END
Notice that there was no code here for moving/resizing
windows, printing, scrolling, displaying error messages,
handling DAs, ...
All these features are implemented automatically by MacApp.}
|