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:

Capto 1.2.9 - \$29.99
Capto (was Voila) is an easy-to-use app that takes capturing, recording, video and image editing to the next level. With an intelligent file manager and quick sharing options, Capto is perfect for... Read more
Opera 51.0.2830.40 - High-performance We...
Opera is a fast and secure browser trusted by millions of users. With the intuitive interface, Speed Dial and visual bookmarks for organizing favorite sites, news feature with fresh, relevant content... Read more
GarageSale 7.0.13 - Create outstanding e...
GarageSale is a slick, full-featured client application for the eBay online auction system. Create and manage your auctions with ease. With GarageSale, you can create, edit, track, and manage... Read more
1Password is a password manager that uniquely brings you both security and convenience. It is the only program that provides anti-phishing protection and goes beyond password management by adding Web... Read more
Evernote 7.0.1 - Create searchable notes...
Evernote allows you to easily capture information in any environment using whatever device or platform you find most convenient, and makes this information accessible and searchable at anytime, from... Read more
MacUpdate Desktop 6.2.0 - \$20.00
MacUpdate Desktop brings seamless 1-click app installs and version updates to your Mac. With a free MacUpdate account and MacUpdate Desktop 6, Mac users can now install almost any Mac app on... Read more
HoudahSpot is a versatile desktop search tool. Use HoudahSpot to locate hard-to-find files and keep frequently used files within reach. HoudahSpot will immediately feel familiar. It works just the... Read more
EtreCheck 4.0.4 - For troubleshooting yo...
EtreCheck is an app that displays the important details of your system configuration and allow you to copy that information to the Clipboard. It is meant to be used with Apple Support Communities to... Read more
WhatsApp 0.2.8361 - Desktop client for W...
WhatsApp is the desktop client for WhatsApp Messenger, a cross-platform mobile messaging app which allows you to exchange messages without having to pay for SMS. WhatsApp Messenger is available for... Read more
iClock is a menu-bar replacement for Apple's default clock but with 100x features. Have your Apple or Google calendar in the menubar. Have the day, date, and time in different fonts and colors in the... Read more

## Latest Forum Discussions

The best games like Florence
Florence is a great little game about relationships that we absolutely adored. The only problem with it is it's over a little too soon. If you want some other games with some emotional range like Florence, check out these suggestions: [Read more] | Read more »
Angry Birds Champions adds cash prizes t...
Collaborating with developer Rovio Entertainment, GSN Games has released a twist on the Angry Birds formula. Angry Birds Champions features the same bird-flinging gameplay, but now you can catapult Red and co for cash. | Read more »
Around the Empire: What have you missed...
148Apps is part of a family. A big family of sites that make sure you're always up to date with all the portable gaming news. Just like a real family, I guess. I don't know, my mum never told me anything about Candy Crush to be fair. [Read more] | Read more »
The Battle of Polytopia Guide - Tips for...
The addition of multiplayer to The Battle of Polytopia has catapulted the game from a fun enough time waster to a fully-fledged 4X experience on your phone. We've been playing quite a few matches over the past week or so, and we've put together a... | Read more »
All the best games on sale for iPhone an...
Hi there, and welcome to our round up of all the best games that are on sale for iOS at the moment. It's not a vintage week in terms of numbers, but I'm pretty sure that every single one of these is worth picking up if you haven't already played... | Read more »
Disc Drivin' 2 Guide - Tips for the...
We're all still playing quite a bit of Disc Drivin' 2 over here at 148Apps, and we've gotten pretty good at it. Now that we've spent some more time with the game and unlocked more powerups, check out some of these more advanced tips: | Read more »
Alto's Odyssey Guide - How to Tackl...
Alto’s Odyssey is a completely stunning and serene runner, but it can also be a bit tricky. Check out these to try and keep your cool while playing this endless runner: Don’t focus too much on tasks [Read more] | Read more »
Here's everything you need to know...
Alto's Odyssey is a really, really good game. If you don't believe me, you should definitely check out our review by clicking this link right here. It takes the ideas from the original Alto's Adventure, then subtly builds on them, creating... | Read more »
Alto's Odyssey (Games)
Alto's Odyssey 1.0.1 Device: iOS Universal Category: Games Price: \$4.99, Version: 1.0.1 (iTunes) Description: Just beyond the horizon sits a majestic desert, vast and unexplored. Join Alto and his friends and set off on an endless... | Read more »
Vainglory 5v5: Everything you need to kn...

## Price Scanner via MacPrices.net

Sale! Amazon offers 13″ 2.3GHz MacBook Pros f...
Amazon has 2017 13″ 2.3GHz Apple MacBook Pros on sale today for \$151-\$150 off MSRP, each including free shipping: – 13″ 2.3GHz/128GB Space Gray MacBook Pro (MPXQ2LL/A): \$1148 \$151 off MSRP – 13″ 2.... Read more
Apple AirPods in stock today for \$159, free s...
Adorama reports stock of Apple AirPods today for \$159 including free shipping, plus pay no sales tax outside of NY & NJ. See our Apple AirPod Price Tracker for the latest prices and stock status... Read more
Saturday Sale: Amazon offers 12″ 1.3GHz MacBo...
Amazon has Silver and Gold 2017 12″ 1.3GHz Retina MacBooks on sale for \$250 off MSRP. Shipping is free: – 12″ 1.3GHz Silver MacBook: \$1349.99 \$250 off MSRP – 12″ 1.3GHz Gold MacBook: \$1349.99 \$250... Read more
Use your Apple Education discount and save up...
Purchase a new Mac using Apple’s Education discount, and take up to \$400 off MSRP. All teachers, students, and staff of any educational institution with a .edu email address qualify for the discount... Read more
Apple Canada offers 2017 21″ and 27″ iMacs fo...
Canadian shoppers can save up to \$470 on the purchase of a 2017 current-generation 21″ or 27″ iMac with Certified Refurbished models at Apple Canada. Apple’s refurbished prices are the lowest... Read more
9″ iPads available online at Walmart for \$50...
Walmart has 9.7″ Apple iPads on sale for \$50 off MSRP for a limited time. Sale prices are for online orders only, in-store prices may vary: – 9″ 32GB iPad: \$279.99 \$50 off – 9″ 128GB iPad: \$379.99 \$... Read more
15″ Apple MacBook Pros, Certified Refurbished...
Save \$360-\$420 on the purchase of a 2017 15″ MacBook Pro with Certified Refurbished models at Apple. Apple’s refurbished prices are the lowest available for each model from any reseller. An standard... Read more
Amazon restocks MacBook Pros with models avai...
Amazon has restocked 15″ and 13″ Apple MacBook Pros with models on sale for up to \$251 off MSRP. Shipping is free. Note that stock of some Macs may come and go (and some sell out quickly), so check... Read more
Lowest price of the year: 15″ 2.8GHz Apple Ma...
Amazon has the 2017 Space Gray 15″ 2.8GHz MacBook Pro on sale today for \$251 off MSRP. Shipping is free: – 15″ 2.8GHz Touch Bar MacBook Pro Space Gray (MPTR2LL/A): \$2148, \$251 off MSRP Their price is... Read more
Apple restocks full line of Certified Refurbi...
Apple has restocked a full line of Apple Certified Refurbished 2017 13″ MacBook Pros for \$200-\$300 off MSRP. A standard Apple one-year warranty is included with each MacBook, and shipping is free.... Read more

## Jobs Board

*Apple* Media Products Commerce Engineering...
# Apple Media Products Commerce Engineering Manager Job Number: 56207285 Santa Clara Valley, California, United States Posted: 26-Jan-2018 Weekly Hours: 40.00 **Job Read more
Digital Platforms Lead, Today at *Apple* -...
# Digital Platforms Lead, Today at Apple Job Number: 56178747 Santa Clara Valley, California, United States Posted: 23-Feb-2018 Weekly Hours: 40.00 **Job Summary** Read more
*Apple* Retail - Multiple Positions - Apple,...
Job Description:SalesSpecialist - Retail Customer Service and SalesTransform Apple Store visitors into loyal Apple customers. When customers enter the store, Read more
*Apple* Retail - Multiple Positions - Apple,...
Job Description: Sales Specialist - Retail Customer Service and Sales Transform Apple Store visitors into loyal Apple customers. When customers enter the store, Read more
*Apple* Retail - Multiple Positions - Apple,...
Job Description:SalesSpecialist - Retail Customer Service and SalesTransform Apple Store visitors into loyal Apple customers. When customers enter the store, Read more