TweetFollow Us on Twitter

Beginning OOPs
Volume Number:7
Issue Number:8
Column Tag:Beginning OOPs

Object Programming With TCL

By John Lengyel, St. Louis, MO

[John Lengyel is a software engineer for Digital Systems Consultants of St. Louis, Missouri. He is currently developing applications on VAX/VMS and Apollo/Unix platforms. He is looking for Macintosh software development opportunities.]

The Objective

Over the past several years numerous articles have appeared in MacTutor espousing the virtues of MacApp. Not being a Macintosh programmer by profession and already owning and enjoying THINK Pascal and THINK C, I managed to suppress my desire to purchase MacApp. So when my THINK C 4.0 upgrade arrived I wanted to immediately dig into its object-oriented capabilities.

After reading the well written manual discussing object-oriented programming and the THINK Class Library (THINK C’s version of MacApp) and looking over the sample projects provided with the upgrade I thought it would be a good idea to do one or two MacApp to THINK Class Library (TCL) conversions before jumping off on my own. The first program I converted is the topic of this article. The program is based upon a similar program that was written by Larry Rosenstein and presented in the December, 1986 issue of MacTutor titled “Dots Game Shows MacApp”.

TCL and MacApp

At this point, I considered writing the “What is TCL” section of the article. But anyone who has read or can read past articles on MacApp will have a very good idea of what TCL is all about. While converting Dots to TCL, the similarity of TCL to MacApp became increasingly noticeable.

For example, in the MacApp version of Dots, the classes requiring redefinition are TApplication, TDocument, TView and TCommand. In the TCL version they are respectively CApplication, CDocument, CView (actually subclasses of CView are redefined) and CMouseTask (similar to MacApp’s TCommand). The functionality provided by these corresponding classes is nearly identical. This similarity in name and function also extends, to a slightly lesser degree, down into the method level.

Studying MacApp articles and code should be helpful to anyone trying to learn how to write programs in TCL. As an additional TCL specific reference, an overview of TCL was provided by Alastair Dallas in the October, 1989 issue of MacTutor. Rather than restating what these people have said about MacApp and TCL I will concentrate my efforts on the classes that needed to be redefined for Dots and in particular on their overridden methods.

About Dots...

For those of you who while in high school actually studied during study hall and do not know how to play Dots, here is a description of the game. Dots is played on a matrix of dots. Players take turns drawing lines connecting adjacent dots. When a player draws a line that completes a square in the matrix, that player is awarded the square. A mark is placed in the square to indicate player ownership. The player completing the square may then take another turn. The game ends when all lines in the matrix are drawn. The player awarded the most squares wins the game.

In the TCL version of Dots, which has essentially the same design as its MacApp counterpart, the game is played in a window. The number of squares awarded each player is displayed in the top portion of the window. These numbers are hereafter referred to as the player’s scores. The dot matrix over which the game is played is displayed in the bottom portion of the window and is scrollable. Lines between adjacent dots are drawn with mouse clicks. When a square is completed, a graphic pattern associated with the moving player is used to fill an awarded square to indicate that player’s ownership of the square. The pattern is also used to display a frame around the appropriate player’s score. See Figure 1 for a view of the window used in Dots. As for as the playing of Dots, a correction to the original version was made so that when a player completes a square it is still that player’s turn.

Figure 1. Views of Dots' Document

Many useful features are exhibited in the Dots program. A multi-view window that can be scrolled, auto-scrolled, resized, zoomed and printed. Support for the undoing and redoing of moves. Multifinder compatibility and desk accessory support. Games can be saved and restored. Multiple windows can be opened and hence games played. All this and more in a fairly small amount of new, Dot specific code thanks to TCL.

Table 1 shows the five TCL classes that were redefined for Dots. These five subclasses and the “main” routine comprise all of the new code that was written for Dots. All other functionality is provided by the standard TCL code. The rest of this article will discuss each of the redefined classes and “main”.

TCL Subclass Abridged Description

CApplication cDotsApp Supervises program

CDocument cDotsDoc Manages window and its data

CPane cScorePane Displays scores

CPanorama cDotsPane Displays dot matrix

CMouseTask cDotsTask Handles mouse clicks in matrix

Table 1: TCL Redefines

Source Descriptions

The source files for Dots are organized in the manner recommended in the THINK C manual. Dots subclasses are defined in a “.h” header file and their methods in a “.c” file each having the name of the class. As an example, the subclass cDotsApp’s associated source files are cDotsApp.c and cDotsApp.h. I prefixed the Dots subclasses with a lower case “c” in order to distinguish them from the standard TCL classes which all start with an upper case “C”. There does not appear to be a standard for prefixing the names of class methods or variables in TCL. For class variables I followed the MacApp practice of preceding the variable name with a lower case “f”. As for class methods, standard TCL methods start with an upper case letter while Dot specific methods start in lower case.

The resources used in Dots were copied from the Starter Project supplied by THINK. A few modifications and additions were then made. A new string was added to STR# “Common” to supply text for displaying the “About...” alert. The “General” ALRT and DITL which are used to display the “About...” were then enlarged to accommodate the text. A DITL and DLOG were added to display a “Press CMD-. to stop printing” message. A WIND resource was modified for the document window. Finally, the string “Move” was added to STR# “Task Names”. This string is used by TCL to display the proper undo/redo command in the edit menu (“Undo Move” or “Redo Move”).

Main

The main routine when using TCL is rather trivial. It creates an application object and sends it an initialization message. Main then tells the application to start running. Finally, when the application finishes running, Main sends an exit message to the application. Exit() gives the application a last chance to clean up before the application terminates. For instance, temporary files may need to be deleted. Dots does not do anything in the Exit() routine. It was left in for illustrative purposes. All very neat, very orderly. Enough said.

cDotsApp::CApplication

Only one application is created for the entire project. As previously stated, the Main routine of the project creates the application object, sends the application an initialization request and then tells the application to begin running. While running, the application directs the processing of events, gives MultiFinder support, takes some wrinkles out of memory management, updates menus, handles DAs and is also capable of handling some of the basic File and Edit menu commands. All of this runtime support is provided in Dots by the standard TCL code.

To understand program flow using TCL it is important to know about the chain of command. A chain of command is used to determine which object handles a direct command. A direct command is a request that an object perform some action. They usually come about from menu selections. Direct commands that cannot be handled by an object move up the chain of command. The application is at the top of the chain of command. By the time the application receives the command it has been passed on by the views and the document. If the application does not handle the command, the command will not be handled. A chain of command for Dots is depicted in Figure 2.

Figure 2. The Chain of Command in Dots

The application’s inherited initialization method must be overridden. The Dots override for this method calls the inherited application’s initialization method. This must always be done because the inherited method initializes the Macintosh Toolbox, sets up menus, initializes application specific TCL globals and does a host of other very helpful but tedious things. The Dots application initialization method also sets up two global variables each of which are the patterns associated with a particular player. The use of these patterns is discussed in the cDotsPane and cScorePane sections of this article. These are application globals because they apply to all games being played and are not game dependent.

The application method SetUpFileParameters() was overridden in order to identify the types of files the Dots application recognizes. These types are used for the Macintosh Toolbox Standard File routines when opening a file. The application method DoCommand() was overridden so that a simple “About” alert could be posted when the “About Dots...” command was selected from the Apple menu. Unlike MacApp, TCL does not support an inherent about box. The application methods CreateDocument() and OpenDocument() were overridden so that Dots documents could be created whenever a “New...” or “Open...” was chosen from the File menu.

cDotsDoc::CDocument

The document associates a window with the data that is to be displayed in the window. The data contained in the document is displayed to the user in the window and may be stored on disk as a file. A subclass of CDocument or its superclass CDirector must be created for every type of window the application supports (CDirector does not have methods for filing or printing). A document object is created whenever “New” or “Open...” is chosen from the File menu.

Documents are in the chain of command. They manage the communication between windows, files and menu commands. If a document cannot handle a command, it passes it up to the application. Methods in the document class are provided to create, initialize and dispose of documents, open and close windows, read and write data to disk, access the document data, support printing, support the undoing and redoing of tasks and update menus.

cDotsDoc is the only document subclass in Dots. Each cDotsDoc object that is created associates the window and data for a particular game. The data needed to store the state of a Dots game must contain the following information: whether or not a line is to be drawn between each adjacent dot in the matrix, the ownership of each completed square, the player who is to move next.

The inherited document’s initialization method must be overridden. The Dots override for this method calls the inherited initialization method in order to put the document in the chain of command and initialize some document variables for filing, printing, recording moves, and menu updating. The Dots data is then initialized to a new game state.

The inherited document methods for creating a new file or opening an existing file must be overridden because they do not do anything. The code for these routines comes out of the sample programs provided with THINK C. Adaptations were made for Dots. The main adaptation involves storing the data read in from an opened file into the document. This is accomplished in the auxiliary method storeData() (Auxiliary means it is called by other methods within the object in which it is located but not by methods of other objects. THINK C 4.0 does not support private methods).

Both the new file and open file methods call an auxiliary method, BuildWindow(), to create a window and the views contained in the window. There are actually four views in the window. A subclass of CPane is used to display the score and current player turn. A subclass of CPanorama displays the view of the dot matrix. These subclasses, cScorePane and cDotsPane, are described in their respective sections. The two other views in the window are objects of CScrollPane and CSizeBox.

The dot matrix view must be scrollable which is why it is a subclass of CPanorama. CPanorama has methods that support scrolling. CScrollPane implements a view with scroll bars to control a panorama. Both CScrollPane and CPanorama are descendants of CPane which is itself a descendent of CView. They work in tandem to provide the scrolling capabilities of Dots.

CSizeBox, also a descendant of CPane, is used by CScrollPane to draw a grow icon in the lower right corner of any pane. By using an icon for the size box, TCL gains some flexibility in its placement of the size box. The size box no longer has to appear in the lower right corner of the window. However, in the case of Dots, that’s where it’s at man.

The code for BuildWindow() is also adapted for Dots from the sample programs. However, I spent a great deal of time reworking the BuildWindow() code for Dots. This was in large part caused by my lack of knowledge as to how some of the standard TCL code worked.

CDecorator is a TCL class that is used to arrange windows on the screen. One of its methods, PlaceNewWindow(), is used by BuildWindow() to implement window staggering. Some of the sample programs have BuildWindow() calling PlaceNewWindow() immediately after the window is created. I followed their example and got some bad results. The scroll bars for the dot matrix view were being placed beneath the window. It turned out that PlaceNewWindow() was shortening the window. Then when the scroll bars were created, they were placed according to the original size of the window. An easy solution was to make the call to PlaceNewWindow() at the end of BuildWindow() after all the views and scroll bars were in place.

Another problem I had involved the use of the inherited document variable itsMainPane. itsMainPane is a variable of type CPane. As stated in the THINK C manual, itsMainPane should point to the document’s main pane and if the document has no main pane, itsMainPane should be NULL. Many of the document’s print routines check to see if itsMainPane is NULL. If it is, they bypass their print code.

Since the Dots window is broken up into a score view and a dots matrix view it did not have what I considered to be a main view. But I wanted to be able to print and so did not want to leave itsMainPane NULL. My initial solution was to use itsMainPane as a view of the entire window and attach all of the other views directly or indirectly to it. This worked fine until I tried to print. All that printed was a page eject. After much digging, I found the problem.

In preparation for printing, the printer object was setting the current port to the printer. Then when the draw message went out to itsMainPane, which had no functionality of its own, it passed the message on to all of its attached views. CScrollPane was the first view that attempted to print. However, the scroll pane has scroll bars attached to it and scroll bars are not supposed to be printed. When the scroll bars received their draw message they set the current port to the window and left it there. Because of this, all of the other views poured themselves onto the screen as well.

After several other arrangements of views in BuildWindow() failed for various reasons I finally decided to just override all of the document’s print routines that used itsMainPane (there were only a few). In the override print routines, two variables were used to replace itsMainPane. One pointed to the score view object and the other to the dot matrix view object. Problem solved.

While I am on the topic of printing I may as well discuss a couple of problems that occurred when attempting to print from the Finder. Both problems were caused by errors in the standard TCL code. In correcting the problems I did not want to modify the TCL source code files in which the errors occurred. This should only be done by THINK. So the simplest solution, which occurred at 3 am after four hours of debugging, was to embed the corrections in a couple of document methods.

The first printing problem involved the use of the global variable gGopher. gGopher points to the first object in the chain of command that gets a chance to handle a command. If the gopher cannot handle the command it sends the command up the chain of command on to its supervisor. The document stores in one of its instance variables a pointer to a view that it encloses. That view is to be used as the gopher when the document is activated. When no document is active the application is the gopher.

When printing a document from the Finder, the application method Preload() sends the document an open file message and then tells the gopher to print. But when I tried to do this in Dots, the documents opened but did not attempt to print. The problem was that gGopher was pointing to the application object at this point rather than the document’s gopher as it should have been. The document’s OpenFile() method had sent a Select() message to its window in order to activate the window. However, Select() only sets gGopher to the document’s gopher if the application was in the background. Since Dots was not in the background gGopher remained pointing at the application. My solution was to force an activate after Select() was called from the document’s OpenFile() method. This sets gGopher to the document’s gopher which when told by Preload() to print passes the print message on to the document where it is finally handled.

The second printing problem was aesthetic in nature. While a document is printing the cursor is set to the watch. When the printing is finished the cursor gets set back to an arrow. Using TCL this arrow reset occurs during the main event loop when DispatchCursor() is called. However, when printing multiple documents from the Finder control does not return to the main event loop between print jobs. Succeeding print job dialogs appear with the watch cursor still in use. For a fix I set the cursor back to an arrow during the document’s DonePrinting() method.

The inherited document methods for handling “Save”, “Save As...” and “Revert” commands do nothing and must be overridden. Again, the code for these routines was easily adapted for Dots from the sample programs. No problem.

Finally, document methods are provided for accessing the Dots-specific instance variables for line states, square states and player turn. These are the only new methods that needed to be developed for the document. Admittedly, there were quite a few code changes that had to be made in the override methods. But the bulk of those changes involved that wonderful programming technique called “cut and paste”. Minor modifications were then made to the “pasted” code.

cScorePane::CPane

As stated in the TCL manual, a pane is a view that defines a drawing area within a window or another pane. All drawing takes place within a pane. Each pane has its own drawing environment and coordinate system. Panes are used for the drawing of non-scrolling views. Panes may also handle mouse clicks.

To understand how objects are drawn using TCL it is important to know about the visual hierarchy. The visual hierarchy is composed of all the visual objects in the application. At the top of the visual hierarchy is the desktop. Except for the desktop, all of the visual objects appearing on the screen are enclosed by other visual objects. The desktop encloses all of the windows. A window encloses all of its views. Views may enclose other views.

Figure 3 shows the visual hierarchy for a Dots application that currently has two games in progress. Note that the visual hierarchy changes as the application runs. Whenever a new game is started a document is created. The document’s window is added to the desktop and the views are added to the window. When a game is closed the window and its enclosed views are removed from the visual hierarchy.

Figure 3. Visual Hierarchy

When a window update or activate event occurs the message moves down the visual hierarchy from the desktop to the appropriate window. Each view attached to the window is passed a draw message. If any part of the view is contained in the area to be redrawn the view will call its own draw routine and then pass the draw message on to each of its subviews. The subviews will continue this process until the bottom of the visual hierarchy is reached.

Panes have draw methods, print methods, mouse methods, cursor methods, methods for changing their appearance, coordinate transformation methods and more. Because panes are views, they are part of the chain of command as well as the visual hierarchy. A pane’s supervisor in the chain of command is usually its director or document.

In Dots, the score view is a subclass of CPane. One score view object is created by the document’s BuildWindow() method whenever a document is created. The score view displays each player’s score in the document’s window and highlights the score of the player who is currently moving. The currently highlighted score is tracked by an instance variable contained in the score view object. The score view does not handle mouse clicks.

The inherited score view’s initialization method must be overridden. The Dots override for this method, IScorePane(), calls the inherited initialization method in order to put the score view in the chain of command and in the visual hierarchy and to initialize inherited instance variables. IScorePane() initializes its instance variable for the currently highlighted score to that of the player who is currently moving.

The only other inherited method to be overridden is the draw method. The code for drawing the line between the score view and the dot matrix view did not appear in the source listings of the MacApp version of Dots. My assumption is that MacApp was somehow tasked to draw the line. TCL has a similar capability through the use of a subclass of CPane called CBorder. Objects of CBorder are panes with the inherent capability of drawing a border around their perimeter. Rather than use CBorder for the score view’s superclass I chose to draw the line myself in the draw method. This is the only difference between the MacApp and TCL versions of Draw();

Four new methods were needed in the score view (they appeared in the MacApp version but are new in the sense that they are not inherent CPane class methods). The new methods are used to calculate the current score, obtain the rectangle around a player’s score, invalidate the area around a player’s score so that it will be redrawn with a window update event and switch the highlighted score when a player moves.

A final note about drawing. All drawing in a pane takes place in the pane’s own coordinate system. This simplifies the drawing of a pane since the pane may now be repositioned or modified possibly in other ways without affecting the drawing code. Panes become independent entities. During an update or activate event, before the pane’s draw routine is called, another inherent pane class method is called by the standard TCL code to prepare the pane for drawing. This prepare method sets up the port and coordinates for the pane.

Some of the new methods for the score view may be called directly from other objects. When this happens the preparation method for the score view has not been called because an update or activate is not involved. This means that the current port and coordinate system are quite possibly not that of the score view. This means that erroneous drawing is quite possible. This means I was having a drawing problem until I started putting calls to prepare the score view for drawing before the calls for invalidating and erasing rectangles in the score view were made.

cDotsPane::CPanorama

A panorama is a pane with the added capability of scrolling. CPanorama is a subclass of a CPane. Panoramas inherit all of the qualities of a pane that were described in the section discussing the score view.

A panorama must be installed in a scroll pane to make scrolling possible. A scroll pane is a pane with a panorama and scroll bars to control what is being displayed in the pane. The scroll bars and panorama communicate through the scroll pane to scroll or shift an image. These objects, if they are to be used, should be created when the document is created. Attach the scroll pane to the window and the panorama to the scroll pane. The scroll bars are created and attached to the scroll pane by the inherent TCL code during the scroll pane’s initialization process.

The dot matrix view in Dots is a subclass of CPanorama. One dot matrix object and its scroll pane are created by the document’s BuildWindow() method whenever a document is created. The dot matrix view displays the dot matrix, the lines connecting adjacent dots and the awarded squares. Mouse clicks are handled by the dot matrix view so that players may select the lines connecting the dots.

The inherited dot matrix view’s initialization method must be overridden. The Dots override for this method, IDotsPane(), calls the inherited initialization method in order to put the score view in the chain of command and in the visual hierarchy and to initialize inherited instance variables. IDotsPane() sets the scrolling scale to the pixel distance between the dots in the matrix as opposed to the default distance of one dot. When a click is made in one of the scroll bars, the image will now scroll by the dot spacing amount rather than one pixel at a time.

The inherited draw method must be overridden. The override method is the same as that appearing in the MacApp version of Dots. In fact, all of the new methods for the dot matrix view are essentially the same as in the MacApp version. The only modification is that some of the routines required that the drawing preparation method be used to set the port and coordinate system to that of the dot matrix view. The reasons for this were discussed in the score view section of this article.

Mouse tracking is handled a little differently in TCL than MacApp. The inherited method DoClick() must be overridden to enable mouse tracking. In Dots, the DoClick() method creates a mouse task to handle the mouse down. The inherited method TrackMouse() is called to follow the mouse down until the mouse button is released. DoClick() must notify the document of the move so that the previous move may be disposed of and the current move saved for the undo process.

The TrackMouse() method called by DoClick() sends the newly created mouse task a BeginTracking() message to give the mouse task an opportunity to make any adjustments before mouse tracking starts. TrackMouse() then repeatedly calls the mouse task method KeepTracking() while the mouse button remains down. When the button is finally released, TrackMouse() sends an EndTracking() message to the mouse task. It is up to the mouse task to initiate any action based upon the mouse click.

cDotsTask::CMouseTask

Mouse tasks are used to track mouse downs within a pane. A mouse task is a subclass of a task. Mouse tasks therefore inherit the task’s methods for implementing undoable actions.

Dots uses a mouse task to track mouse downs in the dot matrix view and to provide for the undoing and redoing of moves. A mouse task is created by the dot matrix view’s DoClick() method whenever there is a mouse down in the dot matrix view. The Dot’s process for handling the mouse down is discussed in the section on the dot matrix view.

The mouse task’s initialization method is a good place to initialize the instance variables that will be saved in the document for undoing the mouse click. The inherent initialization method should be called to store the undo/redo string in the edit menu.

The inherent mouse tracking methods BeginTracking(), KeepTracking() and EndTracking() do not do anything. BeginTracking() should be overridden if the starting point for the mouse down needs to be altered or some initial actions need to be performed for the mouse down. KeepTracking() and EndTracking() must always be overridden. EndTracking() should store the information needed to undo the mouse task in the instance variables of the mouse task.

Dots overrides all three of these mouse tracking methods in order to provide autoscrolling, line selection, the highlighting and unhighlighting of selectable lines as the cursor moves across the dot matrix view and the storage of the move for undoing the mouse task. The TCL code for handling mouse clicks in Dots varies considerably from the code in the MacApp version. However, the functionality is the same and the steps that must be taken to achieve that functionality are fairly easy to follow.

The methods for doing, undoing and redoing a mouse task in Dots are all overrides that mimic their MacApp counterparts. The only new method in the mouse task is used by the document to check on the validity of the move. If the just completed mouse task performed a valid move then the mouse task is saved in the document for undo. Invalid moves are disposed of.

Conclusion

Overall, working with TCL was an enjoyable experience. The inherited code for menu updating, scrolling, Multifinder and DA support, window manipulation and memory management is quite extensive and greatly simplifies the programming process. Good job THINK.

As pointed out in the article, there were a few TCL glitches that caused me hours of consternation. These problems will clear up over time. But one thing I noticed as I tried to track down the errors was that TCL programs and probably object-oriented programs in general can be hard to debug.

Sometimes it is difficult to tell, by looking at the source code, what object is being passed a message and hence where a problem may be occurring. For example, the TCL global variable gGopher is a handle to an object that is a subclass of CBureaucrat. These objects may be applications, documents, views or instances of other classes that inherit bureaucratic traits. By looking at a line like ‘gGopher->DoCommand(theCommand)’ it is impossible to tell which type of bureaucrat is being referenced. The code must be stepped through at runtime to identify the bureaucrat. Program flow can also be a bit confusing to follow because it jumps around a lot from method to method and object to object (at least it was confusing to me).

To help overcome these difficulties the TCL code is well commented and the manual well written. The THINK C runtime debugger is an excellent tool for stepping through code and tracking down errors. It is also a good tool with which to learn exactly how TCL operates.

/********************************************
 dotsTypes.h
Types and constants used throughout Dots
*********************************************/
 
#include <Constants.h>

 /* Include this file only once*/
#define _H_dotsTypes
 /* Rows and columns are 0 thru n. Size of dot
 ** matrix should be even to avoid ties
 ** (n+1 should be even) */
#define kMaxCol  5
#define kMaxRow  5
 /* Player square IDs */
#define  kBoxPlayer1 4
#define kBoxPlayer25
 /* Player IDs */
#define kPlayer1 0
#define kPlayer2 1
 /* Number of sides completed. 4 means completed
 ** by player 1, 5 means completed by player 2 */
typedef short  tBoxState;
 /* Line State - true if line drawn */
typedef Boolean  tLineState;
 /* Line directions (horiz, vert) */
typedef enum {hLine, vLine} tLineDir;

/****** myGlobals.c
Declarations of Global Variables for Dots ******************************************/

 /* The patterns of the 2 players */
Pattern dgPlayerPats[2];

/****************************
 Dots.c
The main file for Dots.
Uses the THINK Class Library
*****************************/

#include “cDotsApp.h”

extern  CApplication *gApplication;

void main()
{
 /* Create Dots application */
 gApplication = new(cDotsApp);
 /* Init Dots application */
 ((cDotsApp *)gApplication)->IDotsApp();     
 gApplication->Run();
 gApplication->Exit();
}

/**************************************
 cDotsApp.h
Application class for Dots game
***************************************/

#define _H_cDotsApp
#include <CApplication.h>

struct cDotsApp : CApplication
{
 /* Initialization Methods */
 void IDotsApp(void);
 
 /* Override Methods */ 
 void SetUpFileParameters(void);
 void DoCommand(long theCommand);
 void CreateDocument(void);
 void OpenDocument(SFReply *macSFReply);
};

/****************************
 cDotsApp.c
SUPERCLASS = CApplication
Dots application methods
*****************************/

#include <CError.h>
#include <Commands.h>
#include <Constants.h>
#include “cDotsApp.h”
#include “cDotsDoc.h”
#include “dotsTypes.h”

 /*** Class Constants ***/
 /* About... string index in STRcommon */
#define I_ABOUT  8
 
 /*** TCL Defined Globals ***/
extern  CError *gError;
extern  OSType gSignature;
 /*** Dots Defined Globals ***/
extern  Pattern  *dgPlayerPats;

/***** I N I T I A L I Z A T I O N *****/

/*** IDotsApp
Initialize the application. At least call the inherited method. Initialize 
application instance variables or globals.
*/
void cDotsApp::IDotsApp(void)
{
 /*Set moreMasters, rainyDayFund, creditLimit*/
 CApplication::IApplication(4, 20480L, 2048L);
 
 /* Set patterns of each player */
 BlockMove(ltGray, &dgPlayerPats[kPlayer1],
 sizeof(Pattern));
 BlockMove(dkGray, &dgPlayerPats[kPlayer2],
 sizeof(Pattern));
}

/*** SetUpFileParameters {OVERRIDE}
Specify the kinds of files the application opens
*/
void cDotsApp::SetUpFileParameters(void)
{
 inherited::SetUpFileParameters();
 gSignature = ‘dots’;/* File creator */
 sfNumTypes = 1;
 sfFileTypes[0] = ‘DATA’;
}

/********** C O M M A N D ********/

/*** DoCommand {OVERRIDE}
Last chance to respond to menu selection. Call the default method for 
standard commands.
*/
void cDotsApp::DoCommand(long theCommand)
{
 switch (theCommand) {
 
 case cmdAbout:
 /* Show the ‘about’ alert */
 gError->PostAlert(STRcommon, I_ABOUT);
 break;
 default:
 inherited::DoCommand(theCommand);
 break;
 }
}

/********** D O C U M E N T ********/

/*** CreateDocument{OVERRIDE}
New was chosen from the File menu. Create a document and send it a NewFile() 
message.
*/
void cDotsApp::CreateDocument()
{
 cDotsDoc *theDocument;
 
 theDocument = new(cDotsDoc);
 theDocument->IDotsDoc(this, TRUE);
 theDocument->NewFile();
}

/*** OpenDocument{OVERRIDE}
Open  was chosen from the File menu. Create a document and send it an 
OpenFile() message. The macSFReply is a good SFReply record that contains 
name and vRefNum of file user chose to open.
*/ 
void cDotsApp::OpenDocument(SFReply *macSFReply)
{
 cDotsDoc *theDocument;
 
 theDocument = new(cDotsDoc);
 theDocument->IDotsDoc(this, TRUE);
 theDocument->OpenFile(macSFReply);
}

/************************************
 cDotsDoc.h
Document class for Dots game
*************************************/

#define _H_cDotsDoc
#include <CDocument.h>

#include “cDotsPane.h”
#include “cScorePane.h”
#include “dotsTypes.h”

 /*** Class Types ***/
 /* Types used to store state of game */
typedef tLineState
 tLines[vLine+1][kMaxRow+1][kMaxCol+1];
typedef tBoxState tBoxes[kMaxRow+1][kMaxCol+1];

struct cDotsDoc : CDocument
{
 /* Instance Variables */
 cDotsPane*fDotsPane;/* Matrix view */
 cScorePane *fScorePane;  /* Score view */
 BooleanfPlayerTurn; /* Whose turn */
 tLines fLines;  /* State of every line */
 tBoxes fBoxes;  /* State of every box */
 DialogPtrprintBanner;  /* Print box */

 /* Initialization Methods */
 void   IDotsDoc(CBureaucrat *aSupervisor,
 Boolean printable);

 /* Override Methods */
 void   NewFile(void);
 void   OpenFile(SFReply *macSFReply);
 BooleanDoSave(void);
 BooleanDoSaveAs(SFReply *macSFReply);
 void   DoRevert(void);
 short  PageCount(void);
 void   AboutToPrint(short *firstPage,
 short *lastPage);
 void   PrintPageOfDoc(short pageNum);
 void   DonePrinting(void);
 /* Auxiliary Methods */
 void BuildWindow(Handle theData);

 /* New Methods */
 void   storeData(Handle theData);
 tBoxStategetBoxState(int row, int col);
 BooleangetLineState(tLineDir direction,
 int row, int col);
 void setLineState(tLineDir direction, int row,
 int col, Boolean thePlayer, Boolean newState);
 BooleanchangeBoxState(int row, int col,
 Boolean thePlayer, Boolean addingLine);
 BooleangetPlayerTurn(void);
};

/************************************
 cDotsDoc.c
 SUPERCLASS = CDocument
Methods for the Dots Document. The document has one window. The window 
containts a scrollable dots pane where the matrix is displayed and a 
non-scrolling score pane which displays the score and current player 
turn.
*************************************/

#include <CApplication.h>
#include <CBartender.h>
#include <CDataFile.h>
#include <CDecorator.h>
#include <CDesktop.h>
#include <CError.h>
#include <Commands.h>
#include <Constants.h>
#include <CPanorama.h>
#include <CPrinter.h>
#include <CScrollPane.h>
#include <TBUtilities.h>

#include “cDotsDoc.h”
#include “cDotsPane.h”
#include “cScorePane.h”
#include “dotsTypes.h”

 /*** Class Constants ***/
#define DLOG_PRINT 2000 /* Print dialog*/
#define kBoxFree 0 /* Initial box state */
#define kDotsTop   44
#define kScoreBottom (kDotsTop)
#define strABORTPRINT9
#define WINDDots 128

 /*** TCL Defined Globals ***/
extern  CApplication *gApplication;
extern  CBartender *gBartender;
extern  CDecorator *gDecorator;
extern  CDesktop *gDesktop;
extern  OSType   gSignature;
extern  CError   *gError;

/***** C O N S T R U C T I O N *****/

/*** IDotsDoc
The document’s initialization method. Initialize documents instance variables. 
Always invoke the default method.
*/
void cDotsDoc::IDotsDoc(
 CBureaucrat*aSupervisor,
 Boolean printable)
{
 int    row;
 int    col;
 
 /* Init doc and make printable */
 CDocument::IDocument(aSupervisor, printable);

 fPlayerTurn = kPlayer1;  /* Player 1 first */

 /* Clear matrix and boxes */
 for (row = 0; row <= kMaxRow; row++)
 {
 for (col = 0; col <= kMaxCol; col++)
 {
 fLines[hLine][row][col] = false;
 fLines[vLine][row][col] = false;
 fBoxes[row][col] = kBoxFree;
 }
 }

}

/********** F I L E **********/

/*** NewFile{OVERRIDE}
When the user chooses New from the File menu, the CreateDocument() method 
in the Application class will send a newly created document this message. 
This method needs to create a new window, ready to work on a new document.
*/
void cDotsDoc::NewFile()
{
 Str255 wTitle;  /* Window title */
 short  wCount;  /* Index of new window*/
 Str63  wNumber; /* Index as a string */

 /* Create empty window */
 BuildWindow(NULL);
 /* Append index to window name */
 itsWindow->GetTitle(wTitle);
 wCount = gDecorator->GetWCount();
 NumToString((long)wCount, wNumber);
 ConcatPStrings (wTitle, (StringPtr) “\p-”);
 ConcatPStrings(wTitle, wNumber);
 itsWindow->SetTitle(wTitle);
 
 itsWindow->Select();/* active window */
}

/*** OpenFile  {OVERRIDE}
When Open  is chosen from the File menu, the OpenDocument() method in 
the Application class will let the user choose a file and then send a 
newly created document this message. The information about the file is 
in the SFReply record. In this method, open the file and display it in 
a window.
*/
void cDotsDoc::OpenFile(SFReply *macSFReply)
{
 CDataFile*theFile;
 Handle theData;
 Str63  theName;
 OSErr  theError;
 
 /* Create a file and send it an SFSpecify()
 **   message to set up the name, volume, and
 ** directory */
 theFile = new(CDataFile);
 theFile->IDataFile();
 theFile->SFSpecify(macSFReply);
 itsFile = theFile;

 theError = theFile->Open(fsRdWrPerm);

 /* If open error, CheckOSError reports
 ** error in alert and returns false */
 if (!gError->CheckOSError(theError)) {
 Dispose(); /* Don’t need doc */
 return;
 }

 /* Read in the data - don’t use rainy
 ** day fund, its ok to fail */
 gApplication->RequestMemory(FALSE, TRUE);
 theFile->ReadAll(&theData);
 gApplication->RequestMemory(FALSE, FALSE);

 /* If not enough memory to open,
 ** post error and dispose doc */
 if (theData == NULL) {
 gError->CheckOSError(MemError());
 Dispose();
 return;
 }
 /* Create empty window */
 BuildWindow(NULL);

 /* Store data as instance variables in
 ** document class */
 storeData(theData);
 DisposHandle(theData);

 /* Put file name into window title */
 itsFile->GetName(theName);
 itsWindow->SetTitle(theName);
 itsWindow->Select(); /* Activate window*/
 /* Select only activates if application
 ** was in background. This may cause
 ** problems (see cDotsDoc section of
 ** article on finder print problem). So
 ** let’s force activate here. */
 ((CDirector*)itsWindow->
 itsSupervisor)->Activate();
}

/*** BuildWindow
Create window  */
void cDotsDoc::BuildWindow (Handle theData)
{
 CScrollPane*theScrollPane;
 Rect   r;
 
 /* Create window */
 itsWindow = new(CWindow);
 itsWindow->IWindow(WINDDots, FALSE,
 gDesktop, this);
 /* Set windows max and min sizes */
 itsWindow->GetInterior(&r);
 SetRect(&r, MIN_WSIZE, MIN_WSIZE,
 r.right - r.left, r.bottom - r.top);
 itsWindow->SetSizeRect(&r);

 /* Create scroll pane in the window */
 theScrollPane = new(CScrollPane);
 theScrollPane->IScrollPane(itsWindow,
 this, 0, 0, 0, 0, sizELASTIC,
 sizELASTIC, TRUE, TRUE, TRUE);
 theScrollPane->FitToEnclFrame(TRUE, TRUE);
 /* Leave space at top of window for
 ** score pane, it does not scroll */
 SetRect(&r, 0, kDotsTop, 0, 0);
 theScrollPane->ChangeSize(&r, FALSE);
 
 /* Create dots pane in scroll pane */
 fDotsPane = new(cDotsPane);
 fDotsPane->IDotsPane(theScrollPane, this,
 0, 0, 0, 0, sizELASTIC, sizELASTIC);
 /* Fit dots pane to interior of scroll
 **pane Interior excludes scroll bars */
 fDotsPane->FitToEnclosure(TRUE, TRUE);
 /* Bounds were initialized to 0’s so
 ** must set bounds to new frame size */
 fDotsPane->GetFrameSpan(&r.right, &r.bottom);
 SetRect(&r, 0, 0, r.right, r.bottom);
 fDotsPane->SetBounds(&r);
 /* Put dots pane in panorama */
 theScrollPane->InstallPanorama(fDotsPane);
 itsGopher = fDotsPane;
 
 /* Create score pane in the window */
 fScorePane = new(cScorePane);
 fScorePane->IScorePane(itsWindow, this,
 0, 0, 0, 0, sizELASTIC, sizFIXEDTOP);
 fScorePane->FitToEnclFrame(TRUE, TRUE);
 /*  Position above dots pane */
 fScorePane->GetFrame(&r);
 SetRect(&r, 0, 0, 0, kScoreBottom - r.bottom);
 fScorePane->ChangeSize(&r, FALSE);
 
 /* Allow for staggered window placement (NOTE:          ** If this routine 
is to be used at all, it
 ** must come after subviews are placed in
 ** window or strange things happen) */
 gDecorator->PlaceNewWindow(itsWindow);
}

/*** DoSave {OVERRIDE}
Save doc data to disk. ‘Save’ chosen from menu. */
Boolean cDotsDoc::DoSave()
{
 OSErr  errCode;
 long   theLength;
 long   totalLength;
 int    x;
 
 if (itsFile == NULL)
 return(DoSaveFileAs());
 else
 {
 /* Go to beginning of file */
 errCode = ((CDataFile *)itsFile)->
 SetMark(0, fsFromStart);
 if (errCode != noErr)
 return(gError->CheckOSError(errCode));
 
 /* Write player turn */
 if (fPlayerTurn == kPlayer1)
 x = 1;
 else
 x = 2;
 errCode = ((CDataFile*)itsFile)->
 WriteSome((Ptr)&x, 2L);
 if (errCode != noErr)
 return(gError->CheckOSError(errCode));
 totalLength = 2;
 
 /* Write line states */
 theLength = sizeof(tLines);
 errCode = ((CDataFile*)itsFile)->
 WriteSome((Ptr)fLines, theLength);
 if (errCode != noErr)
 return(gError->CheckOSError(errCode));
 totalLength += theLength;
 
 /* Write box states */
 theLength = (long)sizeof(tBoxes);
 errCode = ((CDataFile*)itsFile)->
 WriteSome((Ptr)fBoxes, theLength);
 if (errCode != noErr)
 return(gError->CheckOSError(errCode));
 totalLength += theLength;
 
 /* Set file length in case previous
 ** contents was bigger. Can’t happen
 ** in Dots but left in for example*/
 errCode = ((CDataFile *)itsFile)->
 SetLength(totalLength);
 if (errCode == noErr)    /* Force write*/
 errCode = FlushVol(“”, itsFile->volNum);
 if (errCode != noErr)  
 return(gError->CheckOSError(errCode));

 dirty = FALSE;  /* Document now clean */
 gBartender->DisableCmd(cmdSave);
 return(TRUE);
 }
}

/*** DoSaveAs {OVERRIDE}
Save As  chosen from File menu. The default DoCommand() method for documents 
sends a DoSaveFileAs() message which displays a standard put file dialog 
and sends this message. The SFReply record contains all the information 
about the file to create.
*/
Boolean cDotsDoc::DoSaveAs(SFReply *macSFReply)
{
 if (itsFile != NULL)/*Close already open file*/ 
 itsFile->Dispose(); 

 /* Display standard file dialog and
 ** create new file */
 itsFile = new(CDataFile);
 ((CDataFile *)itsFile)->IDataFile();
 itsFile->SFSpecify(macSFReply);
 itsFile->CreateNew(gSignature, ‘DATA’);
 itsFile->Open(fsRdWrPerm);

 /* Set window title to new file name */
 itsWindow->SetTitle(macSFReply->fName);

 return( DoSave() ); /* save normally */
}

/*** DoRevert {OVERRIDE}
Close current file (without writing out) and read the last saved version 
of file. */
void cDotsDoc::DoRevert()
{
 Point  homePos;
 Handle theData;
 
 /* Make sure file is open */
 if (itsFile == NULL)
 return;/* Could use an error message here */

 /* Close file and reopen */
 itsFile->Close();
 if (!gError->CheckOSError(itsFile->
 Open(fsRdWrPerm)))
 return;

 /* Get rid of last undo */
 if (lastTask != NULL) {
 lastTask->Dispose();
 lastTask = NULL;
 }
 
 /* Read in the data */
 /* Don’t tap fund, can fail */
 gApplication->RequestMemory(FALSE, TRUE);
 ((CDataFile *)itsFile)->ReadAll(&theData);
 gApplication->RequestMemory(FALSE, FALSE);

 /* Check to see if data was read */
 if (theData == NULL) {
 gError->CheckOSError(MemError());
 Dispose();
 return;
 }
 
 /* Store the data into the document */
 storeData(theData);
 DisposHandle(theData);

 /* Set panoramas to home position */
 ((CPanorama*)fDotsPane)->
 GetHomePosition(&homePos);
 ((CPanorama*)fDotsPane)->
 ScrollTo(homePos, FALSE);
 
 /* Force redraw of panes on update */
 fDotsPane->Refresh();
 fScorePane->Refresh();
 
 dirty = FALSE;
}

/*** storeData
Store the file data as as instance variables in the document */
void cDotsDoc::storeData(Handle theData)
{
 int    x;
 long count;

 HLock(theData);
 /* read whose turn it is */
 BlockMove(*theData, &x, 2);
 if (x == 1)
 fPlayerTurn = kPlayer1;
 else
 fPlayerTurn = kPlayer2;
 
 /* read the line states */
 x = sizeof(tLines);
 count = (long)x;
 BlockMove(*theData+2, fLines, count);
 
 /* read the box states */
 count = (long)sizeof(tBoxes);
 BlockMove(*theData+x+2, fBoxes, count);
 HUnlock(theData);
}

/******* P R I N T I N G ********/
/*** PageCount {OVERRIDE}
Return the number of pages in a Document */
short cDotsDoc::PageCount()
{
 long pixWidth;
 long pixHeightDots;
 long pixHeightScore;
 
 /* Get dimensions of dots pane */
 fDotsPane->GetPixelExtent(&pixWidth,
 &pixHeightDots);
 /* Get dimensions of score pane */
 fScorePane->GetPixelExtent(&pixWidth,
 &pixHeightScore);
 /* Pages have a fixed pixel height */
 return((pixHeightDots + pixHeightScore) /
 pageHeight + 1);
}
 
/*** AboutToPrint{OVERRIDE}
The specified range of pages is about to be printed, display print banner 
and let panes know they are about to be printed.
*/
void  cDotsDoc::AboutToPrint(
 short  *firstPage,
 short  *lastPage)
{
 Str255 docName;
 
 inherited::AboutToPrint(firstPage, lastPage);
 
 /* Let panes know they’re to print */
 fDotsPane->AboutToPrint(firstPage, lastPage);
 fScorePane->AboutToPrint(firstPage, lastPage);
 
 /* Let user know about printing */
 PositionDialog(‘DLOG’, DLOG_PRINT);
 printBanner = GetNewDialog(DLOG_PRINT,
 NULL, (WindowPtr) -1L);
 DrawDialog(printBanner);
 
 /* Doc name for LaserWriter status */
 GetName(docName); 
 SetWTitle(printBanner, docName);
}
 
/*** PrintPageOfDoc {OVERRIDE}
Print a single page of the document. */
void  cDotsDoc::PrintPageOfDoc(short pageNum)
{
 fDotsPane->PrintPage(pageNum, pageWidth,
 pageHeight);
 fScorePane->PrintPage(pageNum, pageWidth,
 pageHeight);
}
 
/***  DonePrinting {OVERRIDE}
Print loop has been completed. */
void  cDotsDoc::DonePrinting()
{
 short  itemType;
 Handle item;
 Rect   box;
 Str63  abortStr;
 long   ticks;
 
 /* If print aborted, display message */
 if (PrError() == iPrAbort) {
 GetDItem(printBanner, 1, &itemType, &item, &box);
 GetIndString(abortStr, STRcommon, strABORTPRINT);
 SetIText(item, abortStr);
 Delay(120, &ticks);
 }
 DisposDialog(printBanner);

 fDotsPane->DonePrinting();
 fScorePane->DonePrinting();

 /* Changing cursor to arrow. May not
 ** be returning to main event loop */
 SetCursor(&arrow);
}

/********* G A M E ***********/
/** getBoxState
Get state of a box. 3 or less means not yet completed, 4 means completed 
by player 1, 5 means completed by player 2. */
tBoxState cDotsDoc::getBoxState(int row, int col)
{
 return(fBoxes[row][col]);
}

/*** changeBoxState
Change state of box */
Boolean cDotsDoc::changeBoxState(int row, 
 int col, Boolean thePlayer,
 Boolean addingLine)
{
 tBoxStateboxState;
 Booleanchanged;
 int    delta;
 
 changed = false; /* Assume same box owner */
 boxState = fBoxes[row][col];
 
 if (addingLine) {
 /* Is box completed? */
 if (boxState < kBoxPlayer1) {
 boxState = boxState + 1;
 if (boxState == kBoxPlayer1) {
 changed = true; /* box ownership changed */
 
 /* Did player 2 complete box? */
 if (thePlayer == kPlayer2)
 boxState = kBoxPlayer2;
 
 /* Change box and score */
 fDotsPane->invalBox(row, col);
 fScorePane->invalScore(thePlayer);
 }
 }
 } else { /* Remove line */
 /* If box completed redraw box and score */
 if (boxState >= kBoxPlayer1) {
 changed = true; /* box ownership changed */
 fDotsPane->invalBox(row, col);
 fScorePane->invalScore(thePlayer);
 }
 
 /* Update state of box */
 if (boxState == kBoxPlayer2)
 boxState = 3;
 else
 boxState = boxState - 1;
 }
 fBoxes[row][col] = boxState;
 return changed;
}

/*** getLineState
Get state of line. True means there is a line in direction from the input 
dot, false no line. */
tLineState cDotsDoc::getLineState(tLineDir direction, int row, int col)
{
 return(fLines[direction][row][col]);
}

/*** setLineState
Change state of line. Set to true for a line or false for no line. Set 
to next player turn if
box ownership did not change. */
void cDotsDoc::setLineState(tLineDir direction,
 int row, int col, Boolean thePlayer,
 tLineState newState)
{
 BooleanaChange, bChange;
 BooleanoldState;
 
 oldState = getLineState(direction, row, col);
 /* Assume box ownership did not change */
 aChange = bChange = false;

 if (oldState != newState) {
 fLines[direction][row][col] = newState;
 /* must redraw line */
 fDotsPane->invalLine(direction, row, col);
 
 /* Change state of any box to right
 ** or under line */
 if ((row < kMaxRow) && (col < kMaxCol))
 aChange = changeBoxState (row, col,
 thePlayer,newState);
 /* Change state of any box to left or
 ** above line */
 if (direction == hLine) {
 if (row > 0)
 bChange = changeBoxState(row-1, col,
 thePlayer, newState);
 } else {
 if (col > 0)
 bChange = changeBoxState(row, col-1,
 thePlayer, newState);
 }
 /* If box ownership did not change it is
 ** other player’s turn */
 if (!aChange && !bChange) {
 fPlayerTurn = !fPlayerTurn;
 fScorePane->setTurn(fPlayerTurn);
 }
 }
}

/*** getPlayerTurn
Identify player whose move it is */
Boolean cDotsDoc::getPlayerTurn()
{
 return(fPlayerTurn);
}

/**************************************
 cScorePane.h
Class for score view
****************************************/
#define _H_cScorePane
#include <CPane.h>

 /*** Class Types ***/
typedef int tScores[2];

struct cScorePane : CPane
{
 /* Instance variables */
 BooleanfPlayerHilite;  /* Player highlighted */
 
 /* Initialization Methods */
 void IScorePane(CView *anEnclosure, 
 CBureaucrat *aSupervisor, short aWidth, 
 short aHeight, short aHEncl, short aVEncl,
 SizingOption aHSizing, SizingOption aVSizing);

 /* Override Methods */
 void Draw(Rect *area);

 /* New Methods */
 void calculateScore(tScores score);
 void   getPlayerRect(Boolean thePlayer, Rect *r);
 void invalScore(Boolean thePlayer);
 void setTurn(Boolean thePlayer);
};

/**************************************
 cScorePane.c
 SUPERCLASS = CPane
Methods for the score pane. The score pane displays the current player 
turn and calculates and displays the game score.
****************************************/
#include “cDotsDoc.h”
#include “cScorePane.h”
#include “dotsTypes.h”
#include <Global.h>

 /*** Class Constants ***/
#define kScorePlace30
#define kHalfSpacing (kScorePlace / 2)
#define kScoreSpacing(3*kScorePlace)
#define kScoreInset6

 /*** Globals ***/
extern  Pattern  *dgPlayerPats;

/******** C O N S T R U C T I O N ********/
/*** IScorePane
Initialize instance variables */
void cScorePane::IScorePane(CView *anEnclosure,    CBureaucrat *aSupervisor, 
short aWidth,
 short aHeight, short aHEncl, short aVEncl,
 SizingOption aHSizing, SizingOption aVSizing)
{
 /* Highlighted score is player to move next */
 fPlayerHilite = 
 ((cDotsDoc *)aSupervisor)->getPlayerTurn();
 
 /* Call inherited method */
 IPane(anEnclosure, aSupervisor, aWidth, aHeight,
 aHEncl, aVEncl, aHSizing, aVSizing);
}

/******** D R A W I N G **********/
/*** Draw {OVERRIDE}
The area parameter gives the portion of pane that needs to be redrawn. 
Area is in frame coordinates. */
void cScorePane::Draw(Rect *area)
{
 tScoresscore;
 Booleanplayer;
 Rect r;
 Str255 s;
 int    offset;
 
 TextFont(monaco);
 TextFace(NULL);
 TextSize(9);
 PenNormal();
 
 /* Draw line across bottom of pane */
 GetFrame(&r);
 MoveTo(r.left, r.bottom-1);
 LineTo(r.right, r.bottom-1);
 
 PenSize(kScoreInset, kScoreInset);
 calculateScore(score);
 
 for (player = kPlayer1; player <= kPlayer2;
 player++) {
 NumToString(score[player], &s);
 
 /* Center score in player’s rectangle */
 getPlayerRect(player, &r);
 offset = (r.right-r.left-StringWidth(s)) / 2;
 MoveTo(r.left + offset, r.bottom - 3);
 DrawString(s);
 InsetRect(&r, -kScoreInset, -kScoreInset);
 PenPat(&dgPlayerPats[player]);
 FrameRect(&r);
 }
 /* Hilight/Unhighlight player score */
 getPlayerRect(fPlayerHilite, &r);
 InvertRect(&r);
}

/*** calculateScore
Computes the current score */
void cScorePane::calculateScore(tScores theScore)
{
 tBoxStateboxState;
 int    row, col;
 
 theScore[kPlayer1] = 0;
 theScore[kPlayer2] = 0;
 
 /* Check box states for entire matrix */
 for (row = 0; row < kMaxRow; row++) {
 for (col = 0; col < kMaxCol; col++) {
 boxState = ((cDotsDoc *)
 itsSupervisor)->getBoxState(row, col);
 switch (boxState) {
 case kBoxPlayer1:
 theScore[kPlayer1] =theScore[kPlayer1]+1;
 break;
 case kBoxPlayer2:
 theScore[kPlayer2] =theScore[kPlayer2]+1;
 break;
 }
 }
 }
}

/*** getPlayerRect
Gets rectangle in which to draw player’s score */
void cScorePane::getPlayerRect(Boolean thePlayer, Rect *r)
{
 SetRect(r, kScorePlace, kHalfSpacing,
 2*kScorePlace, kScorePlace);
 if (thePlayer == kPlayer2)
 OffsetRect(r, kScoreSpacing, 0);
}

/*** invalScore
Invalidate player score rectangle so it will redraw on update
*/
void cScorePane::invalScore(Boolean thePlayer)
{
 Rect r;
 
 /* Get display rectangle for player score */
 getPlayerRect(thePlayer, &r);

 /* Set score pane’s port before invalidation
 ** otherwise draw in a different port */
 Prepare();
 InvalRect(&r);
}

/*** setTurn
Highlight current player’s turn. Actually draw on screen since it is 
easy to update turn indicator. */
void cScorePane::setTurn(Boolean thePlayer)
{
 Rect r;
 
 if (thePlayer != fPlayerHilite) {
 Prepare(); /* Sets up pane for drawing */
 
 /* Unhighlight currently highlighted score */
 getPlayerRect(fPlayerHilite, &r);
 InvertRect(&r);
 /* Highlight other player score */
 getPlayerRect(thePlayer, &r);
 InvertRect(&r);
 
 fPlayerHilite = thePlayer;
 }
}

/**************************************
 cDotsPane.h
Class for Dot Matrix View
****************************************/
#define _H_cDotsPane
#include <CPanorama.h>
#include “dotsTypes.h”

struct cDotsPane : CPanorama
{
 /* Initialization Methods */
 void   IDotsPane(CView *anEnclosure,
 CBureaucrat *aSupervisor, short aWidth,
 short aHeight, short aHEncl, short aVEncl,
 SizingOption aHSizing, SizingOption aVSizing);

 /* Override Methods */
 void Draw(Rect *area);
 void DoClick(Point hitPt, short modifierKeys,
 long when);

 /* New Methods */
 void drawCorner(int row, int col);
 void box2Rect(int row, int col, Rect *r);
 void dot2Rect(int row, int col, Rect *r);
 void dotCenter(int row, int col, Point *center);
 void line2Pt(tLineDir direction, int row,
 int col, Point *pt);
 void line2Rect(tLineDir direction,
 int row, int col, Rect *r);
 Booleanpt2Line(Point pt, tLineDir
 *direction, int *row, int *col);
 void invalLine(tLineDir direction,
 int row, int col);
 void   invalBox(int row, int col);
};

/*************************************************
 cDotsPane.c

 SUPERCLASS = CPanorama
Methods for the dot matrix view. The dot matrix view contains the grid 
where the game is actually played. Line segments are selected by clicking 
on the area between two horizontal or two vertical dots. When the four 
line segments surrounding a grid cell are selected, the pattern associated 
with the  player completing the ‘box’ is placed inside the cell.
*************************************************/
#include <CScrollPane.h>
#include “cDotsDoc.h”
#include “cDotsPane.h”
#include “cDotsTask.h”
#include “dotsTypes.h”

 /*** Class Constants ***/
#define kHalfDotSize 3
#define kFullDotSize (2*kHalfDotSize+1)
#define kHalfLineSize1
#define kFullLineSize(2*kHalfLineSize+1)
#define kLineOffset
 ((kFullDotSize - kFullLineSize) / 2)
#define kDotSpacing(4*kFullDotSize)
#define UNDOMoveIndex1

 /*** Globals ***/
extern Pattern *dgPlayerPats;

/******* C O N S T R U C T I O N *******/
/*** IDotsPane {OVERRIDE}
Initialize instance variables */
void cDotsPane::IDotsPane(CView *anEnclosure,      CBureaucrat *aSupervisor, 
short aWidth,
 short aHeight, short aHEncl, short aVEncl,
 SizingOption aHSizing, SizingOption aVSizing)
{
 IPanorama(anEnclosure, aSupervisor, aWidth,
 aHeight, aHEncl, aVEncl, aHSizing, aVSizing);
 /* Set scrolling scales */
 SetScales(kDotSpacing, kDotSpacing);
 SetWantsClicks(true); /*For line select*/
}

/******** D R A W I N G **********/
/*** Draw {OVERRIDE}
Draw the entire Dot Matrix View. Area input paramater is ignored. */
void cDotsPane::Draw(Rect *area)
{
 int  row, col;

 for (row = 0; row <= kMaxRow; row++)
 for (col = 0; col <= kMaxCol; col++)
 drawCorner(row, col);
}

/*** drawCorner
Draws the grid dots, grid lines and fills in player boxes */
void cDotsPane::drawCorner(int row,
 int col)
{
 Rect   r;
 tBoxStateboxState;
 
 if ((row < kMaxRow) && (col < kMaxCol)) {
 PenNormal();
 box2Rect(row, col, &r);  /* Get box */
 
 /* first draw the box if needed */
 boxState = ((cDotsDoc*)itsSupervisor)->
 getBoxState(row, col);
 if (boxState == kBoxPlayer1) {
 FillRect(&r, &dgPlayerPats[kPlayer1]);
 FrameRect(&r);
 }
 else if (boxState == kBoxPlayer2) {
 FillRect(&r, &dgPlayerPats[kPlayer2]);
 FrameRect(&r);
 }
 }
 dot2Rect(row, col, &r);  /* Get dot rectangle */
 
 PenNormal();
 PenSize(kFullLineSize, kFullLineSize);
 
 /* Draw line down from dot (if any) */
 if ((row < kMaxRow) &&
 (((cDotsDoc*)itsSupervisor)->
 getLineState(vLine, row, col))) {
 MoveTo(r.left+kLineOffset, r.top+kLineOffset);
 Line(0, kDotSpacing);
 }
 /* Draw line right from dot (if any) */
 if ((col < kMaxCol) &&
 (((cDotsDoc*)itsSupervisor)->
 getLineState(hLine, row, col))) {
 MoveTo(r.left+kLineOffset, r.top+kLineOffset);
 Line(kDotSpacing, 0);
 }
 FillOval(&r, black);/* Draw the dot */
}

/*** box2Rect
Returns the rectangle defining the box */
void cDotsPane::box2Rect(int row, int col,Rect *r)
{
 Point  center;
 
 dotCenter(row, col, &center);
 SetRect(r, 0, 0, kDotSpacing+1, kDotSpacing+1);
 OffsetRect(r, center.h, center.v);
 InsetRect(r, kHalfLineSize+4, kHalfLineSize+4);
}

/*** dot2Rect
Returns the rectangle defining a dot */
void cDotsPane::dot2Rect(int row, int col, Rect *r)
{
 Point  center;
 
 dotCenter(row, col, &center);
 SetRect(r, -kHalfDotSize, -kHalfDotSize,
 kHalfDotSize+1, kHalfDotSize+1);
 OffsetRect(r, center.h, center.v);
}

/*** dotCenter
Returns center coord of a given dot */
void cDotsPane::dotCenter(int row, int col,
 Point *center)
{
 SetPt(center, kDotSpacing*(col+1),
 kDotSpacing*(row+1));
}

/*** line2Pt
Converts coord of line to a pt on grid */
void cDotsPane::line2Pt(tLineDir direction,
 int row, int col, Point *pt)
{
 /* Set pt to center of dot */
 dotCenter(row, col, pt);
 /* Offset it to be on requested line */
 if (direction == hLine)
 pt->h = pt->h + kHalfDotSize + 1;
 else
 pt->v = pt->v + kHalfDotSize + 1;
}

/*** line2Rect
Convert coordinates of a line to a rectangle. Used for highlighting while 
tracking and for invalidating when line is chosen. */    
void cDotsPane::line2Rect(tLineDir direction,
 int row, int col, Rect *r)
{
 Point  center;
 
 /* Set pt to center of dot */
 dotCenter(row, col, &center);
 /* Offset it to be on requested line */
 if (direction == hLine)
 SetRect(r, 0, -kHalfLineSize,
 kDotSpacing+1, kHalfLineSize+1);
 else
 SetRect(r, -kHalfLineSize, 0,
 kHalfLineSize+1, kDotSpacing+1);
 OffsetRect(r, center.h, center.v);
}

/*** pt2Line
Convert point in grid line coordinates */
Boolean cDotsPane::pt2Line(Point pt,
 tLineDir *direction, int *row, int *col)
{
 Booleanok;
 int    modH;
 int    modV;
 
 ok = FALSE;
 
 /* 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 / kDotSpacing - 1;
 *col = pt.h / kDotSpacing - 1;

 /* Check if point selected is on grid*/
 ok = (*row >= 0) && (*row <= kMaxRow) &&
 (*col >= 0) && (*col <= kMaxCol);
 if (ok) {
 /* Consider taking pt.v % kDotSpacing. In
 ** order to be on a horizontal line, this
 ** must be in the range 0..2*kFullDotSize */
 modH = pt.h % kDotSpacing;
 modV = pt.v % kDotSpacing;
 
 /* Pick the closest line to point */
 if (modH < modV) {
 *direction = vLine;
 ok = (*row < kMaxRow) && 
 (modH <= 2*kFullDotSize);
 } else {
 *direction = hLine;
 ok = (*col < kMaxCol) && 
 (modV <= 2*kFullDotSize);
 }
 }
 if (!ok)
 *row = *col = -1;
 return(ok);
}

/*** invalLine
Invalidate line so it redraws on update */
void cDotsPane::invalLine(tLineDir
 direction, int row, int col)
{
 Rect r;
 
 /* Get line’s rectangle */
 line2Rect(direction, row, col, &r); 
 /* Set to dots matrix view’s port
 ** before invalidation otherwise may be
 ** drawing in a different port */
 Prepare();
 InvalRect(&r);
}

/*** invalBox
Invalidate box so it redraws on update */
void cDotsPane::invalBox(int row, int col)
{
 Rect r;

 box2Rect(row, col, &r);  /* Get box’s rectangle */      
 /* Set to dots matrix view’s port
 ** before invalidation otherwise may be
 ** drawing in a different port */
 Prepare();
 InvalRect(&r);
}

/*** M O U S E    T R A C K I N G *****/
/*** DoClick {OVERRIDE}
Respond to a mouse down in the dots pane
*/ 
void cDotsPane::DoClick(Point hitPt,
 short modifierKeys, long when)
{
 Rect   bounds;
 cDotsTask*theDotsTask;
 short  theHScale;
 short  theVScale;

 /* Set up a task both for tracking the
 ** mouse and for do/undo */
 theDotsTask = new(cDotsTask);
 theDotsTask->IDotsTask(UNDOMoveIndex,
 this, (cDotsDoc *)itsSupervisor);
 
 /* Set up a pin rect for autoScroll
 ** based on panorama bounds */
 GetScales(&theHScale, &theVScale);
 GetBounds(&bounds);
 bounds.top *= theVScale;
 bounds.left *= theHScale;
 bounds.bottom *= theVScale;
 bounds.right *= theHScale;
 
 TrackMouse(theDotsTask, hitPt, &bounds);

 /*Only save for undo if move is valid*/
 if (theDotsTask->validMove())
   /* Let doc save for undo */
 itsSupervisor->Notify(theDotsTask);
 else
 theDotsTask->Dispose();
}

/**********************************************
 cDotsTask.h
Class for handling dot matrix view mouse downs
**********************************************/
#define _H_cDotsTask

#include “cDotsDoc.h”
#include “cDotsPane.h”
#include “dotsTypes.h”

struct cDotsTask : CMouseTask
{
 /** Instance Variables **/
 cDotsDoc *fDotsDoc; /* Document of pane */
 cDotsPane*fDotsPane;/* Pane of mouse action */
 BooleanfPlayerMoved;/* Player making move */
 BooleanfValidMove;/* True if previously
 ** unselected line is selected */
 tLineDir fDirection;/* These 3 fields record */
 int    fRow;    /* the line chosen by    */
 int    fCol;    /* the user              */
 
 /** Initialization Methods **/
 void   IDotsTask(short aNameIndex, 
 cDotsPane *theDotsPane, cDotsDoc *theDotsDoc);
 
 /** Override Methods **/
 void   BeginTracking(Point *startPt);
 void   KeepTracking(Point *currPt, 
 Point *prevPt, Point *startPt);
 void   EndTracking(Point *currPt, Point *prevPt,
 Point *startPt);
 void   Do(void);
 void   Undo(void);
 void   Redo(void);

 /** New Methods **/
 BooleanvalidMove(void);
};

/***************************************
 cDotsTask.c
 SUPERCLASS = CMouseTask
Handles mouse clicks in the dots pane.
Also provides undo/redo capability.
****************************************/
#include “cDotsTask.h”
#include “dotsTypes.h”

/******** C O N S T R U C T I O N ********/

/*** IDotsTask
Initialize a DotsTask object
*/
void  cDotsTask::IDotsTask(short aNameIndex,
 cDotsPane*theDotsPane, cDotsDoc *theDotsDoc)
{
 inherited::IMouseTask(aNameIndex);

 /* Initialize instance variables */
 fDotsPane = theDotsPane;
 fDotsDoc = theDotsDoc;
 fPlayerMoved = fDotsDoc->getPlayerTurn();
 fValidMove = false;
 fDirection = hLine;
 fRow = -1;
 fCol = -1;
}

/*****  M O U S E   T R A C K I N G ********/
/*** BeginTracking {OVERRIDE}
Mouse tracking starting. Adjust starting pt. */
void  cDotsTask::BeginTracking(
 Point  *startPt)
{
 
 tLineDir direction;
 int    row;
 int    col;
 Rect   r;

 /* See if pt is on a line */ 
 if (fDotsPane->
 pt2Line(*startPt, &direction, &row, &col)) {
 /* Convert line coords to a pt on the grid */
 fDotsPane->line2Pt(direction, 
 row, col, startPt);
 /* If line unoccupied then fill it */
 if (!fDotsDoc->
 getLineState(direction, row, col)) {
 fDotsPane->line2Rect(direction, row, col,&r);
 FillRect(&r, black);
 }
 }
}

/*** KeepTracking {OVERRIDE}
Continuous mouse tracking while the mouse button is down. Draw only when 
the mouse moves or the panorama autoscrolls. */
void  cDotsTask::KeepTracking(
 Point  *currPt,
 Point  *prevPt,
 Point  *startPt)
{
 
 int    col;
 tLineDir direction;
 BooleaneraseIt;
 Rect   r;
 int    row;

 /* Only track if not autoScrolling */
 if (!fDotsPane->AutoScroll(*currPt)) {
 /* assume won’t erase line previously chosen
 ** by this mouse down */
 eraseIt = false;
 
 /* Check for current pt on a line */
 if (fDotsPane->
 pt2Line(*currPt, &direction, &row, &col)) {
 /* Convert line coords to a pt on grid */
 fDotsPane->
 line2Pt(direction, row, col, currPt);
 /* Check for new chosen line */
   if (!EqualPt(*currPt, *startPt)) {
 /* If new chosen line unoccupied 
 ** then fill it */
 if (!fDotsDoc->
 getLineState(direction, row, col)) {
 fDotsPane->line2Rect(direction,
 row, col, &r);
 FillRect(&r, black);
 }
 eraseIt = true; /* erase previous line */
 }
 } else /* Erase any previously chosen line */
 eraseIt = true;
 
 /* If no longer over a previously chosen line
 ** then erase it */
 if (eraseIt) {
 /* Check if start pt on a line */
 if (fDotsPane->
 pt2Line(*startPt, &direction, &row, &col)){
 /* Only erase if start line unoccupied */
 if (!fDotsDoc->
 getLineState(direction, row, col)) {
 /* erase the previouly chosen line */
 fDotsPane->
 line2Rect(direction, row, col, &r);
 FillRect(&r, white);
 /* redraw both dots */
 fDotsPane->dot2Rect(row, col, &r);
 FillOval(&r, black);
 if (direction == hLine)
 col++;
 else
 row++;
 fDotsPane->dot2Rect(row, col, &r);
 FillOval(&r, black);
 }
 }
 /* Reset startPt to new point */
 SetPt(startPt, currPt->h, currPt->v);
 }
 }
}

/*** EndTracking {OVERRIDE}
Mouse down tracking has ended because the user has released the mouse 
button. Its now time to complete the move and set up the undo. */
void  cDotsTask::EndTracking(Point *currPt, 
 Point   *prevPt, Point *startPt)
{
 tLineDir direction;
 int    row;
 int    col;

 /* The only valid move is selection of a previously unselected line 
so check if selected point is on line and the line is unoccupied */ 
 if ((fDotsPane->
 pt2Line(*prevPt, &direction, &row, &col)) &&
 (!fDotsDoc->
 getLineState(direction, row, col)))
 {
 fValidMove = true;
 fDirection = direction;
 fRow = row;
 fCol = col;
 
 Do();  /* Act on users choice */
 }
}

/*********** D O   /   U N D O ***********/
/*** Do  {OVERRIDE}
Perform a dots pane task, user chose a line */
void  cDotsTask::Do()
{
 fDotsDoc->setLineState(fDirection, fRow, fCol,
 fPlayerMoved, true);
}

/*** Undo {OVERRIDE}
Undo an action performed by a dots pane task */
void  cDotsTask::Undo()
{
 fDotsDoc->setLineState(fDirection, fRow, fCol,
 fPlayerMoved, false);
}

/*** Redo {OVERRIDE}
Redo an action performed by a dots pane task */
void  cDotsTask::Redo()
{
 fDotsDoc->setLineState(fDirection, fRow, fCol,
 fPlayerMoved, true);
}

/*********** A C C E S S ***********/
/*** validMove
Return value of fValidMove instance variable. The value will be true 
if previously unselected line was selected during this mouse task, else 
false. */
Boolean cDotsTask::validMove()
{
 return(fValidMove);
}

 

Community Search:
MacTech Search:

Software Updates via MacUpdate

Dropbox 193.4.5594 - Cloud backup and sy...
Dropbox is a file hosting service that provides cloud storage, file synchronization, personal cloud, and client software. It is a modern workspace that allows you to get to all of your files, manage... Read more
Google Chrome 122.0.6261.57 - Modern and...
Google Chrome is a Web browser by Google, created to be a modern platform for Web pages and applications. It utilizes very fast loading of Web pages and has a V8 engine, which is a custom built... Read more
Skype 8.113.0.210 - Voice-over-internet...
Skype is a telecommunications app that provides HD video calls, instant messaging, calling to any phone number or landline, and Skype for Business for productive cooperation on the projects. This... Read more
Tor Browser 13.0.10 - Anonymize Web brow...
Using Tor Browser you can protect yourself against tracking, surveillance, and censorship. Tor was originally designed, implemented, and deployed as a third-generation onion-routing project of the U.... Read more
Deeper 3.0.4 - Enable hidden features in...
Deeper is a personalization utility for macOS which allows you to enable and disable the hidden functions of the Finder, Dock, QuickTime, Safari, iTunes, login window, Spotlight, and many of Apple's... Read more
OnyX 4.5.5 - Maintenance and optimizatio...
OnyX is a multifunction utility that you can use to verify the startup disk and the structure of its system files, to run miscellaneous maintenance and cleaning tasks, to configure parameters in the... Read more
Hopper Disassembler 5.14.1 - Binary disa...
Hopper Disassembler is a binary disassembler, decompiler, and debugger for 32- and 64-bit executables. It will let you disassemble any binary you want, and provide you all the information about its... Read more

Latest Forum Discussions

See All

Zenless Zone Zero opens entries for its...
miHoYo, aka HoYoverse, has become such a big name in mobile gaming that it's hard to believe that arguably their flagship title, Genshin Impact, is only three and a half years old. Now, they continue the road to the next title in their world, with... | Read more »
Live, Playdate, Live! – The TouchArcade...
In this week’s episode of The TouchArcade Show we kick things off by talking about all the games I splurged on during the recent Playdate Catalog one-year anniversary sale, including the new Lucas Pope jam Mars After Midnight. We haven’t played any... | Read more »
TouchArcade Game of the Week: ‘Vroomies’
So here’s a thing: Vroomies from developer Alex Taber aka Unordered Games is the Game of the Week! Except… Vroomies came out an entire month ago. It wasn’t on my radar until this week, which is why I included it in our weekly new games round-up, but... | Read more »
SwitchArcade Round-Up: ‘MLB The Show 24’...
Hello gentle readers, and welcome to the SwitchArcade Round-Up for March 15th, 2024. We’re closing out the week with a bunch of new games, with Sony’s baseball franchise MLB The Show up to bat yet again. There are several other interesting games to... | Read more »
Steam Deck Weekly: WWE 2K24 and Summerho...
Welcome to this week’s edition of the Steam Deck Weekly. The busy season has begun with games we’ve been looking forward to playing including Dragon’s Dogma 2, Horizon Forbidden West Complete Edition, and also console exclusives like Rise of the... | Read more »
Steam Spring Sale 2024 – The 10 Best Ste...
The Steam Spring Sale 2024 began last night, and while it isn’t as big of a deal as say the Steam Winter Sale, you may as well take advantage of it to save money on some games you were planning to buy. I obviously recommend checking out your own... | Read more »
New ‘SaGa Emerald Beyond’ Gameplay Showc...
Last month, Square Enix posted a Let’s Play video featuring SaGa Localization Director Neil Broadley who showcased the worlds, companions, and more from the upcoming and highly-anticipated RPG SaGa Emerald Beyond. | Read more »
Choose Your Side in the Latest ‘Marvel S...
Last month, Marvel Snap (Free) held its very first “imbalance" event in honor of Valentine’s Day. For a limited time, certain well-known couples were given special boosts when conditions were right. It must have gone over well, because we’ve got a... | Read more »
Warframe welcomes the arrival of a new s...
As a Warframe player one of the best things about it launching on iOS, despite it being arguably the best way to play the game if you have a controller, is that I can now be paid to talk about it. To whit, we are gearing up to receive the first... | Read more »
Apple Arcade Weekly Round-Up: Updates an...
Following the new releases earlier in the month and April 2024’s games being revealed by Apple, this week has seen some notable game updates and events go live for Apple Arcade. What The Golf? has an April Fool’s Day celebration event going live “... | Read more »

Price Scanner via MacPrices.net

Apple Education is offering $100 discounts on...
If you’re a student, teacher, or staff member at any educational institution, you can use your .edu email address when ordering at Apple Education to take $100 off the price of a new M3 MacBook Air.... Read more
Apple Watch Ultra 2 with Blood Oxygen feature...
Best Buy is offering Apple Watch Ultra 2 models for $50 off MSRP on their online store this week. Sale prices available for online orders only, in-store prices may vary. Order online, and choose... Read more
New promo at Sams Club: Apple HomePods for $2...
Sams Club has Apple HomePods on sale for $259 through March 31, 2024. Their price is $40 off Apple’s MSRP, and both Space Gray and White colors are available. Sale price is for online orders only, in... Read more
Get Apple’s 2nd generation Apple Pencil for $...
Apple’s Pencil (2nd generation) works with the 12″ iPad Pro (3rd, 4th, 5th, and 6th generation), 11″ iPad Pro (1st, 2nd, 3rd, and 4th generation), iPad Air (4th and 5th generation), and iPad mini (... Read more
10th generation Apple iPads on sale for $100...
Best Buy has Apple’s 10th-generation WiFi iPads back on sale for $100 off MSRP on their online store, starting at only $349. With the discount, Best Buy’s prices are the lowest currently available... Read more
iPad Airs on sale again starting at $449 on B...
Best Buy has 10.9″ M1 WiFi iPad Airs on record-low sale prices again for $150 off Apple’s MSRP, starting at $449. Sale prices for online orders only, in-store price may vary. Order online, and choose... Read more
Best Buy is blowing out clearance 13-inch M1...
Best Buy is blowing out clearance Apple 13″ M1 MacBook Airs this weekend for only $649.99, or $350 off Apple’s original MSRP. Sale prices for online orders only, in-store prices may vary. Order... Read more
Low price alert! You can now get a 13-inch M1...
Walmart has, for the first time, begun offering new Apple MacBooks for sale on their online store, albeit clearance previous-generation models. They now have the 13″ M1 MacBook Air (8GB RAM, 256GB... Read more
Best Apple MacBook deal this weekend: Get the...
Apple has 13″ M2 MacBook Airs available for only $849 today in their Certified Refurbished store. These are the cheapest M2-powered MacBooks for sale at Apple. Apple’s one-year warranty is included,... Read more
New 15-inch M3 MacBook Air (Midnight) on sale...
Amazon has the new 15″ M3 MacBook Air (8GB RAM/256GB SSD/Midnight) in stock and on sale today for $1249.99 including free shipping. Their price is $50 off MSRP, and it’s the lowest price currently... Read more

Jobs Board

Early Preschool Teacher - Glenda Drive/ *Appl...
Early Preschool Teacher - Glenda Drive/ Apple ValleyTeacher Share by Email Share on LinkedIn Share on Twitter Read more
Senior Software Engineer - *Apple* Fundamen...
…center of Microsoft's efforts to empower our users to do more. The Apple Fundamentals team focused on defining and improving the end-to-end developer experience in Read more
Relationship Banker *Apple* Valley Main - W...
…Alcohol Policy to learn more. **Company:** WELLS FARGO BANK **Req Number:** R-350696 **Updated:** Mon Mar 11 00:00:00 UTC 2024 **Location:** APPLE VALLEY,California Read more
Medical Assistant - Surgical Oncology- *Apple...
Medical Assistant - Surgical Oncology- Apple Hill WellSpan Medical Group, York, PA | Nursing | Nursing Support | FTE: 1 | Regular | Tracking Code: 200555 Apply Now Read more
Early Preschool Teacher - Glenda Drive/ *Appl...
Early Preschool Teacher - Glenda Drive/ Apple ValleyTeacher Share by Email Share on LinkedIn Share on Twitter Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.