Life
 Volume Number: 3 Issue Number: 9 Column Tag: Macintosh II

# Life, Quickdraw & The Picker

By Steven Sheets, Contributing Editor, Hoffman Estates,. IL

Steve wins our Program of the Month award for his excellent treatment of the whole subject of color quickdraw as presented in this example program. Enjoy an extra \$50 on MacTutor, and thanks for sharing the technology!

## Color Life

Life is one of the oldest computer “Games”. Besides being an extremely interesting mathematical puzzle, even a black and white Life program can provide hypnotising graphic animation. It is almost appropriate that this column’s first Color Quickdraw example is this classic program. Using the full color of the Macintosh //, Life is more spell binding than ever! [Yeah, now if Apple would ever ship us some color monitors maybe we’ll see what this program can really do some day! -Ed]

This article will detail the development of a Color Quickdraw program, Color Life. It will explain how to check for Color Quickdraw, how create Color Grafports/Windows, how the Color RGB Model works, how Color Drawing works, how to use the Color Picker Package and how to create Pop Up Menus. First however it will explain what the game of Life is about....

## Game of Life

Life simulates the life and death of a group of cells from one generation to the next. Usually the goal is to find some stable life form (ie. one that will not die). Life was originally designed by Prof. John Conway at the University of Cambridge. It first appeared in Martin Gardner’s “Mathematical Games” column of Scientific American in October 1970. Scientific American and Byte Magazine are two of the main sources of good articles on Life.

The rules of Life are simple. The game is played on a two dimensional grid of a certain size. Each spot (or cell) on the grid can be alive or dead (empty). Also each cell is surrounded by it’s 8 neighboring cells, which can also be alive or dead. The number of living cells around the center cell becomes very important in calculating deaths and births each turn.

Every turn (usually called a generation), a living cell can die or live on to the next generation. Also a empty cell can have a birth (ie. cell becomes alive). If a live cell is surrounded by 2 or 3 cells this generation, it will survive till the next generation. If a live cell is surrounded by 1 or less cells this generation, it dies (from starvation). If the cell is surrounded by 4 or more cells this generation, it dies (from over crowding). Finally if a empty cell is surrounded by exactly 3 living neighbors, there is a birth there. Next generation a new cell exists at that spot.

From these simple rules, Life emerges.

Fig. 1 Color Quickdraw (cmd-shift-3 only works in black and white!)

## Color QuickDraw

The very first thing a Macintosh // Color program has to check for is what environment the program is running in. The program is not actually looking for a Macintosh //; it requires Color Quickdraw to run. While the Macintosh // is now the only computer with Color Quickdraw, it may not be in the future (upgraded Mac //, Mac SE with Color Quickdraw expansion card or even a Mac ///).

No matter what the computer, if Color Quickdraw exists on the machine, the two high bits of the low-memory global ROM85 (word at \$028E) will be cleared (set to zero). By examining this memory location, a program can discover if Color Quickdraw exists. The check for Color Quickdraw should be done after the normal Mac Initialization (InitGraf, InitFont, InitWindow, etc.), but before any Color Quickdraw commands are called (creating Color Grafports or color Pixel Patterns). If Color Quickdraw does not exist, the program should politely inform the user that he needs a Macintosh //. A system bomb is not a polite way of informing the user!

## Color Grafports & Color Windows

Once the program knows Color Quickdraw exists, it can create and work with Color Grafports. All the new Color Quickdraw commands must be done on a Color Grafport, not an old-style Grafport. All the older Quickdraw commands have been expanded so they can work with either a Grafport or a Color Grafport (also called cGrafport). The cGrafport data structure is the same size as the older Grafport structure. While many data fields are the same, some have been changed. The exact format is not important for this program since none of the fields are accessed directly. The new cGrafports are allocated in manners similar to the old Grafports (either a NewPtr call or a pointer to a holding Variable). However, instead of using the Quickdraw commands OpenPort, InitPort and ClosePort, the new Color Quickdraw commands OpenCPort, InitCPort and CloseCPort are used to open, init and close a Color Grafport.

In practise, the new Color Quickdraw Port commands are used as often as the older Quickdraw Port commands; that is almost never. The vast majority of the Macintosh programs draw on a Window (which keeps track of the Grafport itself). Generally the only time a Macintosh program directly manipulates a Grafport is when using an off screen bitmap.

The Window Manager has been expanded to include Color Windows. The changes in the Window Manager are similar to the changes in Color Quickdraw. There is a new Color Window Record of the same size as the old Window Record. All of the field are the same except the Port field now contains a cGrafPort instead of a Grafport. All existing Window Manager calls have been expanded to so that either a Window Pointer or a Color Window Pointer can be used with them. Where the NewWindow or GetNewWindow command was used to create a window, the NewCWindow and GetNewCWindow commands create a color window. The two new commands even have the same parameters and purpose (create a window from scratch or from a window resource) as the old commands. They just return a Color Window instead (complete with a correctly created Color Grafport).

## RGB Color

To understand exactly how Color Drawing is handled on the newly created Color Grafport/Windows, the RGB color model must be reviewed. Color Quickdraw uses a RGB color model as an conceptional model for all drawing. A color is defined as 3 non-signed integer values (0-65535). This defines the strength of the Red, Green and Blue portions of the color. A Black color would have the values 0,0,0, while white would have 65535, 65535, 65535. Blue would be 0, 65535, 0, while Purple would be 65535, 0, 65535. There are 1,777,216 distinct colors in this model. When something is conceptionally drawn, it must be in a color defined by this model.

In reality, the number of colors that can be drawn at one time, is dependent on the pixel depth of the color device. Most graphics devices are video card, but they can be almost anything (printer, off screen bit map, etc.). A color device that can allocate 4 bits of memory for each pixel (pixel depth of 4), can use 16 colors at one time (2 to the 4th power). A color device that has pixel depth of 8, can have 256 colors at one time (2 to the 8th power). Remember that usually the user can set what pixel depth the video card is using by using the Monitor portion of the Control Panel. A 8 pixel depth card may be only using 4, 2 or even 1 pixels at a specific moment. When color Quickdraw tries to draw in a specific RGB color, it uses the Color Manager to find the closest match on the graphics device. That is the color that is actually displayed.

A good example of this would be trying to display a light, medium and dark shade of red. A video card which has been set (and as enough memory) for a pixel depth of 8 would probably have a few shades of red. Even if the shades were not the exact RGB colors requested, the user would at least know that one shade was darker and one shade lighter than regular red when the colors were displayed on the screen. Suppose then the user set the video card pixel level to 4, thus only having 16 colors at one time. There most likely would only be one red color being used by the video card at one time. Chances are that the programs 3 red RGB shades would match to the cards single red color (it would be the closest match for any of them). The user would then not be able to tell the 3 colors apart on the screen.

The Color Quickdraw commands RGBForeColor and RGBBackColor set the RGB value of the Foreground and Background color. Remember that when called, these routine use the Color Manager to determine the best RGB color match of the graphics device that is being drawn on. This is the true RGB color that will be displayed on the screen.

All text is drawn in the Foreground Color. Copybits will display bitmaps in the Foreground and Background Colors . Set pixels will appear in the Foreground Color; unset pixels will appear in the Background Color. The Foreground and Background Colors may also effect the drawing of a new Color Quickdraw data structure, the Pix Pattern.

Fig. 2 Our menus for the game of life

## Color Pix Patterns

Most commonly used Quickdraw drawing commands are done with the Pen. The Move and Moveto commands use the current setting of the pen. The various Paint commands (PaintRect, PaintOval, PaintPoly, etc.) also use the pen’s settings. Old Quickdraw would draw the pen using the Pen Pattern; Color Quickdraw draws using the Pen’s Color Pattern or Pix Pattern. In old Quickdraw, similar to the Pen Pattern, there is Background Pattern. All Erase commands (EraseRect, EraseOval, ErasePoly, etc.) used the Background Pattern to draw with. With Color Quickdraw, similar to the Pen’s Pix Pattern, there is a Background Pix Pattern. What these two Pix Pattern displays depend on how the Pattern was created and thus what type of Pix Pattern it is.

There are 3 types of Pix Patterns. A Pix Pattern variable is a handled to a very elaborate data structure (including handles to other more complex data structures). For this discussion the actual values and formats of the data type is not important.

The simplest Pix Pattern is a monochrome Pix Pattern. Basically the old Quickdraw’s 8 by 8 Pattern is expanded to a Pix Pattern with no preset colors. Instead the monochrome Pix Pattern always uses the current RGB Foreground and Background color to draw in. Where bits (pixels) in the pattern are set, the pixel is drawn in the Foreground color, where they are cleared, it is drawn in the Background color. Changing the Foreground or Background color, does not change the Pix Pattern. Any future drawing with the Pix Pattern will be in the new colors.

The Foreground and Background Pix Patterns can be reset by using the old PenPat and BackPat command. On a Color Quickdraw Grafport/Window, these commands reset the Foreground and Background Pix Pattern to a newly created monochrome Pix Pattern (using the Pattern that is passed as the model). Thus by setting RGB Foreground Color to red, the RGB Background color to white and the PenPat to a brick pattern, the various Pen Drawing commands would draw with a red and white brick pattern. If RGB Foreground Color was suddenly set to Blue, the Pen Drawing commands would draw with a blue and white brick pattern. Thus the PenPat and BackPat commands are two methods to set the Pen Pix Pattern and the Background Pix Pattern.

The second type of Pix Pattern is the RGB Pix Pattern. This Pix Pattern contains an approximation of a certain RGB color. To make it, first a empty Pix Pattern must be created using the NewPixPat function. This Pix Pattern has no value at this point. Then the Pix Pattern and a specific RGB value is passed to MakeRGBPat. This reconstructs the Pix Pattern (it is a handle, remember) into a RGB Pix Pattern. If there is an RGB color used by the Graphics device that is close enough to the RGB color, that entire Pix Pattern is set to that color. If there is not an RGB color that close, Quickdraw tries to construct a Pix Pattern of that approximates that RGB pattern. It does this by mixing 2 RGB colors the graphic device allows in the Pix Pattern. This process is called dithering. In the above shades of Red example, imagine if the three red shades were used to create three Pix Patterns. On a 8 Pixel video card, the three Pix Patterns would probably display a solid red pattern, each red a different shade. When the video card was set to 4 Pixel mode, the medium shade would probably still remain a solid red pattern. However the lighter red Pix Pattern would display a pattern mixing red and white, while the darker red Pix Pattern would display a pattern mixing red and black. When running the example program, play with the Pixel depth of the video Card (using the Monitor portion of the Control Panel) to get an idea of how these Pix Patterns appear. Notice that every time the Pixel depth changes, the RGB Pix Pattern seems to be recalculated without ever calling the MakeRGBPat routine.

The last type of Pix Pattern is the Full Color Pix Pattern. This is the most powerful Pix Pattern. It can have almost any size and any color or combination of colors. This Pix Pattern can be created using the NewPixPat, and then having the data structure stuffed with the correct values. The other, easier method of creating this Pix Pattern is to read it in from a resource file using the GetPixPat function. The Pix Pattern read in can be of any type, but usually GetPixPat is used only for Full Color ones. This type of Pix Pattern (and example of the resource data structure) is not used in the example program.

Once the Pix Patterns are created, the new Color Quickdraw commands PenPixPat and BackPixPat can be used to set the Pen Pix Pattern and Background Pix Pattern. Remember Pix Patterns are handles. Calling the PenPixPat and BackPixPat command (or the PenPat and BackPat command) does not dispose of the old Pix Pattern if it was set using PenPixPat and BackPixPat. The call only resets the handle in use; the original data remains unchanged. Pix Pattern handles created by the program (using NewPixPattern or GetPixPattern) are safe and can be reused. This is not true of Pix Patterns created using PenPat or BackPat. Somehow Color Quickdraw keeps track of Pix Patterns created this way, and disposes of them when they are not needed (the program does not have to do this).

Fig. 3 The Life Game in Living(?) Color(Pictured is the seventh generation; the other generations are in color and cannot be captured)

## Other Color Commands

Besides using the older Quickdraw commands, Color Quickdraw has also been expanded to include six new color operations; FillCRect, FillCOval, FillCRoundRect, FillCArc, FillCRgn, FillCPoly. They are similar to the old Quickdraw Fill commands (FillRect, FillOval, FillRoundRect, FillArc, FillRgn, FillPoly). However instead of being passed a specific Pattern to fill the graphics area, they are passed a specific color Pix Pattern (of any of the 3 defined types). The graphic area is then filled with the specific color Pattern. These commands completely by pass the existing Foreground RGB Color, Background RGB Color, Foreground Pix Pattern and Background Pix Pattern.

## Color Picker Package

While the program has select a specific RGB color, a user may wish to pick a new color (ex. a paint program). Having the user pick the exact color he wants and provide a good interface to do this, is not a trivial task. Fortunately (and after a lot of good forethought by who ever developed Color Quickdraw) there exists a standardized way of letting the user to this; the Color Picker Package. The Color Picker Package, like all Packages, is not a set of Rom resident routines. The Package is stored as a system resource, to be read in when the program needs it. But like the Standard File Package (with it’s SFGetFile and SFPutFile dialogs), the Color Picker can be used easily by any program. The Picker will be the standard way of selecting RGB colors and should be quickly learned by all Macintosh users, just as SFGetFile and SFPutFile were. Also like SFGetFile and SFPutFile, if there are any upgrades to the Color Picker Package, a program using the Picker will automatically use the new version without having to be modified.

The main call in the Color Picker Package is the GetColor function. The routine is passed a point at which the dialog is drawn (the dialog is centered if the point is set to 0,0), a prompt string, an in going RGB color and a VAR to an out going RGB color. The routine will display and run the Picker dialog. The function will return False if the user presses Cancel. If he presses OK, the function will return True and the out going RGB variable will contain the new RGB color. If that RGB color is being used for something, remember to update everything (RGB variables, PixMaps, Color Windows, etc.).

One of the new user interfaces on the Macintosh is the Popup Menu. This menu is similar to the normal Menu Bar based menu, but can appear anywhere on a window. This feature is not exclusive to the Macintosh //. The latest System/Finder has been patched so that any Macintosh with the Enhanced Roms or later (Mac 512KE, MacPlus, Mac SE, Mac //) have the feature. While the PopUp Menu call (PopupMenuSelect) is not documented in the current release of Inside Macintosh V from APDA, the Rom call is implemented in the current release of MPW from APDA.

## The Code

After explaining the new calls, the actual program is fairly simple. It displays a Life game in color. Depending on how old the cell is (0 generations or dead, 1 generation, 2 generation, etc.), a different RGB color is used to display it. Cells 7 generations or older use the same color (usually they have stabilized). The RGB values are converted into RGB Pix Patterns. Try different color combos. Having a black background and lighter cells create a striking image. Play with the number of Pixels the video card is using and see the effect.

The program was written in Lightspeed Pascal, and MPW 2.0 and used the new Build feature of MPW to automatically compile itself. Of course the program can be compiled/linked by hand. The source includes the Pascal file, Resource file and Make file for the MPW version, and the main, globals unit and color stuff unit for the LS Pascal version. The LSP version is shown here and is virtually identical to the MPW version, except that it is broken into units to keep the main segment under 32K, and the resources are given in RMaker format. If you are typing it into MPW, note the include file names required in the comment at the top of the program. It should be easily ported to either Turbo Pascal or TML Pascal as well. Both the MPW and LSP versions are included on the source code disk for this issue if you don’t want to type it in. Be sure to use a version of your compiler which supports the new Inside Macintosh Volume 5 traps and equates. For LS Pascal, this is version 1.1, which was released last month at the Boston Expo.

The Life algorithm used here is a brute force method, there are plenty of more elegant (translate faster) ones around. This program was designed to be readable first, speedy second. If it was not for the faster speed of the Mac //, this program would almost be to slow.

Next Issue Palatte Manipulation and Animation!

```PROGRAM ColorLife;

{A small Mac // Color Application written by Steve Sheets. It plays the
original Life game. It also demonstrates some of the new commands of
Color Quickdraw. LS Pascal version.}

{Memtypes, Quickdraw, OSIntf, ToolIntf, PackIntf, PickerIntf;}

USES
ROM85, ColorQuickDraw, ColorMenuMgr, ColorWindowMgr, PickerIntf, myColorGlobals,
ColorStuff;

{Called if Color Quickdraw does not exists to explain why the program
can not be run.}

PROCEDURE DoSorry;
VAR
n : integer;
BEGIN
END;

{Standard Init calls to set Macintosh up (regardless if it is a Mac //
or not).}

PROCEDURE DoInit;
BEGIN
InitGraf(@thePort);
InitFonts;
FlushEvents(everyEvent, 0);
InitWindows;
TEInit;
InitDialogs(NIL);
InitCursor;
END;

{Does the setup for this program (only if there is Color Quickdraw).
Makes the Menus, clears the Dishes, Sets the Colors, make the Color Patterns
and sets them, Make the windows, and inits in other variable that need
to be set.}

PROCEDURE DoSetup;
VAR
tempS : str255;
tempR : rect;
n : INTEGER;
BEGIN

ClearDish(OldDish);
ClearDish(CurDish);

SetMyColors(0, -1, -1, -1);
SetMyColors(1, 0, -1, -1);
SetMyColors(2, 0, -1, 0);
SetMyColors(3, -1, -1, 0);
SetMyColors(4, -1, 0, 0);
SetMyColors(5, -1, 0, -1);
SetMyColors(6, 0, 0, -1);
SetMyColors(7, 0, 0, 0);
CWhite.red := -1;
CWhite.green := -1;
CWhite.blue := -1;

FOR n := 0 TO MaxAge DO
BEGIN
myPixMap[n] := NewPixPat;
MakeRGBPat(myPixMap[n], myColors[n])
END;

colorWindow := GetNewCWindow(colorID, NIL, POINTER(-1));
SetPortColors(colorWindow);

GetIndString(tempS, strID, 2);
CenterRect(HQuick, VQuick, tempR);
quickWindow := NewCWindow(NIL, tempR, tempS, FALSE, noGrowDocProc, POINTER(-1),
TRUE, 0);
SetPortColors(quickWindow);

GetIndString(tempS, strID, 1);
CenterRect(HLife, VLife, tempR);
lifeWindow := NewCWindow(NIL, tempR, tempS, TRUE, noGrowDocProc, POINTER(-1),
TRUE, 0);
SetPortColors(lifeWindow);

SetRect(bigRect, -32000, -32000, 32000, 32000);
doneFlag := FALSE;
END;

{Standard Main Progrom Loop, loops til done flag is marked.  Handles
drags, go Aways, keys, and menu the same way.  Updates and mouse downs
in windows, call the specific routine to handle those events.}

PROCEDURE MainLoop;
VAR
myWindow : WindowPtr;
myChar : CHAR;
myEvent : EventRecord;
myPort : grafptr;
BEGIN
REPEAT
IF GetNextEvent(everyEvent, myEvent) THEN
CASE myEvent.what OF
mouseDown :
CASE FindWindow(myEvent.where, myWindow) OF
inSysWindow :
SystemClick(myEvent, myWindow);
inDrag :
DragWindow(myWindow, myEvent.where, bigRect);
inGoAway :
IF TrackGoAway(myWindow, myEvent.where) THEN
HideWindow(myWindow);
inContent :
IF myWindow <> frontWindow THEN
SelectWindow(myWindow)
ELSE IF myWindow = lifeWindow THEN
DoLifeClick(myEvent.where)
ELSE IF myWindow = colorWindow THEN
DoColorClick(myEvent.where);
OTHERWISE
BEGIN
END;
END;

keyDown, autoKey :
BEGIN
IF BitAnd(myEvent.modifiers, cmdKey) <> 0 THEN
END;

updateEvt :
BEGIN
GetPort(myPort);
myWindow := WindowPtr(myEvent.message);
BeginUpdate(myWindow);
SetPort(myWindow);
IF myWindow = lifeWindow THEN
DoLifeDraw
ELSE IF myWindow = colorWindow THEN
DoColorDraw
ELSE IF myWindow = aboutWindow THEN
ELSE IF myWindow = quickWindow THEN
DoQuickDraw;
EndUpdate(WindowPtr(myEvent.message));
SetPort(myPort);
END;

OTHERWISE
END;
UNTIL doneFlag;
END;

{Main Program.  First calls DoInit to do the standard inits.  Then checks
if Color Quickdraw Exists.  If so, does Setup and Mainloop.  If not,
call DoSorry to explain.}
BEGIN
DoInit;
IF ColorQDExists THEN
BEGIN
DoSetup;
MainLoop;
END
ELSE
DoSorry;
END.

UNIT ColorStuff;

INTERFACE

USES
ROM85, ColorQuickDraw, ColorMenuMgr, ColorWindowMgr, PickerIntf, myColorGlobals;

PROCEDURE ClearDish (VAR J : Dish);
PROCEDURE SetMyColors (i, r, g, b : integer);
PROCEDURE SetPortColors (W : windowptr);
PROCEDURE CenterRect (h, v : INTEGER;
VAR R : rect);
PROCEDURE DoCommand (mResult : LONGINT);
PROCEDURE DoColorClick (Where : point);
PROCEDURE DoLifeClick (Where : point);
PROCEDURE DoLifeDraw;
PROCEDURE DoColorDraw;
PROCEDURE DoQuickDraw;
FUNCTION ColorQDExists : boolean;

IMPLEMENTATION

{Given Integer value (corrsponding to a string stored in STR# resource),
draw that string, centered on point h,v in c color.}

PROCEDURE DrawCenter (N, h, v, c : integer);
VAR
S : str255;
BEGIN
RGBForeColor(myColors[c]);
GetIndString(S, strID, N);
MoveTo(h - (StringWidth(S) DIV 2), v);
DrawString(S);
END;

{Given a Dish, Clear all the cells (set to 0).}

PROCEDURE ClearDish; {(var J : Dish);}
VAR
h, v : integer;
BEGIN
FOR h := 0 TO HEdgeMax DO
FOR v := 0 TO VEdgeMax DO
J[h, v] := 0;
END;

{Given a Color Window Port, set the Pen and Background color Patterns
(stored in myPicMap in position 0 & 1, respectively).}

PROCEDURE SetPortColors;{(W : windowptr);}
BEGIN
SetPort(W);
BackPixPat(myPixMap[0]);
PenPixPat(myPixMap[1]);
END;

{Given a Color Window Port, calls SetPortColors to set Pen and Background
color Patterns and forces on update by invalidating a big Rectangle.}

PROCEDURE ForceUpdate (W : windowptr);
BEGIN
SetPortColors(W);
InvalRect(bigRect);
END;

{Given a Color Window Port, saves the current Port, calls UpdateOne and
then restore the current port.}

PROCEDURE UpdateOne (W : windowptr);
VAR
tempPort : grafptr;
BEGIN
GetPort(tempPort);
ForceUpdate(W);
SetPort(tempPort);
END;

{Saves the current Port, calls ForceUpdate for all the windows to update
them all and then restore the current port.}

PROCEDURE UpdateAll;
VAR
tempPort : grafptr;
BEGIN
GetPort(tempPort);
ForceUpdate(colorWindow);
ForceUpdate(quickWindow);
ForceUpdate(lifeWindow);
SetPort(tempPort);
END;

{Given a Color Window Port, if it is not in front (or invisible), Shows
it and Select it (make it the top window).}

PROCEDURE ShowIt (W : windowptr);
BEGIN
IF FrontWindow <> W THEN
BEGIN
ShowWindow(W);
SelectWindow(W);
END;
END;

{Loads a new Dish and Dish Colors from disk.  If successful, remakes
the Dishes Color Patterns and updates all the windows (using UpdateAll).}

VAR
tempType : SFTypeList;
tempP : point;
Ref, n : INTEGER;
tempE : OSErr;
tempLong : longint;
BEGIN
tempP.v := 40;
tempP.h := 40;
tempType[0] := FileType;
SFGetFile(tempP, ‘’, NIL, 1, tempType, NIL, tempReply);
Ref := 0;
BEGIN
IF tempE = noErr THEN
BEGIN
tempLong := SIZEOF(Dish);
IF tempE = noErr THEN
BEGIN
tempLong := SIZEOF(DishColor);
IF tempE = noErr THEN
BEGIN
tempE := FSClose(Ref);
Ref := 0;
FOR n := 0 TO MaxAge DO
MakeRGBPat(myPixMap[n], myColors[n]);
UpdateAll;
END;
END;
END;
IF Ref <> 0 THEN
tempE := FSClose(Ref);
END;
END;

{Save the current Dish and Dish Colors to disk.}

PROCEDURE DoSave;
VAR
tempP : point;
Ref : INTEGER;
tempE : OSErr;
tempLong : longint;
BEGIN
tempP.v := 40;
tempP.h := 40;
BEGIN
IF (tempE = noErr) OR (tempE = fnfErr) THEN
BEGIN
IF tempE = noErr THEN
BEGIN
IF tempE = noErr THEN
BEGIN
tempLong := SIZEOF(Dish);
tempE := FSWrite(Ref, tempLong, @CurDish);
IF tempE = noErr THEN
BEGIN
tempLong := SIZEOF(DishColor);
tempE := FSWrite(Ref, tempLong, @myColors);
IF tempE = noErr THEN
BEGIN
tempE := FSClose(Ref);
Ref := 0;
END;
END;
END;
END;
END;
IF Ref <> 0 THEN
tempE := FSClose(Ref);
END;
END;

{Given a cells horiztonal and vertical position (in cell position, not
screen position), return the screen position Rectangle that contains
that cell.}

PROCEDURE MakeRect (h, v : integer;
VAR R : rect);
VAR
n : integer;
BEGIN
n := (h * Big);
R.right := n;
R.left := n - big;
n := (v * Big);
R.bottom := n;
R.top := n - big;
END;

{Given a cells horiztonal and vertical position (in cell position, not
screen position) and the correct screen rectangle holding that cell,}
draws the Cell in the correct pattern.  It will only erase the cell if
this call was not done by an update event (update events erase everything).}

PROCEDURE DrawRect (h, v : INTEGER;
R : rect;
UpEvent : BOOLEAN);
BEGIN
IF CurDish[h, v] = 1 THEN
PaintRect(R)
ELSE IF (CurDish[h, v] > 1) AND (CurDish[h, v] <= MaxAge) THEN
FillCRect(R, myPixMap[CurDish[h, v]])
ELSE IF NOT UpEvent THEN
EraseRect(R);
END;

{Given a point in the window where a mouse down occured (global coordinates),
flips the cell at that point (erasing one that there} or making on in
an empty space).  Then loops for every cell the} mouse pass over (drawing
or erasing cells) until the button is released.}

PROCEDURE DoLifeClick;{(Where : point);}
VAR
h, v : integer;
tempPort : grafptr;
tempR : rect;
empty : BOOLEAN;

PROCEDURE CalcWhere;
BEGIN
h := (Where.h DIV big) + 1;
v := (Where.v DIV big) + 1;
END;

PROCEDURE DoDraw;
BEGIN
MakeRect(h, v, tempR);
DrawRect(h, v, tempR, FALSE);
END;

PROCEDURE DoClick;
BEGIN
IF (h > 0) AND (h <= HEdge) AND (v > 0) AND (v <= VEdge) THEN
BEGIN
IF Empty THEN
BEGIN
IF CurDish[h, v] = 0 THEN
BEGIN
CurDish[h, v] := 1;
DoDraw;
END;
END
ELSE
BEGIN
IF CurDish[h, v] <> 0 THEN
BEGIN
CurDish[h, v] := 0;
DoDraw;
END;
END;
END;
END;

BEGIN
GetPort(tempPort);
SetPort(lifeWindow);
GlobalToLocal(Where);
CalcWhere;
empty := (CurDish[h, v] = 0);
DoClick;
REPEAT
GetMouse(where);
CalcWhere;
DoClick;
UNTIL NOT Button;

SetPort(tempPort);
END;

{Given a mouse down point in the Color Window, display a pop up menu
listing the RGB colors.  If a color is selected, call the Color Picker
(GetColor) to select a new color.  If a color is picked, recalculate
the Color Pattern (for that color) and Update all the windows (using
UpdateAll).}

PROCEDURE DoColorClick;{(Where : point);}
VAR
tempLong : LONGINT;
M, I : INTEGER;
tempP : point;
tempS : str255;
ColorIt : RGBColor;
BEGIN
M := HiWord(tempLong);
I := LoWord(tempLong) - 1;
IF (M = popupID) AND (0 <= I) AND (I <= MaxAge) THEN
BEGIN
tempP.v := 0;
tempP.h := 0;
GetIndString(tempS, strID, 3);
ColorIt := myColors[I];
IF GetColor(tempP, tempS, ColorIt, ColorIt) THEN
BEGIN
myColors[i] := ColorIt;
MakeRGBPat(myPixMap[i], myColors[i]);
UpdateAll;
END;
END;
END;

{Draws all the cells in the Life window.}

PROCEDURE DoLifeDraw;
VAR
h, v : integer;
tempR : rect;
BEGIN
EraseRect(bigRect);
FOR h := 1 TO HEdge DO
FOR v := 1 TO VEdge DO
BEGIN
MakeRect(h, v, tempR);
DrawRect(h, v, tempR, TRUE);
END;
END;

{Draws all the colors in the Color window.}

PROCEDURE DoColorDraw;
VAR
tempR : rect;
n, start, finish : INTEGER;
BEGIN
SetRect(tempR, 0, 0, colorSize, colorSize);
EraseRect(tempR);
FOR n := 1 TO MaxAge DO
BEGIN
start := (360 * (n - 1)) DIV MaxAge;
finish := (360 * n) DIV MaxAge;
IF n = 1 THEN
PaintArc(tempR, start, finish - start)
ELSE
FillCArc(tempR, start, finish - start, myPixMap[n]);
END;
END;

type of desgin).}

VAR
h, v, n : INTEGER;
BEGIN
EraseRect(bigRect);

TextSize(24);
DrawCenter(4, HAbout DIV 2, 120, 7);
DrawCenter(5, HAbout DIV 2, 150, 6);
DrawCenter(6, HAbout DIV 2, 180, 5);

FOR n := 1 TO StepSize - 1 DO
BEGIN
h := n * HStep;
v := n * VStep;

PenPixPat(myPixMap[4]);
MoveTo(0, v);

PenPixPat(myPixMap[3]);
MoveTo(h, 0);

PenPixPat(myPixMap[2]);

PenPixPat(myPixMap[1]);
LineTo(0, v);
END;
END;

{Draws the Quickdraw Window that displays the major Color Quickdraw commands
(similar to the Lisa version).}

PROCEDURE DoQuickDraw;
VAR
tempRect : Rect;
tempPat : Pattern;
tempPoly : PolyHandle;
tempRgn : RgnHandle;
BEGIN
{Erase Background}
EraseRect(bigRect);
{Draws Squares}
PenPixPat(myPixMap[1]);
MoveTo(0, 150);
LineTo(450, 150);
MoveTo(150, 0);
LineTo(150, 300);
MoveTo(300, 0);
LineTo(300, 300);
{Draws Titles}
DrawCenter(7, 75, 20, 2);
DrawCenter(8, 225, 20, 3);
DrawCenter(9, 375, 20, 4);
DrawCenter(10, 75, 170, 5);
DrawCenter(11, 225, 170, 6);
DrawCenter(12, 375, 170, 7);
{Draw Rectangles}
SetRect(tempRect, 15, 25, 95, 105);
RGBForeColor(myColors[1]);
RGBBackColor(CWhite);
PenPat(Black);
FrameRect(tempRect);

OffSetRect(tempRect, QOff, QOff);
RGBForeColor(myColors[2]);
GetIndPattern(tempPat, 0, 31);
PenPat(tempPat);
PaintRect(tempRect);

OffSetRect(tempRect, QOff, QOff);
PenPixPat(myPixMap[3]);
PaintRect(tempRect);

OffSetRect(tempRect, QOff, QOff);
RGBForeColor(myColors[4]);
RGBBackColor(myColors[5]);
GetIndPattern(tempPat, 0, 32);
PenPat(tempPat);
PaintRect(tempRect);

OffSetRect(tempRect, QOff, QOff);
FillCRect(tempRect, myPixMap[6]);
{Draw Ovals}
SetRect(tempRect, 165, 25, 245, 105);
RGBForeColor(myColors[7]);
RGBBackColor(CWhite);
PenPat(Black);
FrameOval(tempRect);

OffSetRect(tempRect, QOff, QOff);
RGBForeColor(myColors[1]);
GetIndPattern(tempPat, 0, 33);
PenPat(tempPat);
PaintOval(tempRect);

OffSetRect(tempRect, QOff, QOff);
PenPixPat(myPixMap[2]);
PaintOval(tempRect);

OffSetRect(tempRect, QOff, QOff);
RGBForeColor(myColors[3]);
RGBBackColor(myColors[4]);
GetIndPattern(tempPat, 0, 34);
PenPat(tempPat);
PaintOval(tempRect);

OffSetRect(tempRect, QOff, QOff);
FillCOval(tempRect, myPixMap[5]);

{Draw Round Rectangles}
SetRect(tempRect, 315, 25, 395, 105);
RGBForeColor(myColors[6]);
RGBBackColor(CWhite);
PenPat(Black);
FrameRoundRect(tempRect, 20, 20);

OffSetRect(tempRect, QOff, QOff);
RGBForeColor(myColors[7]);
GetIndPattern(tempPat, 0, 35);
PenPat(tempPat);
PaintRoundRect(tempRect, 20, 20);

OffSetRect(tempRect, QOff, QOff);
PenPixPat(myPixMap[1]);
PaintRoundRect(tempRect, 20, 20);

OffSetRect(tempRect, QOff, QOff);
RGBForeColor(myColors[2]);
RGBBackColor(myColors[3]);
GetIndPattern(tempPat, 0, 36);
PenPat(tempPat);
PaintRoundRect(tempRect, 20, 20);

OffSetRect(tempRect, QOff, QOff);
FillCRoundRect(tempRect, 20, 20, myPixMap[4]);

{Draw Polygons}
tempPoly := OpenPoly;
SetRect(tempRect, 15, 175, 95, 255);
MoveTo(95, 175);
LineTo(65, 215);
LineTo(95, 255);
LineTo(15, 255);
LineTo(15, 215);
LineTo(55, 175);
LineTo(95, 175);
ClosePoly;

RGBForeColor(myColors[5]);
RGBBackColor(CWhite);
PenPat(Black);
FramePoly(tempPoly);

OffSetPoly(tempPoly, QOff, QOff);
RGBForeColor(myColors[6]);
GetIndPattern(tempPat, 0, 37);
PenPat(tempPat);
PaintPoly(tempPoly);

OffSetPoly(tempPoly, QOff, QOff);
PenPixPat(myPixMap[7]);
PaintPoly(tempPoly);

OffSetPoly(tempPoly, QOff, QOff);
RGBForeColor(myColors[1]);
RGBBackColor(myColors[2]);
GetIndPattern(tempPat, 0, 38);
PenPat(tempPat);
PaintPoly(tempPoly);

OffSetPoly(tempPoly, QOff, QOff);
FillCPoly(tempPoly, myPixMap[3]);

KillPoly(tempPoly);

{Draw Arcs}
SetRect(tempRect, 165, 175, 265, 275);
RGBForeColor(myColors[4]);
RGBBackColor(CWhite);
PenPat(Black);
FrameArc(tempRect, 198, 72);

RGBForeColor(myColors[5]);
GetIndPattern(tempPat, 0, 15);
PenPat(tempPat);
PaintArc(tempRect, 126, 72);

PenPixPat(myPixMap[6]);
PaintArc(tempRect, 270, 72);

RGBForeColor(myColors[7]);
RGBBackColor(myColors[1]);
GetIndPattern(tempPat, 0, 16);
PenPat(tempPat);
PaintArc(tempRect, 342, 72);

OffSetRect(tempRect, 20, 0);
FillCArc(tempRect, 54, 72, myPixMap[2]);

{Draw Regions}
tempRgn := NewRgn;
OpenRgn;
SetRect(tempRect, 315, 175, 395, 255);
FrameRoundRect(tempRect, 20, 20);
InsetRect(tempRect, 10, 10);
FrameOval(tempRect);
CloseRgn(tempRgn);

RGBForeColor(myColors[3]);
RGBBackColor(CWhite);
PenPat(Black);
FrameRgn(tempRgn);

OffSetRgn(tempRgn, QOff, QOff);
RGBForeColor(myColors[4]);
GetIndPattern(tempPat, 0, 17);
PenPat(tempPat);
PaintRgn(tempRgn);

OffSetRgn(tempRgn, QOff, QOff);
PenPixPat(myPixMap[5]);
PaintRgn(tempRgn);

OffSetRgn(tempRgn, QOff, QOff);
RGBForeColor(myColors[6]);
RGBBackColor(myColors[7]);
GetIndPattern(tempPat, 0, 18);
PenPat(tempPat);
PaintRgn(tempRgn);

OffSetRgn(tempRgn, QOff, QOff);
FillCRgn(tempRgn, myPixMap[1]);

DisposeRgn(tempRgn);
END;

{Steps through a generation of growth/death of the Dish.  Stores the
old Graf port, then sets the Life window as the new one.  Copies the
current dish into the old dish, then checks each cells one by one. Calculates
the number of cells around it, and decides deaths, lifes and births.
If the new value does not match the old value} (ie. change), redraw
that window.}

PROCEDURE DoStep;
VAR
n, h, v, c : INTEGER;
tempPort : grafptr;
tempR : rect;
BEGIN
GetPort(tempPort);
SetPort(lifeWindow);
BlockMove(@CurDish, @OldDish, SIZEOF(Dish));

FOR h := 1 TO HEdge DO
FOR v := 1 TO VEdge DO
BEGIN
n := 0;
IF OldDish[h - 1, v - 1] <> 0 THEN
n := n + 1;
IF OldDish[h, v - 1] <> 0 THEN
n := n + 1;
IF OldDish[h + 1, v - 1] <> 0 THEN
n := n + 1;
IF OldDish[h - 1, v] <> 0 THEN
n := n + 1;
IF OldDish[h + 1, v] <> 0 THEN
n := n + 1;
IF OldDish[h - 1, v + 1] <> 0 THEN
n := n + 1;
IF OldDish[h, v + 1] <> 0 THEN
n := n + 1;
IF OldDish[h + 1, v + 1] <> 0 THEN
n := n + 1;
c := OldDish[h, v];
IF (c = 0) AND (n = 3) THEN
CurDish[h, v] := 1
ELSE IF (c <> 0) AND ((n = 3) OR (n = 2)) THEN
BEGIN
IF c < MaxAge THEN
CurDish[h, v] := c + 1;
END
ELSE
CurDish[h, v] := 0;

IF CurDish[h, v] <> c THEN
BEGIN
MakeRect(h, v, tempR);
DrawRect(h, v, tempR, FALSE);
END;
END;

SetPort(tempPort);
END;

{Standard Menu Command command procedures.  Takes card of Desk Accessories,
3 other windows).}
PROCEDURE DoCommand;{(mResult : LONGINT);}
VAR
theItem : INTEGER;
name : Str255;
temp : INTEGER;
tempBool : BOOLEAN;
tempEvent : EventRecord;
BEGIN
theItem := LoWord(mResult);

appleID :
IF (theItem = 1) THEN
ELSE
BEGIN
temp := OpenDeskAcc(name);
END;
fileID :
CASE theItem OF
1 :
BEGIN
ClearDish(CurDish);
UpdateOne(lifeWindow);
END;
2 :
3 :
DoSave;
5 :
doneFlag := TRUE;
OTHERWISE
END;
editID :
tempBool := SystemEdit(theItem - 1);
actionID :
CASE theItem OF
1 :
DoStep;
2 :
FOR temp := 1 TO 10 DO
DoStep;
3 :
DoStep;
OTHERWISE
END;
showID :
CASE theItem OF
1 :
ShowIt(lifeWindow);
2 :
ShowIt(colorWindow);
3 :
ShowIt(quickWindow);
OTHERWISE
END;
OTHERWISE
END;
END;

{Checks if Color Quickdraw Exists on this machine.}

FUNCTION ColorQDExists; { : boolean;}
CONST
ROM85Loc = \$28E;
VAR
WordPtr : ^Integer;
ROM85Value : integer;
BEGIN
WordPtr := pointer(ROM85Loc);
ROM85Value := WordPtr^;
ColorQDExists := (BitAnd(ROM85Value, TwoHighMask) = 0);
END;

{Given a h and v size, calculates a Rectangle that is centered on the
screen.}

PROCEDURE CenterRect; {(h, v : INTEGER; var R : rect);}
VAR
Hoff, Voff : INTEGER;
BEGIN
Hoff := (ScreenBits.bounds.right - h) DIV 2;
VOff := (ScreenBits.bounds.bottom - v) DIV 2;
IF VOff < 35 THEN
VOff := 35;
SetRect(R, Hoff, VOff, Hoff + h, Voff + v);
END;

{Given RGB values (3 integers) and pos (which , places the values in
the Dish Color in the correct position.}
PROCEDURE SetMyColors;{(i, r, g, b : integer);}
BEGIN
myColors[i].red := r;
myColors[i].green := g;
myColors[i].blue := b;
END;
END.

UNIT myColorGlobals;

INTERFACE

USES
ColorQuickDraw;
{Constants: various Resource IDs, File Type and Creator Signitures, Maximum
recorded Age of Cell, Horizontal/Vertical size of Dish (containing cells),
Size of cells, Size of other windows and other size information.}
CONST
appleID = 256;
fileID = 257;
editID = 258;
actionID = 259;
showID = 260;
popupID = 261;

strID = 256;
sorryID = 256;
colorID = 257;

FileType = ‘life’;
CreatorType = ‘life’;

MaxAge = 7;
VEdge = 50;
VEdgeMax = 51;
HEdge = 70;
HEdgeMax = 71;
Big = 8;
VLife = 400; {Big * VEdge}
HLife = 560; {Big * HEdge}

VQuick = 300;
HQuick = 450;
QOff = 10;

colorSize = 300;
StepSize = 30;
HStep = 20;
VStep = 10;
HAbout = 600; {StepSize * HStep}
VAbout = 300; {StepSize * VStep}

{Types: Age range, Dish info (containing all cells & age), Dish Colors
(containing RGB colors), Dish Color Patterns (RGB info converted to a
Color Pattern).}
TYPE
Age = 0..MaxAge;
Dish = PACKED ARRAY[0..HEdgeMax, 0..VEdgeMax] OF Age;
DishColor = PACKED ARRAY[0..MaxAge] OF RGBColor;
DishPixMap = PACKED ARRAY[0..MaxAge] OF PixPatHandle;

large Rect (used for drag & updates), Done Flag and Current & 0ld Dishes.}
VAR
lifeWindow, aboutWindow, colorWindow, quickWindow : WindowPtr;
myColors : DishColor;
myPixMap : DishPixMap;
bigRect : Rect;
doneFlag : BOOLEAN;
OldDish, CurDish : Dish;
CWhite : RGBColor;

IMPLEMENTATION

END.

* Life.R
* Resources for Color Life
* © 1987 by Steve Sheets for MacTutor
*
*
Life.RSRC
????????

Type LIFE = STR
,0
Color Life Application - Version 1.0 by Steve Sheets

Type FREF
,128 (0)
APPL 0
,129 (0)
LIFE 1

Type BNDL
,128 (0)
LIFE 0
ICN#
0 128 1 129
FREF
0 128 1 129

TYPE STR#
,256
12
Life
Quickdraw
Select Color:
Color Life by Steve Sheets
Demo of Color Quickdraw
on the Mac //
Rectangles
Ovals
RoundRectangles:
Arcs
Polygons
Regions

TYPE WIND
,256
40 20 340 620
Invisible Goaway
4
0

TYPE WIND
,257
Colors
40 170 340 470
Invisible Goaway
4
0

*   ,ID (attributes)
*   menu title (an Apple symbol is \ 14 in hex)
*   menu items, ( means it’s initially disabled.
*   (-  means a disabled line of dashes.
*   A trailing /Q, etc. means a command-key.
,256
\14
(-

,257
File
New
Save
(-
Quit/Q

,258
Edit
Undo/Z
(-
Cut/X
Copy/C
Paste/V
Clear

,259
Action
Step/S
Ten Steps/T
Loop til Button/L

,260
Show
Life
Colors
Quickdraw

,261
x
Select Background Color [Age 0]
Select Foreground Color [Age 1]
Select Age 2 Color
Select Age 3 Color
Select Age 4 Color
Select Age 5 Color
Select Age 6 Color
Select Age 7 [&older] Color

* ------ Dialogs --------
* Program Messages Dialog box...
type DLOG
,257
Program Messages
100 100 200 400
Visible NoGoAway
1
0
257

type DITL
,257
3
BtnItem Enabled
65 230 95 285
OK

StatText Disabled
15 60 85 222
^0\0D^1\0D^2\0D^3

IconItem Disabled
10 10 42 42
1

type ALRT
,256
40 106 140 406
256
4444

* A Dialog or Alert Item List
*   ,ID (attributes)
*   number of items in list
*   type of item
*   top left bottom right
*   message
Type DITL
,256
2

button
60 120 80 180
OK

staticText Disabled
20 10 40 290
Sorry, this program only runs on a Mac //

* misc resources

* An icon list for the icon
*   ,ID (attributes)
*   Data is Hex data (.H)
*   the icon data: 32 lines of 8 hex chars each
*   the icon mask: 32 lines of 8 hex chars each
Type ICN# = GNRL
,128 (0)
.H
0000 0000 EE00 0EEE EE00 0EEE EE00 0EEE
0000 0000 E000 000E E000 000E E000 000E
0000 0000 000E 00E0 000E 00E0 000E 00E0
0000 0000 00EE 0000 00EE 0000 00EE 0000
0000 0000 0000 0000 0000 0000 0000 0000
0000 0000 0EE0 0000 0EE0 0000 0EE0 0000
0000 0000 E00E 00EE E00E 00EE E00E 00EE
0000 0000 0EE0 00EE 0EE0 00EE 0EE0 00EE
* [2]
0000 0000 EEEE EEEE EEEE EEEE EEEE EEEE
0000 0000 EEEE EEEE EEEE EEEE EEEE EEEE
0000 0000 EEEE EEEE EEEE EEEE EEEE EEEE
0000 0000 EEEE EEEE EEEE EEEE EEEE EEEE
0000 0000 EEEE EEEE EEEE EEEE EEEE EEEE
0000 0000 EEEE EEEE EEEE EEEE EEEE EEEE
0000 0000 EEEE EEEE EEEE EEEE EEEE EEEE
0000 0000 EEEE EEEE EEEE EEEE EEEE EEEE

* An icon list for the icon
*   ,ID (attributes)
*   Data is Hex data (.H)
*   the icon data: 32 lines of 8 hex chars each
*   the icon mask: 32 lines of 8 hex chars each
Type ICN# = GNRL
,129 (0)
.H
1FFF FC00 1000 0600 1000 0500 1000 0480
1000 0440 1360 3420 1360 37F0 1000 0010
1300 00D0 1300 00D0 1000 0010 1001 8610
1001 8610 1000 0010 100D 8010 100D 8010
1000 0010 1000 0010 1000 0010 1000 0010
106C 0010 106C 0010 1000 0010 1301 86D0
1301 86D0 1000 0010 106C 06D0 106C 06D0
1000 0010 1000 0010 1000 0010 1FFF FFF0
* [2]
1FFF FC00 1FFF FE00 1FFF FF00 1FFF FF80
1FFF FFC0 1FFF FFE0 1FFF FFF0 1FFF FFF0
1FFF FFF0 1FFF FFF0 1FFF FFF0 1FFF FFF0
1FFF FFF0 1FFF FFF0 1FFF FFF0 1FFF FFF0
1FFF FFF0 1FFF FFF0 1FFF FFF0 1FFF FFF0
1FFF FFF0 1FFF FFF0 1FFF FFF0 1FFF FFF0
1FFF FFF0 1FFF FFF0 1FFF FFF0 1FFF FFF0
1FFF FFF0 1FFF FFF0 1FFF FFF0 1FFF FFF0
```

Community Search:
MacTech Search:

## Latest Forum Discussions

Brawl Stars veteran-backed Octopo Studio...
The 2024 PlayX4 B2B is kicking off tomorrow in Ilsan, lasting until the 26th in KINTEX Hall 1, and Octopo Studio has announced that they will be making an appearance. Shooting RPG Bounty Pang will be on show as the developers seek publishing... | Read more »
The Astral Express sets off to bring Hon...
After vanquishing yet another threat and adding a fine hat to their wardrobe, you’d think the crew of the Astral Express would be able to kick back after their adventures in Penacony. Clearly not though, as HoYo has announced the next leg of the... | Read more »
Dragons rein supreme in Pokemon Unite as...
If you were looking for the strongest type in Pokemon and asked the fanbase, I would wager most people's thoughts would turn to Dragon. They hit hard, are often powerful, and get used by some of the toughest trainers. Pokemon Unite is honouring... | Read more »
Players can take a peek into the design...
It doesn’t matter how much effort developers put into their classes, or how many special little mechanics there are; if there is one that wields two blades, I’m ignoring everything else. Diablo Immortal recently announced such a class in the shape... | Read more »
Android users have a new option in the c...
When you are in the thick of a firefight or trying to pull off a mid-combat parkour flip through a squad of foes, sometimes touchscreen control just won’t do it for you. For those intense sessions, you could benefit from a good mobile controller,... | Read more »
Jagex releases the first of three origin...
At this point, I am sure everyone has heard of Runescape, and or Runescape Classic. It has been going strong for 23 years, with constant content and story coming out. Luckily for fans of the game, or fantasy in general, Jagex has announced an... | Read more »
Watcher of Realms unveils new story and...
Watcher of Realms players are in for quite the feast this month, as Moonton release two powerful new heroes, including one that will burst down even the most mighty of foes. Recruit your new friends, and then burn through the Main Quest expansion... | Read more »
Reverse: 1999 continues its trip down un...
The field trip to Australia continues in Reverse: 1999 as Phase 2 of Revival! The Uluru Games kicks off. You will be able to collect new characters, engage with new events, get hordes of free gifts, and follow the story of a mushroom-based... | Read more »
Ride into the zombie apocalypse in style...
Back in the good old days of Flash games, there were a few staples; Happy Wheels, Stick RPG, and of course the apocalyptic driver Earn to Die. Fans of the running over zombies simulator can rejoice, as the sequel to the legendary game, Earn to Die... | Read more »
Top Mobile Game Discounts
Every day, we pick out a curated list of the best mobile discounts on the App Store and post them here. This list won't be comprehensive, but it every game on it is recommended. Feel free to check out the coverage we did on them in the links below... | Read more »

## Price Scanner via MacPrices.net

Amazon is blowing out clearance 13-inch M1 Ma...
Amazon has clearance Apple 13″ M1 MacBook Airs on Memorial Day sale for \$300 off original MSRP, only \$699. Their prices are the lowest available for new MacBooks this Holiday shopping season. Stock... Read more
MacBook Sale! 13-inch M2 MacBook Airs are onl...
Best Buy has Apple 13″ MacBook Airs with M2 CPUs in stock and back on sale this week on their online store for \$150 off MSRP in Space Gray, Silver, Starlight, and Midnight colors. Prices start at... Read more
Get up to \$650 on the purchase of new or refurbished iPhone at Apple using their official Trade In program, now with increased values ahead of WWDC 2024. Trade in your old iPhone, and Apple will... Read more
Amazon has Apple’s iPad discounted to a new l...
Amazon has Apple’s 10th-generation WiFi iPads on sale discounted up to \$40 off MSRP, starting at only \$334. With the discounts, Amazon’s prices are the lowest we’ve seen for these iPads: – 10″ 10th-... Read more
13-inch M3 MacBook Airs on sale for \$100 off...
Best Buy has Apple 13″ MacBook Airs with M3 CPUs in stock and on sale this week for \$100 off MSRP. Prices start at \$999. Their prices, along with Amazon’s, are the lowest currently available for new... Read more
Weekend Deal! Best Buy has Apple Watch Series...
Best Buy has Apple Watch Series 9 models on sale for \$100 off MSRP on their online store this weekend. Sale prices available for online orders only, in-store prices may vary. Order online, and choose... Read more
Apple Watch SE on sale starting at \$199 this...
Best Buy has all Apple Watch SE models on sale this weekend for \$50 off MSRP on their online store. Sale prices available for online orders only, in-store prices may vary. Order online, and choose... Read more
Mac Pro with M2 Ultra CPU on sale for \$6699,...
In the market for one of Apple’s Mac Pro towers? B&H Photo has the base Mac Pro with an Apple M2 Ultra CPU, 64GB RAM, and 1TB SSD on sale for \$6699 including free 1-2 day shipping to most... Read more
New May Verizon promotion: Switch and get a f...
Red Hot Deal Days at Verizon: Switch to Verizon this month, and get the 256GB iPhone 15 Pro for free, with trade-in, when you add a new line of service. Verizon is also offering a free cellular iPad... Read more
Updated Apple MacBook Price Trackers
Our Apple award-winning MacBook Price Trackers are continually updated with the latest information on prices, bundles, and availability for 16″ and 14″ MacBook Pros along with 13″ and 15″ MacBook... Read more

## Jobs Board

Salon Manager BTC - *Apple* Blossom Mall -...
Salon Manager BTC - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/144535/winchester-va-united-states) - Job Read more
Omnichannel Associate - *Apple* Blossom Mal...
Omnichannel Associate - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Apple Read more
Beauty Consultant - *Apple* Blossom Mall -...
Beauty Consultant - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Apple Read more
Hair Stylist - *Apple* Blossom Mall - JCPen...
Hair Stylist - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Apple Blossom Read more
Operations Associate - *Apple* Blossom Mall...
Operations Associate - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Apple Read more