Program CairoShootOut;{Copyright © 1987 by Duane Blehm, All Rights Reserved, Distribution of this SourceCode is strictly prohibited!} {version 1.2, fixes bug in formula to find random Char's for targets} {disclaimer: We make not claims about the 'elegance' of this code. We are relatively new to Pascal and to Mac specific memory management, etc. Our trial and error code will seem crude to anyone experienced in these matters. ... but we just keep on learning and having fun! If you find any 'for sure' Errors, let us know.} {A couple of procedures dealing with the 'Double KeyCode Lock' scheme have been removed from this code. Also the game resulting from compiling this code will not execute very well beyond the first few levels; however as an example of how the game works the code is otherwise complete. Please do not distribute any copys of the game produced by compiling this code! The Copy of 'Cairo ShootOut!' on this disk is complete in every way.} {we have a growing library of 'crude' utilitys for creating the various 'custom' resources used in this and our other programs. For instance we've written one to create our 'RGN ' resources which are 'tracings' of picture outlines used to mask irregular shapes for use with CopyBits. We have a couple that take resources created with MacroMind's Music to Video utility (from MusicWorks data) and create our 'SONG' resources. Also one that reads text from a text file data fork and creates the 'TBUF' text resources we use with the TextBox() rom call. We noticed the 'bmap' resource type in ResEdit and came up with procedures to save and 'get' this type resource which can result in significant savings of space and code in the late stages of program developement. Including all this stuff is beyond the scope of this code. However, if you are interested in specific (not elegant) code, it is available. Drop us a line about what you'd like to see plus $15 and we'll send you a disk on 'how we did it'.} {note this version executes GameLoop level#1 at approx. 4 ticks/loop or 15 loops/ second} {also does away with Cairo font,had to write myTextBox() to draw clues between levels with myCFPtr (Cairo font bitimage)} USES MacIntf,MacPrint;{we may have to print the order form} {$T APPL NILE set the TYPE and CREATOR}{TML Pascal Directives} {$B+ set the application's bundle bit } {$L aCairo.rsrc}{link all our resource stuff} CONST WindResId = 128; {Window Resource} lastMenu = 5; {Number of Menus} appleMenu = 1; fileMenu = 256; {Menu Resource Id's} optionMenu = 257; worksMenu = 258; messageMenu = 259; {Control Resources} pressResume = 130; pressEnd = 131; LastTarget = 7;{number of targets in list} MaxInteger = 32767; TYPE {type Dialogs and Sounds so we can access them by name} DialogList = (Help,About,ClueDial,BMap,Tomb,Guard,What, UnLock,Source,Scores,Name,SetSpeed,PrintDial); SoundList = (GunShot,GoodHit,BadHit,Miss,BonusSnd,TimeOut, SnakeCharmer,Walk,Silence); {type bmap for loading previously created 'bmap' resources} BmapRec = Record baseAddr:Ptr; rowBytes:integer; bounds:Rect; bitStart:array[1..1] of byte;{so we can Ptr to start of BitImage} end; BmapPtr = ^BmapRec; BmapHand = ^BmapPtr; {to get bmap's we created the BitMaps as in the Animation Example, then drew the pictures,etc... then called the following to make the bmap: procedure CreateBMaps(addBits:Boolean); var theErr:OsErr; myBMap:BmapHand; Begin (*make a copy of the Bitmap with a Handle*) myBMap := BmapHand(NewHandle(SizeOf(BitMap))); myBMap^^.rowBytes := OffScreen.rowBytes; myBMap^^.bounds := OffScreen.bounds; (*now stick a copy of the BitImage on the end of the Handle*) If addBits then theErr := PtrAndHand(OffScreen.baseAddr, Handle(myBMap),GetPtrSize(OffScreen.baseAddr)); (*watch out for memory errors!*) AddResource(Handle(myBmap),'bmap',128,'offscreen'); If theErr <> noErr then SysBeep(1);(*our PtrAndHand probably failed*) End; this is more efficient than a lot of pictures, creating Bitmaps, drawing, locating rects, etc... however locking all this down too soon in development process could hinder making changes later on....} ScoreNText = Record {record for hiscores} nameStr:Str255;{we'll pack 4 names into NameStr} level:array [1..4] of Longint; score:array [1..4] of Longint; end; ScorePtr = ^ScoreNText; {at one time we included the Cairo font in resources, loaded it and made copies of Bitimage and locate table... but came up with this record to store only the parts we needed!.. see 'CairoFont.pro' file for more info} CFRec = Record {resource contains only bitimage and locate table of Cairo} bitImage:Array[1..105,1..24] of integer;{char. bitimage} locate:Array[0..130] of integer;{partial location table} end; CFPtr = ^CFRec; CFHandle = ^CFPtr; {note: it is easy to create a resource... just create the data with a handle to it... do an AddResource()..copy/paste it into application resource file and reverse the procedure to load it into application.} nrctRec = Record {so we can load a list of rects from resource} num:integer;{Number of records} theRects:array[1..100] of Rect;{limits on # of rects??} end; nrctPtr = ^nrctRec; nrctHandle = ^nrctPtr; ClueRec = Record{record of stuff for drawing clues between levels} message:Str255; {message for this clue} messRect:Rect; {box to contain message.. TextBox()} hint:Str255; {clue word} hintRect:Rect; just:integer; {justification for hint textbox,all messages centered} glyphPic:PicHandle;{heiroglyph picture from resource} dialHand:Handle;{handle to textitem in dialog} dialWord:Str255;{string contains user input} end; SongRec = Record {import a song record from our snake 'SONG' resource} noteCount:integer;{how many notes in this song} pitch:array[1..48,1..2] of Longint;{48 notes,2 voices} duration:array[1..48] of integer;{how many ticks will pitch play} end; SongPtr = ^SongRec; SongHandle = ^SongPtr; WSongRec = Record {import a song record from our Walk 'SONG' resource} noteCount:integer;{how many notes in this song} pitch:array[1..68,1..2] of Longint;{68 notes,2 voices} duration:array[1..68] of integer;{how many ticks will pitch play} end; WSongPtr = ^WSongRec; WSongHandle = ^WSongPtr; {note:could have used same song type for both songs except for # of notes, We'll change the Record to be note 'independent' next time.} TimeBonusRec = Record {stuff for display of Time-to-Score at end of level} frame:Rect; {size window for Timebonus} hourmove:Point; {offset for hourglass...move from normal position} numbox:Rect; {frame for numbers} arrowDest:Rect; {destination for copybits of arrow} arrowSource:Rect;{source in Cairo bitmap} end; TargetRec = Record {record of individual targets on display} theChar:integer;{character in the font} head:integer; {pixOff when the target begins in Wings} wide:integer; {width of target} end; ValidRec = Record {record for scorebox and score targets} which:integer; {which char in font} points:integer; {points for hitting this target} remain:integer; {how many of this target left to hit} remainPt:Point; {for finding moveto point for drawString} blankRect:Rect; {erase rect for drawing remain # into scorebox} centerPt:Point; {top,centerline of box in scoreframe} end; StatusRec = Record {4 boxes for score,hiscore,etc} blankRect:Rect; {rect to erase before drawing new number} centerPt:Point; {for finding MoveTo point before DrawString} end; VAR {local var's made global.. for speed of execution of code} unNames: array[1..4] of Str255; {unpack our hiscore names} RListHand: nrctHandle; si,sj: integer;{StartOne and StaggerOne} di,dj,dk,dm: integer;{DetectAHit and HandlAHit} HitAValidTarget: boolean; GameOver: Boolean; {set True if hourglass empty} myRects: nrctHandle; {access to Rects in nrct resource} TarWindPic: PicHandle; {pic of our collective windows} myCFPtr: CFPtr; {access to Cairo font stuff in resources} SpeedTrapOn: Boolean; {if true slow down the game} SpeedFactor, SpeedTicks: Longint; {rate of delay / slow down} theHighs: ScorePtr; {access to hiscore record/handle} ScoreHandle: Handle; {handle to hiscore resource} NameString: Str255; {store our players name for hiscore} BuffSize: Longint; {size buffers} PagePrint: THPrint; { handle to the page setup record } ResRefNum: integer; {reference number for appl.resource file} tBuf: Handle; {text from TBUF resource for TextBox} ClueNdx: integer; {point to next clueword} ClueWords: Array[1..10] of Str255; {hold various possible clues} DialNdx: DialogList; {will step through our dialogs array} myDialog: Array[Help..PrintDial] of DialogPtr; myDialogRec: Array[Help..PrintDial] of DialogRecord; Tut: Array[168..171] of PicHandle; {for drawing tomb} ClueShadow: PicHandle; {shadow the booth to hilight clue} ClueErase: Rect; {rect for erase before Clues} Clues: Array[1..4] of ClueRec; {our awards..} FetchChar: Rect; {used in startOne... constant top/bottom} CursorNdx: integer; {which cursor to use} myCursor: Array[1..3] of CursHandle;{our three cursors} StartPitch: Array[1..4] of Longint;{pitches to start targetsound} UpDateMap: BitMap; {keep a copy of targetwindows during pause} UpDateRect: Rect; {for copybits of bitmap} ResumeButton, EndButton: ControlHandle; { handles to controlbuttons } RedrawRect: Rect; {area for invalRect for levels} TimeCount: integer; {interval between hourglass moves} TBone: TimeBonusRec;{hold all our timebonus stuff} ShadowRect: Rect; {rect used to shadow booth interior} LastValid: integer; {number of Score targets..max of 6} Score,HiScore, Level,MissCount: Longint; StatPic: PicHandle; {Status is pic of Score/HiScore frame} StatList: Array[1..4] of StatusRec;{stuff for drawing Scores,etc} ValidDone: Boolean; {flag is all valids have 0 remain} Direction: Array[1..3] of integer;{scroll right or left} Vector: Array[1..3] of integer;{will be product of speed and direction} TarWindRgn: RgnHandle; {region to mask drawing eye in TarWindow[4]} BonusMap: BitMap; {load from bmap resource, contains BTarget pics} OffBonus: Array[1..6,1..2] of Rect;{Rects to shapes in BonusMap} BonusNdx: integer; {pointer to OffBonus rects, 1-6} BonusShape: integer; {pointer to 1 of two shapes,internal anim.} BonusRect: Rect; {onScreen Rect for CopyBits of Bonus Shapes} BonusUnderWay: Boolean; {is bonus underway?} BonusLoops, PrevBonusLoops: integer; {count for # of loops} BonusSpeed: integer; {-4 to +4 lateral movement...left or right} MouseRect, GunLimit: Rect; {limit movement of site during game} GameUnderWay: Boolean; PauseStatus: integer; {0 no pause,1 game pause, 2 clue pause} NowValid: Array[1..6] of integer;{list of Valids with targets remaining} NowNdx: integer; {index for NowValid} ScoreNumRect, ScoreFrameRect: Rect; ScoreNum, ScoreFrame: PicHandle; {for drawing ScoreBox frame} tPt: Point; tFlag: Boolean; tRect,sRect,dRect: Rect; {global temporary varibles} LeftSide,RightSide, BackSide: PicHandle; {our shooting gallery pics} Valid: Array[1..6] of ValidRec;{list of valid targets} OffHour,Hour, {hourglass} OffBotSand,OffTopSand, {frame complete top and bottom sand} OBSand,BotSand, {partial top and bottom sand,for animation} OTSand,TopSand: Rect; TrickleTop, TrickleBot: Point; {left sand trickle (line) top and bottom} TrickTopR, TrickBotR: Point; {points for drawing right sand trickle line} TrickleLeft: Boolean; {true if trickle currently leftside} BotSandRgn, TopSandRgn: RgnHandle; {regions for masking sand into hourglass} {for drawing spot targets in side windows} SpotSpeed: Array[5..6] of integer;{window 5&6,speed of target} OffSpotTarget: Rect; {whole spot} OffSpot: Array[5..6] of Rect;{partial spot...} SpotRect: Array[5..6] of Rect;{locate spot on screen} TopSpot,BotSpot: Array[5..6] of integer;{turn around point for spots,speed} TarList: Array[1..3,1..LastTarget] Of TargetRec;{targets on Screen} NxtTar, {points to TarList element} Speed,PixOff: Array[1..3] of integer;{pixoff= # of pixels target moves} HitWhich, {which target in the window target list} HitWindow: integer; {which window hit was made in} Hit: Boolean; {true if a target was hit} TargetCenter: Point; {point at center of Rect pointed to by mouse/site} myMenus: Array[1..lastMenu] of MenuHandle; refNum,theMenu, theItem: integer; Finished: Boolean; {used to terminate the program,quit} ClockCursor: CursHandle; {handle to watch cursor} myWindow: WindowPtr; wRecord: WindowRecord;{we alocate window on the stack} Screen,DragArea: Rect; {set limits of drag area} myMouse: Point; {used with GetMouse in animateoneloop} aTick: Longint; {usually with TickCount but sometimes just temp.} ExplodeRect, OffExplode: Rect; {Rects to explode shape in offscreen} WhichGun: integer; {which gun to draw for gray backgound} GunRect: Rect; {destination rect for drawing Gun} OffGun,OffSite: Array[0..1] of Rect;{two guns, for drawing over gray} TarWindow: Array[1..6] of Rect;{our targets windows..6 of them} ShotFired: Boolean; Piece,OffPiece: Array[1..3] of Rect;{rects to tiny area left after scroll} SiteRect:Rect; SiteRgn: Array[0..1] of RgnHandle;{for drawing site into OffGun rects} myPattern, myDiagPat: Pattern; {patterns for drawing shading} FontMap: BitMap; {Cairo font bitimage from resource,myCFPtr} Wings: Array[1..3] of BitMap;{new targets just coming into window} tStr: Str255; tPtr: Ptr; tLong: Longint; OffScreen,OldBits: BitMap; {for drawing into offscreen} tRgn: RgnHandle; Counter: integer; {count loops for hourglass/time} i,j,k,m,n: integer; {Sound varibles} Buff: Array[GunShot..TimeOut] of Longint;{for Sound buffers} myPtr: Ptr; myHandle: Handle; Sound: Array[GunShot..SnakeCharmer] of FFSynthPtr;{FreeForm synthesizer sound} SnakeSynth: FTSynthPtr; {Snake is a FourTone sound/use for Walk too} SnakeSound: FTSndRecPtr; {two FourTone Sounds} SoundParmBlk: ParmBlkPtr; {used for PBWrite instead of StartSound} WhichSound: SoundList; {which sound is being played?} err: OSerr; SawToothWave: wavePtr; SquareWave: wavePtr; mySongPtr: SongPtr; {snake song} myWSongPtr: WSongPtr; {walk song.. different # of notes} ToneTicks, ToneDelay: Longint; {# of ticks each song pitch will play} NoteCount: Longint; {# of notes played for song} SoundOn: Boolean; {user request for sound off/on} {----------------------------------------------} procedure SetCursorMenus(StartGame:Boolean); var i:integer; Begin If StartGame then begin {starting the game} InsertMenu(myMenus[5],0); {display 'any key to exit' message} For i := 1 to 4 do DisableItem(myMenus[i],0);{disable (gray) others} DrawMenuBar; {draw the menus} GameUnderWay := True; {we're back into game mode} HideCursor; FlushEvents(mDownMask,0); {clear all mouseDowns} end Else Begin {interrupt the game} If CursorNdx < 3 then inc(CursorNdx) else CursorNdx := 1; SetCursor(myCursor[CursorNdx]^^);{display one of our 3 cursors} ShowCursor; GameUnderWay := False; DeleteMenu(MessageMenu); {remove exit message,i-354} For i := 1 to 4 do EnableItem(myMenus[i],0);{show other menu options} DrawMenuBar; end; End; procedure DrawStatus(which:integer;amount:Longint); {draw score,missCount, etc. into Stat frame} Begin NumToString(amount,tStr);{convert number to string} MoveTo(StatList[which].centerPt.h-(StringWidth(tStr) div 2), StatList[which].centerPt.v);{center it} EraseRect(StatList[which].blankRect);{erase last} DrawString(tStr);{draw it!} end; {myTextBox is similar to TextBox() rom but uses Cairo BitImage in myCFPtr} procedure myTextBox(text:Ptr;length:Longint;box:Rect;just:integer); var StartLine:Ptr; EndLine,TestEnd:Ptr; LineWidth,TestWidth:integer; Done:Boolean; which:byte; procedure DrawLine; var index:Longint; Begin Case just of {justification....find starting point for first dRect} -1: dRect.left := box.right - LineWidth; {right} 0: dRect.left := box.left;{left} 1: dRect.left := box.left + ((box.right-box.left-LineWidth) div 2);{center} end;{case} For index := ord(StartLine) to ord(EndLine) do begin If Ptr(index)^ <> 32 then begin {Ptr(index)^ points to which} sRect.left := myCFPtr^.locate[Ptr(index)^]; sRect.right := myCFPtr^.locate[Ptr(index)^+1]; dRect.right := dRect.left + sRect.right-sRect.left; CopyBits(FontMap,myWindow^.portBits,sRect,dRect,srcCopy,nil); dRect.left := dRect.right + 2;{move over for next char} end{if Ptr(} else dRect.left := dRect.right + 10;{for space char} end;{for index} End; procedure InitialLine; Begin StartLine := EndLine; TestEnd := Endline; LineWidth := 0; TestWidth := 0; end; procedure NewLine; Begin OffSetRect(dRect,0,28);{move down a line,font height + 4} EndLine := Ptr(ord(EndLine)+1);{end currently a space so move one} {could test here for more spaces!} InitialLine; End; Begin SetRect(sRect,0,0,0,24);{height of 24 for Cairo} If length > 0 then length := length -1;{we're zero based} {initialize varibles} EndLine := text; InitialLine; dRect.top := box.top; dRect.bottom := dRect.top + 24;{fHeight := 24 for Cairo} Done := false; EraseRect(box); Repeat which := TestEnd^; If which <> 32 then begin {break lines on spaces} TestWidth := TestWidth + myCFPtr^.locate[which+1] - myCFPtr^.locate[which] + 2; {test for end of text here} If ord(TestEnd) >= (ord(text) + length) then Begin {end of text} If TestWidth > (Box.right-Box.left) then begin {line too long} DrawLine; NewLine;{set TestEnd back to Last EndLine and continue} end else begin {line width ok so draw it all} EndLine := Ptr(ord(text)+length);{set to end of text} LineWidth := TestWidth;{approx width?} DrawLine; Done := True; {we're finished} end;{else} end;{if ord} end{if which} else begin {current which is a space} TestWidth := TestWidth + 10;{width of my space} If TestWidth > (Box.right-Box.left) then begin {word won't fit} DrawLine;{draw the line without Test stuff on end} NewLine; end else begin {move actual end of line up to testend} EndLine := TestEnd; LineWidth := TestWidth; end;{else } end;{else} TestEnd := Ptr(ord(TestEnd)+1);{move TestEnd up one} Until(Done); End; procedure DrawAClue(index:integer);{clues are displayed on even levels 2-8} Begin DrawPicture(ClueShadow,ClueShadow^^.picFrame);{shadow on booth} EraseRect(ClueErase);{erase the background in target area} {draw one of 4 glyph pictures} DrawPicture(Clues[index].glyphpic,Clues[index].glyphpic^^.picframe); {myTextBox uses Cairo bitimage in FontMap to display text} myTextBox(Pointer(ord(@Clues[index].message)+1),Length(Clues[index].message), Clues[index].messRect,teJustCenter);{draw message} myTextBox(Pointer(ord(@Clues[index].hint)+1),Length(Clues[index].hint), Clues[index].hintRect,Clues[index].just);{draw clueword} End; procedure DrawRemainIntoScoreBox(which:integer); {draw # of targets remaining for 'which' Valid target} Begin EraseRect(Valid[which].blankRect);{erase last} NumToString(Valid[which].remain,tStr); MoveTo(Valid[which].remainPt.h-(StringWidth(tStr) div 2), Valid[which].remainPt.v);{center it} DrawString(tStr); end; procedure PaintAShadow(aRect:Rect);{used to shade booth and Valid targets} Begin PenMode(patOr);{ will combine pattern with existing background} PenPat(myDiagPat);{diagonal lines} PaintRect(aRect);{fill aRect with the pattern... mode 'or'} PenNormal; {reset the Pen stuff to default} end; procedure DrawHourGlass;{draw hourglass and top/bottom sand} Begin CopyBits(OffScreen,myWindow^.portBits,OffHour,Hour,srcCopy,nil);{hourglass} {OTSand & OBSand rects are moving over Sand pics on OffScreen} CopyBits(OffScreen,myWindow^.portBits,OTSand, TopSand,srcCopy,TopSandRgn);{top Sand} CopyBits(OffScreen,myWindow^.portBits,OBSand, BotSand,srcCopy,BotSandRgn);{Bottom sand} end; procedure SizeRectsForBox(whichchar,whichbox:integer); {for drawing char/targets into scorebox each level and updates} Begin {sRect.top and bottom are constant.. fontMap.bounds} sRect.left := myCFPtr^.locate[whichchar];{left point from locate table} sRect.right := myCFPtr^.locate[whichchar+1];{right point from locate table} {center tRect in its ScoreBox} tRect.left := Valid[whichBox].centerPt.h - ((sRect.right-sRect.left) div 2); tRect.right := tRect.left + (sRect.right-sRect.left); End; procedure DrawTargetsInScoreBox; Begin {draw the targets into the scoreBox} sRect := FontMap.bounds;{bounds rect of FontMap bitmap,top/bottom constant} For i := 1 to LastValid do begin {4-6 scoreboxes} tRect.top := Valid[i].centerPt.v;{topcenter of box} tRect.bottom := tRect.top + 24;{height of font} SizeRectsForBox(Valid[i].which,i);{size sRect/tRect left and right} CopyBits(FontMap,myWindow^.portBits,sRect,tRect,srcCopy,nil); DrawRemainIntoScoreBox(i);{# remaining, draw just below target} if Valid[i].remain = 0 then begin {disabled shading over box} tRect := Valid[i].blankrect; tRect.top := Valid[i].centerPt.v;{tRect now is whole box} PaintAShadow(tRect); end;{if valid remain} end; End; procedure DrawWindowContents(WhichWindow:WindowPtr);{update events} Begin DrawPicture(BackSide,BackSide^^.picFrame);{back/center of booth} DrawPicture(LeftSide,LeftSide^^.picFrame);{left side of booth} DrawPicture(RightSide,RightSide^^.picFrame);{right side of booth} DrawPicture(ScoreFrame,ScoreFrameRect); {score box frame} DrawPicture(ScoreNum,ScoreNumRect); {points in score box} DrawPicture(StatPic,StatPic^^.picFrame); {status/score box frame} DrawHourGlass; {hourglass stuff} DrawStatus(1,Level);{four varibles in status box, score etc.} DrawStatus(2,Score); DrawStatus(3,MissCount); DrawStatus(4,HiScore); Case PauseStatus of {PauseStatus tells us 'how' the screen 'looks',what to draw} 0:Begin {no pause.. usually in between games} DrawPicture(TarWindPic,TarWindPic^^.picFrame); If not(GameUnderWay) then begin PaintAShadow(ShadowRect);{shadow the booth interior} DrawTargetsInScoreBox; end; end;{case 0} 1,2:Begin {game under way pause...1 = keydown interrupt?, 2 = clue} If PauseStatus = 1 then CopyBits(OffScreen,mywindow^.portBits, OffGun[whichGun],GunRect,srcCopy,nil);{redraw gun} DrawTargetsInScoreBox; If PauseStatus = 2 then DrawAClue(Level div 2) {clue on display} else {Copy from UpDateMap... contains copy of targets, when pause began} CopyBits(UpDateMap,myWindow^.portBits,UpDateMap.bounds,UpDateRect, srcCopy,nil); end;{case 1,2} end;{case pauseStatus} DrawControls(myWindow);{draw Continue/End buttons if they're not hidden} End; procedure UpdateMyWindow; Begin BeginUpDate(myWindow); EraseRect(myWindow^.portRect);{will be clipped to update rgn} DrawWindowContents(myWindow); EndUpDate(myWindow); End; procedure DrawBoldButton(WhichDialog:DialogList);{bold button in dialogs} var tHand:Handle;{j:integer,tRect:Rect} Begin PenSize(3,3); GetDItem(myDialog[WhichDialog],1,j,tHand,tRect);{frame Item #1} InsetRect(tRect,-4,-4); FrameRoundRect(tRect,16,16); PenSize(1,1); End; procedure DisplayWhatDialog(WhichDialog:DialogList);{called from Guard dialog} var tRect,fRect: Rect; itemHit,i,j: integer; tPort: GrafPtr; Begin GetPort(tPort); ShowWindow(myDialog[WhichDialog]);{all dialogs are hidden} SelectWindow(myDialog[WhichDialog]);{bring it to the front, activate} SetPort(myDialog[WhichDialog]); {so we can draw into our dialog window} tBuf := GetResource('TBUF',127);{load our text from resource} HLock(tBuf);{lock it down, so we can use a ptr, don't want it moving on us} TextFont(3); TextSize(9); {draw text into dialog window} TextBox(tBuf^,GetHandleSize(tBuf),myDialog[what]^.portRect,teJustLeft); HUnLock(tBuf);{done} DrawBoldButton(WhichDialog); ModalDialog(Nil,itemHit); HideWindow(myDialog[WhichDialog]); SetPort(tPort);{restore port} end; procedure DrawOrderForm(frame:Rect);{for printing} var tRect:Rect; tStr:Str255; myHandle:Handle; Begin {all drawing is being done into the printer port/bit map} TextFont(geneva); TextSize(10); tRect := frame;{size of printing output} tRect.top := tRect.top + 130;{reserve 130 pixels for our header} InsetRect(tRect,20,0);{shink the page size} {draw page stuff here} HLock(tBuf);{tbuf loaded in the DoPrint procedure} TextBox(tBuf^,GetHandleSize(tBuf),tRect,teJustCenter);{draw it } HUnLock(tBuf); tRect := Clues[3].glyphpic^^.picframe;{Chariot glyph pic, upper left} OffSetRect(tRect,frame.left+20-tRect.left,frame.top+10-tRect.top); DrawPicture(Clues[3].glyphpic,tRect); GetIndString(tStr,1000,7);{source of copy of program} tRect.top := tRect.top + 10;{relocate tRect from pic above} tRect.left := tRect.right + 10; tRect.right := tRect.left + 100; tRect.bottom := 129; TextBox(Pointer(ord(@tStr)+1),Length(tStr),tRect,teJustLeft); TextSize(12); GetIndString(tStr,1000,5);{logo text stuff,upper right} tRect := frame; tRect.top := tRect.top + 10; tRect.left := tRect.right - 180; tRect.bottom := 129;{just above completed textbox!} TextBox(Pointer(ord(@tStr)+1),Length(tStr),tRect,teJustLeft); OffSetRect(tRect,-186,0); tRect.bottom := tRect.top + 32; tRect.left := tRect.right - 32;{for drawing logo icon} myHandle := GetIcon(130); PlotIcon(tRect,myHandle); MoveTo(Frame.left + 20,Frame.top + 120);{just below glyph pic} TextSize(12); TextFace([Bold,Shadow]); DrawString('Cairo #1 KeyCode = '); End; procedure DoPrintUpdates;{Printer dialogs erase the windows behind them so update myWindow,Guard dialog,display PrintDialog} {there has got to be a better way of updating while dialogs are on display.. perhaps by using Modeless dialogs and handling all events in the program rather than calling ModalDialog()} var EventFlag,Result:Boolean; tPort:GrafPtr; myEvent:EventRecord; itemHit:integer; Digit:Char; Begin GetPort(tport); SetPort(myWindow); UpdateMyWindow;{dialogs have erased our window so do updates} SetPort(myDialog[Guard]);{do updates pending for dialog} Repeat Result := GetNextEvent(everyEvent,myEvent);{mask only Updates?} EventFlag := IsDialogEvent(myEvent); If EventFlag then Result := DialogSelect(myEvent,myDialog[guard],itemHit) Until(not(EventAvail(EveryEvent,myEvent))); ShowWindow(myDialog[PrintDial]);{our 'cancel printing' dialog} SelectWindow(myDialog[PrintDial]); SetPort(myDialog[PrintDial]); {so we can draw into our dialog window} Digit := chr(17);{this is the 'Commandkey symbol'} tStr := concat(Digit,' .'); ParamText(tStr,'','','');{install command key-period into dialog ^0} Repeat Result := GetNextEvent(everyEvent,myEvent);{do updates} EventFlag := IsDialogEvent(myEvent); If EventFlag then Result := DialogSelect(myEvent,myDialog[PrintDial],itemHit) Until(not(EventAvail(EveryEvent,myEvent))); SetPort(tPort);{restore port and get back to printing stuff} End; procedure DoPrint;{print the order form} var myPrPort:TPPrPort; myStRec:TPrStatus; tPort:GrafPtr; begin GetPort(tPort);{we'll be changing to printer port, so get copy of current} tBuf := GetResource('TBUF',128);{order form text,had error during OpenPage?} PagePrint := THPrint(NewHandle(SizeOf(TPrint)));{create our print record} PROpen;{open the print manager!! ii-157} If PrError = noErr then begin PrintDefault(PagePrint);{fill with default values ii-158} {tFlag := PrStlDialog(PagePrint);this is page setup stuff} If PrJobDialog(PagePrint) then begin {dialog with user} DoPrintUpDates;{clean up our windows,display 'cancel' dialog} myPrPort := PrOpenDoc(PagePrint,nil,nil); PrOpenPage(myPrPort,nil); { start new page} If PrError = noErr then DrawOrderForm(PagePrint^^.prInfo.rPage);{draw my page} PrClosePage(myPrPort); PrCloseDoc(myPrPort); If (PagePrint^^.prJob.bjDocLoop = bSpoolLoop) AND (PrError = noErr) then PrPicFile(PagePrint,nil,nil,nil,myStRec); {If PrError <> noErr then alert user of printing error} end;{if user says then open the printing job} end;{if PROpen successfull} PRClose;{close the print manager} HideWindow(myDialog[PrintDial]);{close our cancel/command-period dialog} SetPort(tPort); End; procedure DisplayAlert(WhichAlert:integer;Var result:integer); var tPort:GrafPtr; Begin GetPort(tPort); result := NoteAlert(WhichAlert,nil);{display the alert} ResetAlrtStage;{i-423} SetPort(tPort); End; procedure SetControlValue(which:integer); {for SetSpeed dialog,which item#, the radio controls are numbered 2 thru 4} var i,h:integer; Begin For i := 2 to 4 do begin {reset controls to user selected control,which} GetDItem(myDialog[SetSpeed],i,h,myHandle,tRect); If i = which then SetCtlValue(ControlHandle(myHandle),1) else SetCtlValue(ControlHandle(myHandle),0); end; Case which of 2: SpeedTrapOn := False;{normal mode... no delays} 3,4:begin SpeedTrapOn := True;{flag that delays are needed} If which = 3 then SpeedFactor := 2 else SpeedFactor := 3;{how much delay.. 2 or 3 ticks} end; end;{case which} End; procedure DisplayDialog(WhichDialog:DialogList);{from dialog list} var tRect,fRect,messFrame,itemRect: Rect; itemHit,i,j,theType: integer; tPort: GrafPtr; tHandle: Handle; index:integer; Begin GetPort(tPort); ShowWindow(myDialog[WhichDialog]);{dialogs are hidden in resource} SelectWindow(myDialog[WhichDialog]);{bring to front, activate} SetPort(myDialog[WhichDialog]); {so we can draw into our dialog window} FlushEvents(mDownMask,0); {clear all mouseDowns} DrawBoldButton(whichDialog);{draw item one button bold, Enter key default} Case WhichDialog of Help: ModalDialog(Nil,itemHit); {close it no matter what was hit} About:begin aTick := 1;{for a delay} Repeat ModalDialog(Nil,itemHit); If itemHit = 8 then begin {animate the chariot from OffScreen} SetRect(tRect,0,0,43,19);{size of one chariot} fRect := tRect; OffSetRect(tRect,OffScreen.bounds.right-tRect.right, OffScreen.bounds.bottom-tRect.bottom);{in offScreen} OffSetRect(fRect,myDialog[About]^.portRect.left-fRect.right, myDialog[About]^.portRect.bottom - 2 -fRect.bottom); MoveTo(fRect.right,fRect.bottom); LineTo(500,fRect.bottom);{baseline} {animate until chariot is off right side} While (fRect.left < myDialog[About]^.portRect.right) do begin CopyBits(OffScreen,myDialog[About]^.portBits,tRect,fRect, srcCopy,nil);{draw a chariot} OffSetRect(fRect,3,0);{move destination to right 3 pixels} {make tRect point to one of chariot shapes in ChariotMap} If tRect.right < OffScreen.bounds.right then OffSetRect(tRect,43,0) {move right to next shape} else OffSetRect(tRect,-86,0);{move back to first} Delay(aTick,SpeedTicks);{delay one tick} end;{while fRect} end;{if itemhit} Until(itemHit = 1); end; ClueDial:{get 4 clue words from user to open tomb} Begin For i := 1 to 4 do SetIText(Clues[i].dialHand,'');{empty strings} SelIText(myDialog[ClueDial],6,0,0);{select first box} Repeat ModalDialog(Nil,itemHit);{Modal does an Update event for the dialog box} Until((itemHit = 1) or (itemHit = 11));{ok button or Pass} {exit with Pausestatus = 3 for tomb or 0 for continue} If itemHit = 1 then begin {check the user input against hints} PauseStatus := 3; For i := 1 to 4 do begin {check user input against clues} GetIText(Clues[i].dialHand,Clues[i].dialWord); If Clues[i].hint <> Clues[i].dialWord then PauseStatus := 0; end;{for i} If PauseStatus = 0 then DisplayAlert(134,i);{sorry, bad clues} end{If itemhit} else If itemHit = 11 then PauseStatus := 0;{user wants to pass} End;{cluedial} BMap:begin{peek offscreen bitmap} CopyBits(OffScreen,myDialog[BMap]^.portBits,OffScreen.bounds, OffScreen.bounds,srcCopy,nil); {copy bitmap to dialog box} tRect := BonusMap.bounds; OffSetRect(tRect,myDialog[BMap]^.portRect.right - 3 - trect.right, myDialog[BMap]^.portRect.bottom - 3 - trect.bottom); CopyBits(BonusMap,myDialog[BMap]^.portBits,BonusMap.bounds, tRect,srcCopy,nil); {bonus pics to bottom right} DrawBoldButton(whichDialog); ModalDialog(Nil,itemHit); {close it no matter what was hit} end;{BMap} Tomb:begin SetOrigin(70,6);{shrink tut to fit in dialogue window} {draw inside of tomb} DrawPicture(Tut[170],Tut[170]^^.picFrame);{background} tRect := Tut[169]^^.picFrame; For i := 1 to 11 do begin DrawPicture(Tut[169],tRect);{pics along back wall} OffSetRect(tRect,36,0); end; DrawPicture(Tut[171],Tut[171]^^.picFrame);{tutplatform} DrawPicture(Tut[168],Tut[168]^^.picFrame);{tut} SetOrigin(0,0); GetDItem(myDialog[Tomb],1,j,myHandle,tRect);{erase under button} InsetRect(tRect,-2,-2); EraseRect(tRect); {frame the text box, item #2} DrawBoldButton(WhichDialog); GetDItem(myDialog[Tomb],2,j,myHandle,tRect); InsetRect(tRect,-1,-1);{frame the text item} EraseRect(tRect); FrameRect(tRect); ModalDialog(Nil,itemHit); end;{tomb} Guard:begin {this is the protection dialog} GetDItem(myDialog[Guard],9,theType,tHandle,itemRect); tStr := 'disabled'; GetDItem(myDialog[Guard],13,theType,tHandle,tRect); SetIText(tHandle,tStr); InsetRect(tRect,-3,-3); GetDItem(myDialog[Guard],10,theType,tHandle,messFrame); InsetRect(messFrame,-4,-8);{message frame} Repeat FrameRect(tRect);{this is around our #1keycode} FrameRoundRect(messFrame,10,10);{around our message} DrawBoldButton(whichDialog);{draw each time in case updates} ModalDialog(Nil,itemHit); Case itemHit of 2: DisplayWhatDialog(What);{reinterate dialogs? don't work} 4: begin DoPrint;{print out our order form stuff} InvalRect(myDialog[Guard]^.portRect);{redraw our Dialog} FlushEvents(KeyDownMask,0);{case of key commands or such} end;{4:} 5: begin {lock the game} {display alert... consequences} DisplayAlert(135,i); If i = 2 then begin {item LOCKIT} Finished := True;{quit the game after lock!} itemHit := 1;{will close the Guard dialog} end; {if i = 2} end; 6: begin {unlock all the features?} GetDItem(myDialog[Guard],7,theType,tHandle,fRect); GetIText(tHandle,tStr); SetIText(tHandle,'disabled'); DisplayAlert(136,i);{sorry invalid keycode2} end;{6:} end;{case itemHit} InvalRect(itemRect);{always redraw the Messages:} Until(itemHit = 1);{done button} end; UnLock:begin {at end of Locked levels,about more levels,etc.} tBuf := GetResource('TBUF',129);{get text from resource} HLock(tBuf);{locked for Ptr} TextFont(3); TextSize(10); TextBox(tBuf^,GetHandleSize(tBuf),myDialog[UnLock]^.portRect,teJustLeft); HUnLock(tBuf); DrawBoldButton(WhichDialog); ModalDialog(Nil,itemHit); end;{Unlock:} Source:{source code message} ModalDialog(Nil,itemHit); {close it no matter what was hit} Scores:begin {hiscore stuff} TextFont(3); Repeat For i := 1 to 4 do begin TextSize(9); {unpack 4 names stored in theHighs^.nameStr,etc. and draw into dialog} unNames[i] := copy(theHighs^.nameStr,((i-1) * 23)+1,23);{short} MoveTo(7,87+16*i); DrawString(unNames[i]);{draw all 4 names} TextSize(12); Moveto(180,87+16*i); NumToString(theHighs^.level[i],tStr);{draw all 4 levels} DrawString(tStr); NumToString(theHighs^.score[i],tStr); MoveTo(myDialog[Scores]^.portRect.right - StringWidth(tStr) - 7,87+16*i); DrawString(tStr);{draw all 4 scores} PenPat(gray); move(0,3); lineto(7,90+16*i);{draw the lines in between scores} PenPat(black); end; ModalDialog(Nil,itemHit); if itemHit = 4 then begin {reset the hiscore stuff} For i := 1 to 4 do begin theHighs^.level[i] := 0; theHighs^.score[i] := 0; end; theHighs^.nameStr := ''; While Length(theHighs^.nameStr) < 102 do theHighs^.nameStr := Concat(theHighs^.nameStr,' '); ChangedResource(ScoreHandle);{write changes on quit} InvalRect(myDialog[Scores]^.portRect);{modalDialog will redraw items} EraseRect(myDialog[Scores]^.portRect); HiScore := 0; DrawBoldButton(WhichDialog);{redraw after erase} end;{if itemhit} Until(itemhit = 1) end;{Score} Name:Begin {get hiscorer's name/sort into hiscore record} GetDItem(myDialog[Name],3,theType,tHandle,tRect); SetIText(tHandle,NameString);{put default in text box} SelIText(myDialog[Name],3,0,maxint);{select the text} Repeat ModalDialog(Nil,itemHit);{Modal handles Update events for dialog} Until(itemHit = 1);{ok button} GetIText(tHandle,NameString);{must be happy with text..so use it} For i := 1 to 4 do {copy packed strings into temp full strings} unNames[i] := copy(theHighs^.nameStr,((i-1) * 23)+1,23); {sort the New LoScore} index := 4;{we know that score is more than 4 already} For i := 3 Downto 1 do If Score > theHighs^.score[i] then index := i;{how how is it,#1?} i := 4; While (i > index) do begin {switch old for new scores} unNames[i] := unNames[i-1]; theHighs^.score[i] := theHighs^.score[i-1]; theHighs^.level[i] := theHighs^.level[i-1]; dec(i); end;{While} theHighs^.score[index] := Score;{make 'index' score = new high} theHighs^.level[index] := Level; unNames[index] := copy(NameString,1,23);{just first 23 chars of name} theHighs^.namestr := ''; For i := 1 to 4 do begin {repack the strings into namestr} theHighs^.nameStr := concat(theHighs^.nameStr,unNames[i]); While Length(theHighs^.nameStr)< (i*23) do theHighs^.nameStr := concat(theHighs^.nameStr,' '); end;{for i} ChangedResource(ScoreHandle);{flag, write out changes on Quit} end;{Name:} SetSpeed:Begin Repeat ModalDialog(Nil,itemHit); Case itemHit of 2,3,4: SetControlValue(itemHit);{our radio controls} end;{case itemhit} Until(itemHit = 1); end;{SetSpeed} end;{Case WhichDialog} HideWindow(myDialog[WhichDialog]); SelectWindow(myWindow);{restore our game window} SetPort(tPort);{restore port} end; procedure GetIndRect(Var theRect:Rect;rectListID:integer;index:integer); {similar to GetIndString RomCall for use with 'nrct' resource type} Begin {make sure we've got a copy of resource in memory} RListHand := nrctHandle(GetResource('nrct',rectListID)); If (index > RListHand^^.num) or (RListHand = Nil) {watch out for errors} then SetRect(theRect,0,0,0,0) else theRect := RListHand^^.theRects[index];{copy the rect...} End; procedure CreateStrings;{messages and hints} Begin For i := 1 to 4 do begin GetIndString(Clues[i].message,1000,i);{4 messages} GetIndRect(Clues[i].messRect,1000,i);{box for textbox messages} GetIndRect(Clues[i].hintRect,1001,i);{box to contain hints} end; For i := 1 to 10 do GetIndString(ClueWords[i],1001,i);{10 cluewords} Clues[1].just := teJustRight;{justification for hint rect... next to glyph} Clues[2].just := teJustCenter; Clues[3].just := teJustLeft; Clues[4].just := teJustCenter; end; procedure NextTrgtSound(Trgt:integer);{we'll use our snake stuff to play sound} Begin SnakeSound^.sound1Rate := StartPitch[1] * Trgt;{*2 is one octave} SnakeSound^.sound2Rate := StartPitch[2] * Trgt; SnakeSound^.sound3Rate := StartPitch[3] * Trgt; SnakeSound^.sound4Rate := StartPitch[4] * Trgt; SnakeSound^.duration := 120;{so tone will keep playing} end; procedure WriteASoundBlock(aSoundPtr:Ptr;BuffSize:Longint;LastTick:Longint); {make changes to our SoundParmBlk and then start the sound,the lasttick pause is included due to occassional buzz/click/or worse if sound is written immediately.. might be because Sound Driver set ioResult to zero before the other soundstuff was complete.. and we happened to peek at it at just the wrong moment} Begin SoundParmBlk^.iobuffer := aSoundPtr;{set pointer to our sound} SoundParmBlk^.ioreqcount := BuffSize;{size of our sound buffer} Repeat Until(TickCount > LastTick);{we'll wait a tick before writing sound} err := PBWrite(SoundParmBlk,true);{start the sound, asynch} end; procedure SetSnakeSoundStuff;{for whichsound} Begin Case WhichSound of {our 2 voice FourTone sounds} SnakeCharmer:Begin SnakeSound^.sound1Rate := mySongPtr^.pitch[NoteCount,1]; SnakeSound^.sound2Rate := mySongPtr^.pitch[NoteCount,2]; ToneDelay := mySongPtr^.duration[NoteCount] + TickCount; end; Walk:Begin SnakeSound^.sound1Rate := myWSongPtr^.pitch[NoteCount,1]; SnakeSound^.sound2Rate := myWSongPtr^.pitch[NoteCount,2]; ToneDelay := myWSongPtr^.duration[NoteCount] + TickCount; end; end;{case whichsound} SnakeSound^.duration := 80;{none of our notes is over 60 ticks long} end; procedure InitialSnakeSong(BeginNote:integer);{use for 'Walk' also, just use myWSongPtr stuff} Begin NoteCount := BeginNote;{set to first note in our song array} SetSnakeSoundStuff; SnakeSound^.sound3Rate := 0;{no 3 and 4 voices sound} SnakeSound^.sound4Rate := 0; If SoundOn then WriteASoundBlock(ptr(Sound[SnakeCharmer]), SizeOf(Sound[SnakeCharmer]^),TickCount); End;{of Procedure} procedure CreateSound;{i,j,k = global} {we're writing direct to the sound driver with PBWrite...this procedure sets up all the various buffers, Parm blocks, and such for the different types of sounds used in this game. You can determine if a sound is finished by checking the ioresult field of the Parmblock, if it is less than 1 then the sound is done. In the mainloop we check it and start another sound if the last one is done. Sometimes the Driver has changed ioresult but is not done with the ParmBlock so writing another sound can be messed up.. buzzing, this can be avoided by waiting for Tickcount to increment once,or by doing a PBKillIO. We use the PBKill when we want to switch sounds before they are complete... this avoids the problem of our program trying to write to the Driver buffers at the same time the driver is trying to access them. To avoid system errors always be sure to Kill sound I/O before exiting the program!!.. and remember freeform sound slows the program by about 20%, fourTone sound by 50%.} Begin myHandle := (GetResource('SONG',522));{snake charmer notes} {HLock(myHandle);locked in resource file} mySongPtr := SongPtr(myHandle^); myHandle := (GetResource('SONG',523));{'Walk like' notes} {HLock(myHandle);locked in resource file} myWSongPtr := WSongPtr(myHandle^); new(SawToothWave); {wavePtr...a SawToothWave form for Blue sound} new(SquareWave); {wavePtr...a squarewave form for Blue sound} for i := 0 to 127 do {build our four tone waves} begin SawToothWave^[i] := 2*i;{sort of a raspy sound....} {SawToothWave^[i+128] := 0;} SquareWave^[i] := 0; SquareWave^[i+128] := 255;{height of Square determines Loudness} end; {we'll coerce Snake to FreeForm pointer throughout the program just so we can use WhichSound and our SoundList type later on....} new(SnakeSynth); {my FTSynthPtr, FourTone Synthesizer} SnakeSynth^.mode := ftMode; new(SnakeSound);{our fourtone sound} SnakeSound^.sound1Phase := 0; SnakeSound^.sound2Phase := 128; SnakeSound^.sound3Phase := 128; {out of phase just for fun} SnakeSound^.sound4Phase := 0; SnakeSound^.sound1Wave := SawToothWave; SnakeSound^.sound2Wave := SquareWave; SnakeSound^.sound3Wave := SawToothWave; SnakeSound^.sound4Wave := SquareWave; {must make sndRec point to our Sound} SnakeSynth^.sndRec := SnakeSound; Sound[SnakeCharmer] := FFSynthPtr(SnakeSynth);{its just a pointer.. so coerce it} {note: We're using Snake FourTone stuff also for both Walk and Trgt sounds} Buff[GunShot] := 376;{size of all the freeform buffers,keep multiples of 370} Buff[GoodHit] := 1486;{!!! remember to set range below} Buff[BadHit] := 746; Buff[Miss] := 746; Buff[BonusSnd] := 746; {now create all the FreeForm sounds....} For WhichSound := GunShot to BonusSnd do begin {they all have these in common} Sound[WhichSound] := FFSynthPtr(NewPtr(Buff[WhichSound]));{sound block} Sound[WhichSound]^.mode := ffMode; {knock off 6 bytes for mode & count.. plus 1 for switch to Zero base} Buff[WhichSound] := Buff[WhichSound] - 7;{size of waveBytes} end; {GunShot Stuff, played when a shot is fired, mousedown} Sound[GunShot]^.count := FixRatio(1,6);{see the Sound Driver stuff} j := 0; While j<= Buff[GunShot] do Begin If j > 30 then i := abs(Random) div (j*3) {just fading noise} else begin If (j mod 5) = 0 then i := 255;{SquareWave with Period of 10} If (j mod 10) = 0 then i := 0; end;{else} Sound[GunShot]^.WaveBytes[j] := i; {fill up the buffer} inc(j); end; { of while} {GoodHit Stuff, sound played if valid target hit} {set .count field by target value} j := 0; While j<= Buff[GoodHit] do Begin If j > 370 then begin If (j mod 64) = 0 then i := 255 - (j div 12); If (j mod 128) = 0 then i := (j div 12); end else begin If (j mod 64) = 0 then If (j mod 128) = 0 then i := 0 else i := 255; end;{else} Sound[GoodHit]^.WaveBytes[j] := i; {fill up the buffer} inc(j); end; { of while} {BadHit Stuff, hit a target but not a valid one} Sound[BadHit]^.count := FixRatio(1,4); j := 0; While j<= Buff[BadHit] do Begin if i < 127 then inc(i) else i := 0;{sawtooth} Sound[BadHit]^.WaveBytes[j] := i; {fill up the buffer} inc(j); end; { of while} {Miss Stuff, missed everything! splat} Sound[Miss]^.count := FixRatio(1,2);{see the Sound Driver stuff} j := 0; i := 0; While j<= Buff[Miss] do Begin if i < 255 then inc(i) else i := 0; Sound[Miss]^.WaveBytes[j] := i; inc(j); end; { of while} {BonusSnd Stuff, sound played if bonus target underway} Sound[BonusSnd]^.count := FixRatio(1,4); j := 0; While j<= Buff[BonusSnd] do Begin If (j mod 37) = 0 then If (j mod 74) = 0 then i := 0 else i := 63; Sound[BonusSnd]^.WaveBytes[j] := i; {fill up the buffer} inc(j); end; { of while} {create our Sound Parameter block for use with PBWrite} new(SoundParmBlk);{create record the Ptr will point to!} with SoundParmBlk^ do begin {see Apple tech note 19, PBWrite vs. StartSound} iocompletion := nil; iorefnum := -4; {we'll poke in the pointers and size stuff in WriteASoundBlock} ioresult := 0; {will Start sound ,MainEventLoop} end; {of with} end; procedure PauseThisGame(index:integer); {called if a keydown during game} var i: integer; Begin err := PBKillIO(SoundParmBlk,false);{kill sound} PauseStatus := index;{determines update drawing} {copy current screen to UpDateMap, so update events are redrawn correctly} CopyBits(myWindow^.portBits,UpDateMap,UpDateRect, UpDateMap.bounds,srcCopy,nil); ShowControl(ResumeButton); ShowControl(EndButton); SetCursorMenus(false); End; procedure NewHiScore;{check for new hiscore/update HiScore??} Begin If Score > HiScore then begin HiScore := Score; DrawStatus(4,HiScore); end; End; procedure MoveHourGlass(Horz,Vert:integer);{move for timescore after each level} Begin OffSetRect(Hour,Horz,Vert); OffSetRect(TopSand,Horz,Vert); OffSetRgn(TopSandRgn,Horz,Vert); OffSetRect(BotSand,Horz,Vert); OffSetRgn(BotSandRgn,Horz,Vert); TrickleTop.h := TrickleTop.h + Horz; TrickleBot.h := TrickleTop.h; TrickTopR.h := TrickleTop.h + 1;{right side line trickle} TrickBotR.h := TrickTopR.h; TrickleTop.v := BotSand.top; TrickleBot.v :=TrickleBot.v + Vert; TrickBotR.v := TrickleBot.v; end; procedure BuildValidNow; {list of Valids which have targets remaining, we step thru NowValid to find the next valid target to display in StartOne(), so we have a good mix of all the remaining valid targets in the windows, for instance if we have 6 valid targets numbered 1..6 but the user has completed numbers 2,5 and 6 (they have no targets remaining) then after this procedure NowValid will contain the following... 1,3,4,0,0,0 NowNdx is inc'd until NowValid = 0 then its reset to 1} Var i:integer; Begin For i := 1 to 6 do NowValid[i] := 0;{initialize all to zero} NowNdx := 1; For i := 1 to LastValid do If Valid[i].remain > 0 then begin {has targets remaining so Valid} NowValid[NowNdx] := i; inc(NowNdx);{inc to point to next now} end; NowNdx := 1;{reset to first in list} End; procedure BuildValidTargets; {create list of targets for scorebox & initial them} Var i,j:integer; Begin randSeed := TickCount;{longint seed Random Function} {our font bitimage has shapes for chars 33 thru 129} Valid[1].which := trunc(abs(Random) / MaxInteger * 96) + 33;{pick one} For i := 2 to LastValid do begin Repeat Valid[i].which := trunc(abs(Random) / MaxInteger * 96) + 33;{33 to 129} tFlag := True; For j := 1 to i-1 do {check against ones already picked} If (Valid[i].which = Valid[j].which) then tFlag := False; Until(tFlag = True); end;{for i} If LastValid < 6 then begin {create #of each target to be hit} For i := 1 to LastValid do Valid[i].remain := LastValid+1-i;{4,3,2,1} end else For i := 1 to LastValid do Valid[i].remain := Level+4-i; BuildValidNow; End; procedure CreatePictures; {get PICT's from resource file} Begin {note: we've either created our pics in FullPaint or by doing 'openPicture' type stuff. In the case of FullPaint we 'copy' the picture from a document where the pic is located in same position relative to upperleft of page as it will be relative to upperleft of window origin.. this way when we draw the picture in its picFrame it appears in proper place in window, to move a picture.. just set a rect = to picframe and OffsetRect it, then draw the picture into the rect instead of the picframe} LeftSide := GetPicture(138); RightSide := GetPicture(139);{of gallery walls} BackSide := GetPicture(140); ScoreFrame := GetPicture(142); ScoreFrameRect := ScoreFrame^^.picFrame; ScoreNum := GetPicture(141); ScoreNumRect := ScoreNum^^.picFrame; OffSetRect(ScoreFrameRect,8,3); OffSetRect(ScoreNumRect,ScoreFrameRect.left +2-ScoreNumRect.left, ScoreFrameRect.top + 4-ScoreNumRect.top); StatPic := GetPicture(145);{status display box} {locate all the stuff for drawing our scores, etc. into Status} StatList[1].centerPt := StatPic^^.picFrame.topleft; StatList[1].centerPt.h := StatList[1].centerPt.h + 44; StatList[1].centerPt.v := StatList[1].centerPt.v + 26; StatList[2].centerPt.h := StatList[1].centerPt.h + 84; StatList[2].centerPt.v := StatList[1].centerPt.v; StatList[3].centerPt.h := StatList[1].centerPt.h; StatList[3].centerPt.v := StatList[1].centerPt.v + 27; StatList[4].centerPt.h := StatList[2].centerPt.h; StatList[4].centerPt.v := StatList[3].centerPt.v; For i := 1 to 4 do SetRect(StatList[i].blankRect, StatList[i].centerPt.h - 38,StatList[i].centerPt.v - 13, StatList[i].centerPt.h + 38,StatList[i].centerPt.v); For i := 1 to 4 do Clues[i].glyphPic := GetPicture(160 + i);{load glyphs} ClueShadow := GetPicture(165); For i := 168 to 171 do Tut[i] := GetPicture(i);{load tut pics} TarWindPic := GetPicture(166); BotSandRgn := RgnHandle(GetResource('RGN ',356));{mask sand into glass} TopSandRgn := RgnHandle(GetResource('RGN ',357)); {these rgns are a tracing of the outline of the top and bottom half of the Hourglass. We wrote a little utility that can import a picture, trace its outline into a RgnHandle then save the new rgn into our 'RGN ' resource, the rgn resource can then be used to mask drawing done by CopyBits().. both our GunSite and the sand in the hourGlass are examples of this. Some truly stunning effects can be created with Rgn masks and CopyBits()!} end; {bmap Type resources contain a Bitmap 'and' bitimage. we created our bmaps by using PtrAndHand to stick our bitimage (already containing pictures shapes,etc.) onto the end of its bitmap and store as bmap... Getbmap() loads the resource, locks it down, and makes 'theMap' point to the BitImage. In some cases there is no bitimage (empty) so we create a ptr in the conventional way} procedure Getbmap(Var theMap:Bitmap;resID:integer); var myBmap:BmapHand; Begin myBMap := BmapHand(GetResource('bmap',resID)); {HLock(Handle(myBMap));locked in resource file} theMap.rowBytes := myBMap^^.rowBytes; theMap.bounds := myBMap^^.bounds; theMap.baseAddr := Ptr(@myBMap^^.bitStart[1]);{point to bitimage} End; procedure CreateOffScreenBitMap; {see CopyBits stuff,also tech.note 41} Begin {offScreen contains Gun/explode/spot/hourglass/sand and chariot pics} Getbmap(OffScreen,128);{bmaps come with complete BitImages!} Getbmap(BonusMap,129); Getbmap(UpDateMap,131); {we saved no bitimage for UpDateMap, so create it} UpDateMap.baseAddr := QDPtr(NewPtr((UpdateMap.bounds.bottom- UpdateMap.bounds.top)*UpdateMap.rowBytes)); For i := 1 to 3 do begin GetbMap(Wings[i],131+i); Wings[i].baseAddr := QDPtr(NewPtr((Wings[i].bounds.bottom- Wings[i].bounds.top)*Wings[i].rowBytes)); end; End; procedure CreateCairoStuff; {we're only using bitimage and locate table of Cairo font, to access the 'parts' of the Font we did some pointer math and made copies of only bitImage and locate table, then saved the new records in our 'MYCF' resource, the bitImage contains shapes for chars 33 thru 129.. see CairoFont.pro file for more information on how we accessed the Cairo Font.} var trect,bRect:Rect; i:integer; Begin {load font Character image and locate table} myHandle := GetResource('MYCF',128); {HLock(myHandle);locked in resource} myCFPtr := CFPtr(myHandle^);{lock it down, used as ptr block} FontMap.baseAddr := QDPtr(myCFPtr);{make FontMap point to bit image} FontMap.rowbytes := 210;{row width in bytes, must be even number} SetRect(FontMap.bounds,0,0,1656,24);{looked at with ResEdit}; End; procedure InitialHourGlass;{position hourglass stuff for start of game} Begin OffSetRect(OBSand,0,OffBotSand.top-OBSand.top);{at top, will move down} OffSetRect(OTSand,0,OffTopSand.bottom-OTSand.bottom);{at bottom, will move up} TrickleBot.v := BotSand.bottom-1;{trickle to top of pile} End; procedure SizeLocateSpots(offset:integer); Begin For i := 5 to 6 do begin OffSpot[i].bottom := OffSpot[i].bottom - (5 + offset); SpotRect[i] := OffSpot[i];{size SpotRect} end; OffSetRect(OffSpot[5],0,OffSpotTarget.top+offset-OffSpot[5].top); OffSetRect(OffSpot[6],0,OffSpotTarget.bottom-offset-OffSpot[6].bottom); End; procedure InitialSpotTargets; Var i:integer; Begin {locate and size all the Spot target rectangles, we're doing some shifting/sizing of OffSpots to minimize the work of CopyBits()} For i := 5 to 6 do OffSpot[i] := OffSpotTarget; Case SpotSpeed[5] of 2: SizeLocateSpots(2); 4: SizeLocateSpots(0); End;{Case Spotspeed} OffSetRect(SpotRect[5],TarWindow[5].left+7-SpotRect[5].left, TarWindow[5].top-SpotRect[5].top);{top moving down} OffSetRect(SpotRect[6],TarWindow[6].left+7-SpotRect[6].left, TarWindow[6].bottom-SpotRect[6].bottom);{bottom moving up} End; procedure OneTimeGameStuff; Begin {when we created the bmaps we saved the rects we'd already calculated in a 'nrct' resource so we wouldn't have to rewrite the code} GetIndRect(OffGun[0],2000,1);{Rects for shapes in OffScreen bmap} GunRect := OffGun[0];{size} GetIndRect(OffGun[1],2000,2); GetIndRect(OffExplode,2000,3); ExplodeRect := OffExplode; GetIndRect(OffSpotTarget,2000,4); GetIndRect(OffHour,2000,5); GetIndRect(OffBotSand,2000,6); GetIndRect(OffTopSand,2000,7); {find all individual bonus shapes in the map... 6 in a row, 2 rows} with BonusMap.bounds do SetRect(tRect,left,top,left+37,top+23);{size of one} For i := 1 to 6 do begin OffBonus[i,1] := tRect;{top row} OffSetRect(tRect,0,23); OffBonus[i,2] := tRect;{bottom row} OffSetRect(tRect,37,-23); end; BonusRect := OffBonus[1,1];{size BonusRect} GetIndPattern(myPattern,sysPatListID,25);{see i-473} GetIndPattern(myDiagPat,sysPatListID,26);{used to disable valid} For i := 1 to 3 do begin {size 3 scrolling windows} SetRect(TarWindow[i],125,39 + 36*i,125+251,66 + 36*i);{font - 1} OffPiece[i] := TarWindow[i];{establish Height of OffPiece} Piece[i] := TarWindow[i];{establish Height of Piece} OffSetRect(OffPiece[i],0,1-OffPiece[i].top);{ready for startone} end; SetRect(SiteRect,0,0,25,25);{circular site in synch with gun} For i := 0 to 1 do begin OffSite[i] := SiteRect;{two offSites for two offguns} OffSetRect(OffSite[i],OffGun[i].left+ 4 -OffSite[i].left, OffGun[i].top+ 13-OffSite[i].top); SiteRgn[i] := NewRgn;{create the site regions for use with CopyBits} OpenRgn; FrameOval(OffSite[i]);{used to mask background drawn into Offsite} CloseRgn(SiteRgn[i]); end; SetRect(TarWindow[5],0,0,37,105);{top is even,bottom odd due to pattern} OffSetRect(TarWindow[5],TarWindow[1].left-TarWindow[5].right-10, TarWindow[1].top-TarWindow[5].top-5);{locate to left of others} TarWindow[6] := TarWindow[5];{6 is exactly like 5,except on right side} OffSetRect(TarWindow[6],TarWindow[1].right-TarWindow[5].left+10,0);{right} For i := 5 to 6 do begin TopSpot[i] := TarWindow[i].top + 4;{used in SpotStatus for speed} BotSpot[i] := TarWindow[i].bottom - 4; end; {locate and size #4/bottom window.. bonus targets} TarWindow[4].top := TarWindow[3].top + 38; TarWindow[4].bottom := TarWindow[3].bottom + 38; TarWindow[4].left := TarWindow[5].left +1; TarWindow[4].right := TarWindow[6].right -1; OffSetRect(BonusRect,TarWindow[4].left-BonusRect.right, TarWindow[4].top-BonusRect.top); TarWindRgn := NewRgn;{region - frame of bonus target window} OpenRgn; FrameRect(TarWindow[4]);{will mask the bonuspics} CloseRgn(TarWindRgn); Hour := OffHour;{hourglass stuff} OffSetRect(Hour,280-Hour.left,3-Hour.top);{position in scorebox} TopSand := TopSandRgn^^.rgnBBox;{size of rgn or of sand pic????} OTSand := TopSand; OffSetRect(TopSand,Hour.left-TopSand.left,Hour.top+4-TopSand.top); OffSetRect(TopSand,(Hour.right-TopSand.right) div 2,0);{center it} OffSetRgn(TopSandRgn,TopSand.left -TopSandRgn^^.rgnBBox.left, TopSand.top-TopSandRgn^^.rgnBBox.top); BotSand := BotSandRgn^^.rgnBBox;{size of rgn or of sand pic????} OBSand := BotSand; OffSetRect(BotSand,Hour.left -BotSand.left,Hour.bottom-4-BotSand.bottom); OffSetRect(BotSand,(Hour.right-BotSand.right) div 2,0);{center it} OffSetRgn(BotSandRgn,BotSand.left -BotSandRgn^^.rgnBBox.left, BotSand.bottom-BotSandRgn^^.rgnBBox.bottom); OffSetRect(OBSand,OffBotSand.left-OBSand.left, OffBotSand.bottom-OBSand.bottom); OffSetRect(OTSand,OffTopSand.left-OTSand.left, OffTopSand.top-OTSand.top); TrickleTop.h := Hour.left + ((Hour.right-Hour.left)div 2)-1; TrickleBot.h := TrickleTop.h; TrickTopR.h := TrickleTop.h + 1;{right side line trickle} TrickBotR.h := TrickTopR.h; TrickleTop.v := BotSand.top; {all the Score box stuff} tPt.h := ScoreFrameRect.left + 2 + 22;{center of first box} tPt.v := ScoreFrameRect.top + 16;{to top of draw area} j := ScoreFrameRect.bottom - 5;{to bottom of blank area} For i := 1 to 6 do begin Valid[i].centerPt := tPt; Valid[i].remainPt.h := tPt.h; Valid[i].remainPt.v := j; SetRect(Valid[i].blankRect,tPt.h-20,tPt.v +28,tPt.h+20,j); tPt.h := tPt.h + 44; end;{for i} Valid[6].points := 500;{scoring for our valid targets} Valid[5].points := 100; Valid[4].points := 50; Valid[3].points := 20; Valid[2].points := 10; Valid[1].points := 5; {set up all the Timebonus stuff.. TBone topleft := 179,85} SetRect(TBone.frame,179,85,328,154); SetRect(TBone.numBox,267,110,319,130); SetPt(TBone.hourMove,-93,88); SetRect(TBone.arrowSource,myCFPtr^.locate[104],FontMap.bounds.top, myCFPtr^.locate[105],FontMap.bounds.bottom); TBone.arrowDest := TBone.arrowSource; OffsetRect(TBone.arrowDest,234-TBone.arrowDest.left, 108-TBone.arrowDest.top); StartPitch[1] := 29316;{Long integers for notes,played when targets} StartPitch[2] := 78264;{are displayed,doubling long integers is an octive} StartPitch[3] := 98607; StartPitch[4] := 117264; {locate gun and site} OffSetRect(GunRect,100-GunRect.left, 300-GunRect.bottom); OffSetRect(SiteRect,GunRect.left+ 4-SiteRect.left, GunRect.top+ 13-SiteRect.top);{synch Site with gun} SetRect(MouseRect,LeftSide^^.picFrame.right,ScoreFrameRect.bottom, RightSide^^.picFrame.left - 32,GunRect.top - 32); GunLimit := MouseRect;{GunLimit is fixed... MouseRect will slide with mouse} SetRect(ShadowRect,LeftSide^^.picFrame.left,ScoreFrameRect.bottom+3, RightSide^^.picFrame.right,297); {shadow rect extends down to edge of front counter in booth} RedrawRect := ShadowRect; RedrawRect.bottom := GunRect.bottom;{redraw extends farther,to bottom of gun} SetRect(ClueErase,LeftSide^^.picFrame.right+1,64, RightSide^^.picFrame.left-1,219);{erase tarwindows for Clues} UpDateRect := ClueErase;{update area to redraw windows from CopyBits copy} {our UpDateMap.bounds is equal to UpDateRect} FetchChar := FontMap.bounds;{Used to speed up StartOne, top/bottom coords.} For i := 1 to 4 do {get handle to text items in clue dialog} GetDItem(myDialog[ClueDial],5 + i,j,Clues[i].dialHand,tRect); RandSeed := TickCount; ClueNdx := (abs(Random) div 3277) + 1;{ndx to cluewords,1-10} tRgn := NewRgn;{used tRgn in the ScrollRect call} ShotFired := False;{flag a shot fired... mousedown} NameString := ''; GameUnderWay := False;{game is not under way on start up} PauseStatus := 0;{no pause} HiScore := theHighs^.score[4];{lowest of the hiscores} Score := 0; Level := 0; MissCount := 0;{number of misses} CursorNdx := 1;{point to first of three cursors} LastValid := 6;{we'll draw 6 targets on start up} Level := 4; {for BuildValidTargets to work} BuildValidTargets;{so that update will work correctly} End; procedure StaggerOne(Which:integer); {fill wings with pattern and OffSet, so our targets aren't lined up vertically} Begin OldBits := myWindow^.portBits; {preserve old BitMap} SetPortBits(Wings[Which]); { our new BitMap } FillRect(Wings[Which].bounds,myPattern);{fill with horz.lines} si := abs(Random div 1368) + 10;{10 to 33,random offset for start} If Direction[Which] > 0 then begin OffPiece[Which].right := Wings[Which].bounds.right - si; OffPiece[Which].left := OffPiece[Which].right - Speed[Which]; end Else begin OffPiece[Which].left := si; OffPiece[Which].right := si + Speed[Which]; end; SetPortBits(OldBits); {restore old bitmap} end; procedure StartOne(Which:integer); {which TarWindow[1..3]} {get a char from either valid targets or not-valid targets and draw it into a Wings Bitmap and initial stuff for scrolling it onto the screen via OffPiece/Piece and CopyBits()} Begin OldBits := myWindow^.portBits; {preserve old BitMap} SetPortBits(Wings[Which]); { our new BitMap } FillRect(Wings[Which].bounds,myPattern);{erase previous with horiz.line patt} {FetchChar := FontMap.bounds;}{bounds rect of FontMap bitmap} {find which char to draw... one from Valid targets or not} If ((abs(Random) div 3277) > 5) and not(ValidDone) then begin {get a Valid.which} si := Valid[NowValid[NowNdx]].which;{NowValid points to Valid with remain} If NowNdx < 6 then begin inc(NowNdx); If NowValid[NowNdx] = 0 then NowNdx := 1;{reset to first} end{if NowNdx} else NowNdx := 1;{6 is last so reset to first} end {if ((abs...} Else {put up one that's not on our list} Repeat {find i not in our Valid list} si := trunc(abs(Random) / MaxInteger * 96) + 33;{si := 33 to 129} tFlag := True; For sj := 1 to LastValid do If (si = Valid[sj].which) then tFlag := False;{in our list} Until(tFlag = True); FetchChar.left := myCFPtr^.locate[si];{locate[i] is offset in Bitimage} FetchChar.right := myCFPtr^.locate[si+1];{FetchChar frames Char image in Fontmap} dRect := FetchChar; {dRect.top := 0;}{FetchChar.top is already 0} dRect.bottom := 29; {expand dRect make white border/frame around target} If Direction[Which] > 0 then begin {offset to rightside} {resize dRect to frame target and offset to proper side} dRect.left := 42 - (FetchChar.right - FetchChar.left);{grow by 8 pixels} dRect.right := 50;{Wings[Which].bounds.right} OffPiece[Which].right := Wings[Which].bounds.right; {'Speed' := width of OffPiece/Piece and vice versa} OffPiece[Which].left := OffPiece[Which].right - Speed[Which]; end Else begin {offset to leftside} dRect.left := 0; dRect.right := 8 + FetchChar.right - FetchChar.left; OffPiece[Which].left := 0; OffPiece[Which].right := Speed[Which];{size based on speed} end; EraseRect(dRect);{to erase pattern...inside target area} FrameRect(dRect);{frame around target} {TarList stuff...install this new target in TarList for this window} TarList[Which,NxtTar[Which]].theChar := si; TarList[Which,NxtTar[Which]].head := PixOff[Which] - Speed[Which]; TarList[Which,NxtTar[Which]].wide := dRect.right - dRect.left; If NxtTar[Which] < LastTarget then inc(NxtTar[Which]) Else NxtTar[Which] := 1;{index of TarList} {now shrink relocated dRect back to size of FetchChar before copybits} dRect.top := dRect.top + 3; dRect.bottom := dRect.bottom - 2; dRect.left := dRect.left + 4; dRect.right := dRect.right - 4; CopyBits(FontMap,Wings[Which],FetchChar,dRect,srcCopy,nil);{char into target} SetPortBits(OldBits); {restore old bitmap} end; procedure InitialBonus;{set up bonus for drawing..movement} Begin BonusUnderWay := True; BonusRect.left := TarWindow[4].left - (BonusRect.right-BonusRect.left); BonusRect.right := TarWindow[4].left;{move BonusRect to left side window} BonusSpeed := 4;{max right, pixels per loop} BonusLoops := 20;{20 * 4 = 80 pixels....} WhichSound := BonusSnd;{will start the BonusSnd} End;{of procedure} Procedure InitialAnimation; Var i,j:integer; myErase:Rect; Begin BuildValidTargets;{pick out Cairo chars for targets,etc.} {draw the scorebox} EraseRect(ScoreFrameRect);{erase old} DrawPicture(ScoreFrame,ScoreFrameRect); DrawPicture(ScoreNum,ScoreNumRect); If SoundOn then begin NextTrgtSound(1);{initialize pitches for sound during drawing} WriteASoundBlock(ptr(Sound[SnakeCharmer]), SizeOf(Sound[SnakeCharmer]^),TickCount);{use snake for target} end;{if soundon} {draw the targets into the scoreBox, for each level} SpeedTicks := 1;{for our delay} sRect := FontMap.bounds;{bounds rect of FontMap bitmap,top/bottom constant} For i := 1 to LastValid do begin NextTrgtSound(i);{change pitch for each box} with Valid[i].centerPt do SetRect(myErase,h-21,v,h+21,v+24);{erase area} tRect.top := Valid[i].centerPt.v; tRect.bottom := tRect.top + 24; For j := 32 to Valid[i].which do begin SystemTask; If SpeedTrapOn then Delay(SpeedTicks,aTick);{slow down for MacII's} SizeRectsForBox(j,i);{sRect and tRect} EraseRect(myErase);{erase the previous} CopyBits(FontMap,myWindow^.portBits,sRect,tRect,srcCopy,nil); end;{for j := 32} DrawRemainIntoScoreBox(i);{# remaining, draw just below target} end; err := PBKillIO(SoundParmBlk,false);{kill sound} randSeed := TickCount; For i := 1 to 3 do begin {initilize for each of scrolling windows} For j := 1 to LastTarget do TarList[i,j].theChar := 0;{no targets} NxtTar[i] := 1;{point to first target} PixOff[i] := -32766;{will determine offsets to targets in list} If odd(Random) then Direction[i] := 1 else Direction[i] := -1; Vector[i] := Direction[i] * Speed[i];{used to speed up animateoneloop} {locate Piece} If Direction[i] > 0 then begin {moving right, Piece is on left side} Piece[i].left := TarWindow[i].left; Piece[i].right := Piece[i].left + Speed[i]; end else begin Piece[i].right := TarWindow[i].right;{Piece is on right side} Piece[i].left := Piece[i].right - Speed[i]; end; end; StartOne(1);{start a target in top window} StaggerOne(2);{stagger wings, so targets out of synch} StaggerOne(3); Counter := 1;{number of loops before sand in hourglass moves} InitialSpotTargets; ValidDone := False; MissCount := 0; DrawStatus(3,MissCount); BonusNdx := 0;{will be inc'd by first bonus to point to first} BonusShape := 1;{point to first shape} If SoundOn then begin {start the Walk song just before start of level} WhichSound := Walk; InitialSnakeSong(57);{start the walk song...just the last bit...} Repeat SystemTask; If (TickCount > ToneDelay) then begin {start another note} inc(NoteCount); SetSnakeSoundStuff;{get next sound,etc.} end; {if TickCount} Until(NoteCount = myWSongPtr^.noteCount);{our last walk note} err := PBKillIO(SoundParmBlk,false); WhichSound := Silence; end;{if soundOn} InitialHourGlass;{position and draw hourglass} TrickleLeft := True; CopyBits(OffScreen,myWindow^.portBits,OffHour, Hour,srcCopy,nil); CopyBits(OffScreen,myWindow^.portBits,OTSand, TopSand,srcCopy,TopSandRgn); end; procedure BeginTheGame; Begin SetCursorMenus(True);{do all the cursor and menus stuff} DisableItem(myMenus[2],1);{disable 'begin' until end of game} ClipRect(RedrawRect);{so drawing will only affect RedrawREct} EraseRect(RedrawRect);{erase the shadow in booth} DrawWindowContents(myWindow);{redraw shaded area (gameunderWay)} ClipRect(myWindow^.portRect);{reset clipRgn to whole window} For i := 1 to 4 do begin {list of clues for this game} Clues[i].hint := ClueWords[ClueNdx]; If ClueNdx < 10 then inc(ClueNdx) else ClueNdx := 1; end; Score := 0; DrawStatus(2,Score); DrawStatus(4,HiScore); Level := 1; DrawStatus(1,Level); LastValid := 4;{only 4 valid targets for now} For i := 1 to 3 do Speed[i] := 1;{start out at slowest speed, 1 pix/loop} SpotSpeed[5] := 2;{start at top moving down} SpotSpeed[6] := -2;{start at bottom moving up} TimeCount := 45;{# of loops for each move of hourglass} InitialAnimation; BonusUnderWay := False; end; procedure DetectAHit;{search targets for a hit} Begin Hit := False; For dj := 1 to 6 do begin {check all 6 TarWindows} If PtInRect(TargetCenter,TarWindow[dj]) then {is hit in the Window Rect?} Case dj of 1,2,3:{check the three scrolling windows} Case Direction[dj] of 1: begin {scrolling right} dk := TarWindow[dj].left + PixOff[dj];{locate head constant} di := LastTarget;{search TarList in reverse} Repeat If TarList[dj,di].theChar <> 0 then If TargetCenter.h < (dk - TarList[dj,di].head) then if TargetCenter.h > (dk - TarList[dj,di].head - TarList[dj,di].wide) then begin Hit := True; HitWhich := di;{which target in List is hit} HitWindow := dj;{which window} end; dec(di); Until((di = 0) or (Hit = True)); end;{case 1:} -1: begin {scrolling left} dk := TarWindow[dj].right - PixOff[dj];{Locate head constant} di := LastTarget; Repeat If TarList[dj,di].theChar <> 0 then If TargetCenter.h > (dk + TarList[dj,di].head) then if TargetCenter.h < (dk + TarList[dj,di].head + TarList[dj,di].wide) then begin Hit := True; HitWhich := di;{which target in List is hit} HitWindow := dj;{which window} end; dec(di); Until((di = 0) or (Hit = True)); end;{Case -1:} end;{Case Direction} 4: If BonusUnderWay then begin {this is bonus window} tRect := BonusRect; tRect.left := tRect.left + 4; tRect.right := tRect.right - 4;{shrink the rect border for hittest} If PtInRect(TargetCenter,tRect) then begin Hit := True; HitWindow := dj; WhichSound := Silence;{kill bonus sound} end;{if PtInRect} end;{ If BonusUnderWay } 5,6: begin tRect := SpotRect[dj]; tRect.top := tRect.top + abs(SpotSpeed[dj]); tRect.right := tRect.right - abs(SpotSpeed[dj]);{shrink the rect for hittest} If PtInRect(TargetCenter,tRect) then begin Hit := True; HitWindow := dj; end;{if PtInRect} end; {these are spot windows} end;{Case dj} end;{for dj:= 1} If not(Hit) then begin {hit will be true if a hit was detected} If SoundOn then begin {play miss sound} err := PBKillIO(SoundParmBlk,false);{kill current sound} WriteASoundBlock(ptr(Sound[Miss]),Buff[Miss],TickCount); end;{if soundOn} inc(MissCount); DrawStatus(3,MissCount);{draw in new missCount} end;{if not(Hit)} End; procedure EraseTargetInTarWindow(Window,Index:integer); {remove a target from a window... watch out for targets on window borders} Begin {erase the target in window} tRect.top := TarWindow[Window].top; tRect.bottom := TarWindow[Window].bottom; If Direction[Window] > 0 then begin{moving right} tRect.right := TarWindow[Window].left + PixOff[Window] - TarList[Window,Index].head; tRect.left := tRect.right - TarList[Window,Index].wide; {in case the target is not fully in the window yet} If tRect.left < TarWindow[Window].left then begin OldBits := myWindow^.portBits; {preserve old BitMap} SetPortBits(Wings[Window]); { our new BitMap } FillRect(Wings[Window].bounds,myPattern); SetPortBits(OldBits); {restore old bitmap} tRect.left := TarWindow[Window].left;{limit to left} end{if tRect.left} else If tRect.right > TarWindow[Window].right then tRect.right := TarWindow[Window].right; end Else begin tRect.left := TarWindow[Window].right - (PixOff[Window] - TarList[Window,Index].head); tRect.right := tRect.left + TarList[Window,Index].wide; {in case the target is not fully in the window yet} If tRect.right > TarWindow[Window].right then begin OldBits := myWindow^.portBits; {preserve old BitMap} SetPortBits(Wings[Window]); { our new BitMap } FillRect(Wings[Window].bounds,myPattern); SetPortBits(OldBits); {restore old bitmap} tRect.right := TarWindow[Window].right;{limit to left} end{if tRect.left} else If tRect.left < TarWindow[Window].left then tRect.left := TarWindow[Window].left; end; FillRect(tRect,myPattern);{fill what is showing with myPattern} End; procedure HandleAHit;{Hit = True... process it} Begin Case HitWindow of {one of our 6 windows} 1,2,3:begin {scrolling windows} EraseTargetInTarWindow(HitWindow,HitWhich);{erase the hit target} HitAValidTarget := False;{initialize to false} {is the target hit on our Valid list?} dj := TarList[HitWindow,HitWhich].theChar;{this is target char we hit} For di := 1 to LastValid do begin {is it a valid target?} If (Valid[di].which = dj) and (Valid[di].remain > 0) then begin {this is a valid target} If SoundOn then begin {start the hit sound} err := PBKillIO(SoundParmBlk,false);{kill current sound} Sound[GoodHit]^.count := FixRatio(di+3,2);{tone by value of target} WriteASoundBlock(ptr(Sound[GoodHit]),Buff[GoodHit],TickCount); end;{if soundOn} Score := Score + Valid[di].points;{update score} DrawStatus(2,Score); NewHiScore; HitAValidTarget := True; dec(Valid[di].remain);{one less of this target} DrawRemainIntoScoreBox(di);{draw into scorebox} If Valid[di].remain = 0 then begin {disable this valid target} If not(BonusUnderWay) then begin {start only if not already going} InitialBonus;{start a bonus target} BonusUnderWay := True; If BonusNdx < 6 then inc(BonusNdx); end else If BonusNdx < 6 then inc(BonusNdx);{get next bonus} Sound[BonusSnd]^.count := FixRatio(BonusNdx,10); BuildValidNow; {rebuild NowValid list} {draw disabled Validtarget in scorebox} tRect := Valid[di].blankrect; tRect.top := Valid[di].centerPt.v; PenPat(myDiagPat); PenMode(patOr);{black force black,white leave alone} PaintRect(tRect);{use penPat and mode to fill rect,i-177} PenNormal;{i-170} {erase other targets of this char already on the screen} For dk := 1 to 3 do {three tarWindows} For dm := 1 to LastTarget do {search thru list for each window} If TarList[dk,dm].theChar = Valid[di].which then begin TarList[dk,dm].theChar := 0; EraseTargetInTarWindow(dk,dm); end;{if TarList} {check to see if all Valids are disabled/complete} If NowValid[NowNdx] = 0 then ValidDone := True; end;{if Valid.remain} end;{if (Valid.which} end;{for di} If not(HitAValidTarget) then begin {target hit was not a Valid one} If SoundOn then begin err := PBKillIO(SoundParmBlk,false);{kill current sound} WriteASoundBlock(ptr(Sound[BadHit]),Buff[BadHit],TickCount); end;{if soundOn} inc(MissCount); DrawStatus(3,MissCount); end; TarList[HitWindow,HitWhich].theChar := 0;{reset TarList} end;{Case 1,2,3} 4:begin {bonus window} If SoundOn then begin err := PBKillIO(SoundParmBlk,false);{kill current sound} Sound[GoodHit]^.count := FixRatio(8,2); WriteASoundBlock(ptr(Sound[GoodHit]),Buff[GoodHit],TickCount); end;{if soundOn} Score := Score + (Level * 100); DrawStatus(2,Score); NewHiScore; FillRect(TarWindow[4],myPattern);{erase the BonusRect} BonusUnderWay := False; end;{bonus window} 5,6:begin {spot targets on sides} If MissCount > 0 then begin {if there are misses then reduce count} If SoundOn then begin err := PBKillIO(SoundParmBlk,false);{kill current sound} Sound[GoodHit]^.count := FixRatio(8,2); WriteASoundBlock(ptr(Sound[GoodHit]),Buff[GoodHit],TickCount); end;{if soundOn} dec(MissCount); DrawStatus(3,MissCount); end;{if Misscount} end;{spot windows on sides} end;{Case HitWindow} End;{procedure} procedure CheckSpotStatus;{move spot targets to next position} Begin For si := 5 to 6 do begin {both targets} If SpotSpeed[si] < 0 then begin {moving up...} If SpotRect[si].top > TopSpot[si] then begin {move up by speed pixels} SpotRect[si].top := SpotRect[si].top + SpotSpeed[si]; SpotRect[si].bottom := SpotRect[si].bottom + SpotSpeed[si]; end else begin {upper limit,reverse direction} OffSpot[si].top := OffSpot[si].top + SpotSpeed[si]-1;{speed is minus} OffSpot[si].bottom := OffSpot[si].bottom + SpotSpeed[si]-1; SpotSpeed[si] := -SpotSpeed[si];{reverse direction} dec(SpotRect[si].top); dec(SpotRect[si].bottom); end; end {If SpotSpeed} Else begin {moving down} If SpotRect[si].bottom < BotSpot[si] then begin SpotRect[si].top := SpotRect[si].top + SpotSpeed[si]; SpotRect[si].bottom := SpotRect[si].bottom + SpotSpeed[si]; end else begin {at lower limit, reverse direction} OffSpot[si].top := OffSpot[si].top + SpotSpeed[si]+1;{speed is plus} OffSpot[si].bottom := OffSpot[si].bottom + SpotSpeed[si]+1; SpotSpeed[si] := -SpotSpeed[si];{reverse direction} inc(SpotRect[si].top); inc(SpotRect[si].bottom); end; end; CopyBits(OffScreen,myWindow^.portBits,OffSpot[si], SpotRect[si],srcCopy,nil); {draw the spot} end;{for si:=} End; procedure DrawSandStuff;{hourglass... GameOver will flag end of level} Begin GameOver := False; {set up for drawing on sand trickle} If TrickleLeft then begin {draw left trickle in gray} PenPat(Gray); MoveTo(TrickleTop.h,TrickleTop.v); LineTo(TrickleBot.h,TrickleBot.v);{gray left trickle} PenPat(White); TrickleLeft := False; end else begin {erase left trickle} PenPat(White); MoveTo(TrickleTop.h,TrickleTop.v);{white left trickle} LineTo(TrickleBot.h,TrickleBot.v); PenPat(Gray); TrickleLeft := True; end; {draw right trickle either gray or white (erase) set above} MoveTo(TrickTopR.h,TrickleTop.v);{right side line,opposite of left} LineTo(TrickBotR.h,TrickleBot.v); PenPat(Black);{back to normal penPat} If Counter < TimeCount then inc(Counter) {move sand shapes yet?} else begin {animate the hour glass} dec(OTSand.top); dec(OTSand.bottom);{top sand lower means off Rect goes higher} inc(OBSand.top); inc(OBSand.bottom);{bottom sand higher} dec(TrickleBot.v);{shorten trickle} CopyBits(OffScreen,myWindow^.portBits,OTSand, TopSand,srcCopy,TopSandRgn);{draw topsand} If OTSand.top = OffTopSand.top then begin GameOver := True;{flag end of game/hourglass empty!!} CopyBits(OffScreen,myWindow^.portBits,OffHour, Hour,srcCopy,nil);{erase all the trickles} end; CopyBits(OffScreen,myWindow^.portBits,OBSand, BotSand,srcCopy,BotSandRgn);{draw bottom sand} Counter := 1;{reset the counter} end; {if else} End; procedure DoCommonLevelStuff; Begin inc(Level);{next level} DrawStatus(1,Level); {increase speeds etc.} Case Level of 3: inc(Speed[3]); 4: begin inc(Speed[2]);{increase scroll window speed} inc(Speed[3]); SpotSpeed[5] := 4;{increase spot speed} SpotSpeed[6] := -4; end; end;{of case level} SpotSpeed[5] := abs(SpotSpeed[5]);{a must for each level,positive speed} SpotSpeed[6] := -abs(SpotSpeed[6]);{negative speed} If LastValid < 6 then inc(LastValid);{increase Valid targets} err := PBKillIO(SoundParmBlk,false);{kill sound} InitialAnimation; FlushEvents(mDownMask,0); End; procedure EndThisGame; var i:integer; Begin {draw hourglass empty} OffSetRect(OBSand,0,OffBotSand.bottom-OBSand.bottom); OffSetRect(OTSand,0,OffTopSand.top-OTSand.top); If PauseStatus = 2 then InvalRect(myWindow^.portRect) {clue, redraw window} else begin InvalRect(RedrawRect);{force redraw of shade area+gun} InvalRect(Hour);{update will draw empty hourglass} end; PauseStatus := 0;{no pause} WhichSound := Silence;{not sound} SetCursorMenus(false);{show cursor and menus} EnableItem(myMenus[2],1);{enable begin} If Score > theHighs^.score[4] then begin {new hiscore} UpDateMyWindow;{update/draw window before dialog} FlushEvents(upDatemask,0);{we just updated} DisplayDialog(Name);{new hiScore dialog,name and sort} end;{if score} End; procedure AnimateOneLoop; Begin GetMouse(myMouse); {limit movement to target area, note: by using a sliding MouseRect with a fixed GunLimit we can get the GunRect to respond immediately to a change of direction outside the GunLimit Rect. MouseRect simply moves with the mouse when the mouse is outside the GunLimit Rect then we map the mouse to MouseRect and GunLimit to get relative move inside GunLimit if there is any} If not(PtInRect(myMouse,MouseRect)) then begin {a coordinate is outside mouseRect so slide mouseRect to myMouse} If myMouse.h < MouseRect.left then begin {slide MouseRect to left} MouseRect.right := MouseRect.right - (MouseRect.left - myMouse.h); MouseRect.left := myMouse.h; end else If myMouse.h > MouseRect.right then begin MouseRect.left := MouseRect.left + myMouse.h - MouseRect.right; MouseRect.right := myMouse.h; end; If myMouse.v < MouseRect.top then begin MouseRect.bottom := MouseRect.bottom - (MouseRect.top - myMouse.v); MouseRect.top := myMouse.v; end else If myMouse.v > MouseRect.bottom then begin MouseRect.top := MouseRect.top + myMouse.v - MouseRect.bottom; MouseRect.bottom := myMouse.v; end; end;{if not(ptinRect)} {myMouse is to Mouse as site is to GunLimit,myMouse becomes Site coordinates} MapPt(myMouse,MouseRect,GunLimit); If myMouse.h < GunRect.left-2 then begin {slide gun and site to left or right} GunRect.left := GunRect.left - 3; GunRect.right := GunRect.right - 3;{gun/site rect.top/bottom are constant} SiteRect.left := SiteRect.left - 3; SiteRect.right := SiteRect.right - 3; end Else If myMouse.h > GunRect.left+2 then begin GunRect.left := GunRect.left + 3; GunRect.right := GunRect.right + 3; SiteRect.left := SiteRect.left + 3; SiteRect.right := SiteRect.right + 3; end; {horizontal site always points to area directly above the gun} {vertical siteRect moves with mouse,whatever user requests} SiteRect.top := myMouse.v; SiteRect.bottom := myMouse.v + 25;{25 is Site bottom - top} {we have two OffGuns in OffScreen, one is offset over gray by one pixel} WhichGun := GunRect.right mod 2;{proper shape to draw over gray background} CheckSpotStatus;{move and draw the spot targets} {do all the scrolling window stuff} For i := 1 to 3 do begin ScrollRect(TarWindow[i],Vector[i],0,tRgn);{scroll the 3 windows} If (OffPiece[i].left < 0) then StartOne(i) {scrolling right} else {scrolling left} If (OffPiece[i].right > Wings[i].bounds.right) then StartOne(i); CopyBits(Wings[i],myWindow^.portBits,OffPiece[i],Piece[i],srcCopy,nil); OffPiece[i].left := OffPiece[i].left - Vector[i];{move OffPiece} OffPiece[i].right := OffPiece[i].right - Vector[i];{Direction[i] * Speed[i]} end;{for i := 1 to 3} {delay either 2 or 3 ticks, in our tests Delay returned 0.4 ticks sooner than speedFactor requested} If SpeedTrapOn then Delay(SpeedFactor,aTick); If BonusUnderWay then begin {draw all the BonusRect target stuff} dec(BonusLoops); If BonusLoops = 0 then begin {find new speed/loop count} If BonusSpeed < 0 then begin {moving left so double to right} BonusSpeed := abs(BonusSpeed); BonusLoops := PrevBonusLoops * 2;{multiply is faster than add} end else begin {moving right.. pick a new at random..left or right} BonusSpeed := Random div 6554;{-4 to +4} BonusLoops := abs(Random div 1639) + 8; PrevBonusLoops := BonusLoops;{Prev.. needed later if moving left} end; end;{If BonusLoops} {is new Rect.left to right side of window yet?} If BonusRect.left + BonusSpeed > TarWindow[4].right then begin WhichSound := Silence;{will kill the bonussnd} BonusUnderWay := false; end else begin {draw the bonus shape} {change shapes..ie.each of two faces every 8 loops} Case BonusShape of 1: If ((Counter Mod 8) = 0) then inc(BonusShape); 2: If ((Counter Mod 8) = 0) then dec(BonusShape); end;{case BonusShape} {draw the BonusRect...} BonusRect.left := BonusRect.left + BonusSpeed;{move rect} BonusRect.right := BonusRect.right + BonusSpeed;{offset BonusRect rect} CopyBits(BonusMap,myWindow^.portBits, OffBonus[BonusNdx,BonusShape], BonusRect,srcCopy,TarWindRgn);{draw it} end; end;{If BonusUnderWay} If ShotFired = True then begin {ShotFired is set by mouseDown event} {draw bullet trace and explode, use PatXOr mode so we can reverse it} PenMode(PatXOr);{invert} TargetCenter.h := GunRect.left+ 16; TargetCenter.v := myMouse.v + 14; MoveTo(TargetCenter.h,GunRect.top); LineTo(TargetCenter.h,TargetCenter.v);{line from gun to explode} {locate Explode shape} ExplodeRect.right := GunRect.left+(ExplodeRect.right-ExplodeRect.left); ExplodeRect.left := GunRect.left; ExplodeRect.bottom := myMouse.v+(ExplodeRect.bottom-ExplodeRect.top); ExplodeRect.top := myMouse.v; CopyBits(OffScreen,myWindow^.portBits,OffExplode, ExplodeRect,srcXor,nil);{draw it} {detect a hit here....sound and such....} DetectAHit;{we'll process the hit later, after erasing line/explode} {draw Site (from screen) to OffGun then OffGun to Screen, do it here so explode is shown in site!} CopyBits(myWindow^.portBits,OffScreen,SiteRect, OffSite[WhichGun],srcCopy,SiteRgn[WhichGun]); CopyBits(OffScreen,myWindow^.portBits,OffGun[WhichGun], GunRect,srcCopy,nil); aTick := TickCount;{delay abit...so we can see line/explode} inc(aTick); Repeat Until(TickCount > aTick); ShotFired := False;{reset gun,ready for next mousedown} {erase the bullet line & explode, just invert the inversion!} MoveTo(TargetCenter.h,GunRect.top); LineTo(TargetCenter.h,TargetCenter.v);{inverse is still in effect} CopyBits(OffScreen,myWindow^.portBits,OffExplode, ExplodeRect,SrcXor,nil);{inverse mode} PenMode(PatCopy);{set back to normal} {window is back to normal (no lines/explode) so do hit stuff} {would have a mess if we erased targets before erasing explode} If Hit = True then HandleAHit; end Else begin {no shot fired... just draw the site/gun} CopyBits(myWindow^.portBits,OffScreen,SiteRect, OffSite[WhichGun],srcCopy,SiteRgn[WhichGun]);{site into OffGun} CopyBits(OffScreen,myWindow^.portBits,OffGun[WhichGun], GunRect,srcCopy,nil);{offGun into window} end; PixOff[1] := PixOff[1] + Speed[1];{distance (in pixels) targets have moved} PixOff[2] := PixOff[2] + Speed[2]; PixOff[3] := PixOff[3] + Speed[3]; If ValidDone then {all score targets finished, check for end of this level} If (MissCount = 0) and not(BonusUnderWay) then begin {do new levelstuff} FlushEvents(mDownMask,0);{flush the mouseDown events} DrawPicture(TarWindPic,TarWindPic^^.picFrame);{erase old targets} ClipRect(GunRect);{erase the gun area only} EraseRect(GunRect);{will erase in clipped area only} DrawPicture(BackSide,BackSide^^.picframe);{will draw over gun} ClipRect(myWindow^.portRect);{reset to whole window} {display the timebonus frame stuff and animate} EraseRect(Hour);{erase current hourglass} MoveHourGlass(TBone.hourMove.h,TBone.hourMove.v);{move hourglass to new} EraseRect(TBone.frame); tRect := TBone.frame; PenSize(2,2); FrameRect(tRect); PenSize(1,1); InsetRect(tRect,3,3); FrameRect(tRect);{erase and frame area for timebonus} DrawHourGlass;{draw hourglass in new location} Copybits(FontMap,myWindow^.portbits,TBone.arrowSource, TBone.arrowDest,srcCopy,nil);{draw the arrow} FrameRect(TBone.numBox);{draw box to frame numbers} tRect := TBone.numBox; InsetRect(tRect,4,4);{for moveTo and erase} aTick := 0;{will use to accumulate/display time score} If SoundOn then begin If odd(Level) then begin whichSound := SnakeCharmer; {designate which of FourTone} i := mySongPtr^.noteCount; end else begin whichSound := Walk; i := myWSongPtr^.noteCount;{walk noteCount for loop} end; InitialSnakeSong(1);{Start the Song} end;{if sound} {now animate hourglass and score until sand is gone (GameOver)} Repeat SystemTask; {check song note to see if we need to start another note} If (TickCount > ToneDelay) then begin {get another songnote} {if last note then reset to beginning} If NoteCount < i then inc(NoteCount) else NoteCount := 1; SetSnakeSoundStuff;{get next sound,etc.} end; {if TickCount} If SpeedTrapOn then Delay(Speedticks,BuffSize);{slow down 1 tick} DrawSandStuff; {will return GameOver true if done} {use aTick to accumulate the Time Score} aTick := aTick + (Level div 2) + 1;{timebonus score} NumToString(aTick,tStr); MoveTo(tRect.right-StringWidth(tStr)-1,tRect.bottom); EraseRect(tRect); DrawString(tStr);{draw the new score} Until(GameOver = True);{true when all the sand is gone} Score := Score + aTick;{update score} DrawStatus(2,Score); NewHiScore; MoveHourGlass(-TBone.hourMove.h,-TBone.hourMove.v);{back to original} DrawHourGlass; whichSound := Silence; err := PBKillIO(SoundParmBlk,false);{kill sound} EraseRect(TBone.frame);{get rid of TimeBonus frame} If Level <> 4 then begin {handle various levels} Case Level of 2:Begin DrawAClue(1);{draw a clue} PauseThisGame(2);{2 indicates a clue pause} end; 10:Begin {display the clue dialog, get user input} ShowCursor;{for dialog input} DisplayDialog(ClueDial);{will cause update for myWindow} HideCursor;{continue with game} If PauseStatus = 3 then begin {3 if tomb,0 if not} ShowCursor; DisplayDialog(Tomb);{display the tomb dialog} HideCursor; PauseStatus := 0;{enable normal redrawing} Score := Score + 10000;{tomb bonus = 10000} DrawStatus(2,Score); NewHiScore; end; InvalRect(myWindow^.portRect);{whole window} UpdateMyWindow; FlushEvents(updateMask,0); DrawPicture(TarWindPic,TarWindPic^^.picFrame);{continue} DoCommonLevelStuff; end;{case 5} OtherWise begin {draw the empty TarWindows and start another level} DrawPicture(TarWindPic,TarWindPic^^.picFrame); DoCommonLevelStuff;{do here for no clue, controls for clues} end;{otherwise} end;{case level} end{if level <> 4} Else begin {game is locked...let user know} ShowCursor; DisplayDialog(Unlock);{text about unlocking the game} HideCursor; EndThisGame; end; FlushEvents(mDownMask,0); end;{if (MissCount} {draw the hourglass stuff, end game if out of sand} If PauseStatus = 0 then begin {if no pause, watch out for clue level pause} DrawSandStuff; {SandStuff will return GameOver = true if sand empty} If GameOver then EndThisGame; end;{if pauseStatus} End; PROCEDURE InitThings; Begin InitGraf(@thePort); {create a grafport for the screen} MoreMasters; {extra pointer blocks at the bottom of the heap} MoreMasters; {this is 5 X 64 master pointers} MoreMasters; MoreMasters; MoreMasters; MoreMasters; {get the cursors we use and lock them down - no clutter} ClockCursor := GetCursor(watchCursor); {HLock(Handle(ClockCursor));locked in resource} For i := 1 to 3 do begin myCursor[i] := GetCursor(129 + i); {HLock(Handle(myCursor[i]));locked in resource} end; {show the watch while we wait for inits & setups to finish} SetCursor(ClockCursor^^); {init everything in case the app is the Startup App} InitFonts; {startup the fonts manager} InitWindows; {startup the window manager} InitMenus; {startup the menu manager} TEInit; {startup the text edit manager} InitDialogs(Nil); {startup the dialog manager} Finished := False; {set program terminator to false} FlushEvents(everyEvent,0); {clear events from previous program} { set up screen size stuff } Screen := ScreenBits.Bounds; { Get screen dimensions from thePort } with Screen do SetRect(DragArea,Left+4,Top+24,Right-4,Bottom-4); ResRefNum := CurResFile;{i-116} End; procedure CreateWindow;{windows,dialogs} var tRect: Rect; Begin {get window. template will use our WDEF 128 resource. set PROCId field} myWindow := GetNewWindow(WindResId,@wRecord,Pointer(-1)); SetRect(tRect,0,-10,600,400);{this will clip OffScreen bitMaps as well!!} ResumeButton := GetNewControl(pressResume,myWindow); EndButton := GetNewControl(pressEnd,myWindow); {remember: Dialog itemlists should be purgable, dialogs invisible} For DialNdx := Help to PrintDial do begin {read all the dialogs into array} myDialog[DialNdx] := GetNewDialog(ord(DialNdx)+129,@myDialogRec[DialNdx],myWindow); SetPort(myDialog[DialNdx]); ClipRect(tRect);{set clip to smaller size..} end; Environs(i,j);{peek to see what hardware, i is rom, j is machine 1 = 128k thru Plus,2 = Mac II,3 = SE} If j = 2 then SetControlValue(4) {must be a Mac II speedwise} else SetControlValue(2);{set NORMAL button in Speed dialog} ShowWindow(myWindow);{done so back to myWindow...} SetPort(myWindow); ClipRect(tRect); {i-166, set cliprgn to small rgn} TextFont(3);{Geneva font, installed in System resource file} TextSize(12); ScoreHandle := GetResource('HSCR',128);{we're keeping Scores in Resource} {HLock(ScoreHandle);lock it permanently in resource} theHighs := ScorePtr(ScoreHandle^); end; procedure DoMenuCommand(mResult:LongInt); var name: Str255; tPort: GrafPtr; i,h: integer; Begin theMenu := HiWord(mResult); theItem := LoWord(mResult); Case theMenu of appleMenu: Begin GetPort(tPort); If theItem = 1 then DisplayDialog(About) Else begin GetItem(myMenus[1],theItem,name);{must be a desk acc.} refNum := OpenDeskAcc(name); end; SetPort(tPort); End; fileMenu: Case theItem of 1:Begin HiliteMenu(0); BeginTheGame;{rem draws a new menu bar} end; 2:Finished := True; {quit this program} end;{of case} optionMenu: Case theItem of 1:DisplayDialog(Guard);{protection dialog} 2:DisplayDialog(Help); 3:DisplayDialog(Scores);{hiscore stuff} 4:Begin {toggle sound on or off} If SoundOn then SoundOn := false else SoundOn := true; CheckItem(myMenus[3],theItem,SoundOn);{check if true,none if false} end; 5:DisplayDialog(SetSpeed);{for hardware} end; { case theItem} worksMenu: Case theItem of 1:DisplayDialog(BMap);{two of our Offscreen bitmaps} 2:DisplayDialog(Source);{our source code offer} end;{case theItem} End;{case theMenu of} HiliteMenu(0); End; procedure TakeCareKeyDown(Event:EventRecord); Var CharCode: char; Begin CharCode := chr(LoWord(BitAnd(Event.message,CharCodeMask))); If BitAnd(Event.modifiers,CmdKey) = CmdKey then DoMenuCommand(MenuKey(CharCode)) Else If GameUnderWay then PauseThisGame(1);{pause with any other key press} End; procedure TakeCareControls(whichControl:ControlHandle;localMouse:point); var ControlHit,i: integer; Begin ControlHit := TrackControl(whichControl,localMouse,nil); { Find out which} If ControlHit > 0 then {i-417} Begin If whichControl = ResumeButton then {RESUME the game..} Begin HideControl(ResumeButton);{watch out for update event} HideControl(EndButton); If (PauseStatus = 2) then InvalRect(myWindow^.portRect);{erase clue} BeginUpdate(myWindow);{clear Update stuff...buttons} EraseRect(myWindow^.portRect); Case PauseStatus of 2:Begin {clue is being draw over} PauseStatus := 0;{so Draw will redraw properly} GameUnderWay := True; DrawWindowContents(myWindow); PauseStatus := 2;{so we can DoCommon... below} end; OtherWise DrawWindowContents(myWindow) {just the hidden control area} end;{case PauseStatus} EndUpDate(myWindow); FlushEvents(updateMask,0);{clear Update caused by HideControl} If PauseStatus = 2 then DoCommonLevelStuff;{start next level} SetCursorMenus(True); PauseStatus := 0; End; If whichControl = EndButton then {END this current game...} Begin HideControl(ResumeButton);{hide the resume and end} HideControl(EndButton); EndThisGame; End; End; {of If ControlHit} End; { of procedure} procedure TakeCareMouseDown(myEvent:EventRecord); var Location: integer; WhichWindow: WindowPtr; WhichControl: ControlHandle; MouseLoc: Point; WindowLoc: integer; ControlHit: integer; GlobalMouse:Point; Begin If GameUnderWay = True then begin {handle a game click} ShotFired := True;{flag a shot, will be checked in AnimateOneLoop} If SoundOn then begin {write/start a Gunshot sound} {leave whichsound = to whatever it is} err := PBKillIO(SoundParmBlk,false);{kill current sound} WriteASoundBlock(ptr(Sound[GunShot]),Buff[GunShot],TickCount); end;{if soundOn} end Else begin {Mouse is normal...handle normal functions} MouseLoc := myEvent.Where; {Global coordinates} WindowLoc := FindWindow(MouseLoc,WhichWindow); {I-287} case WindowLoc of inMenuBar: DoMenuCommand(MenuSelect(MouseLoc)); inSysWindow: SystemClick(myEvent,WhichWindow); {I-441} inDrag: DragWindow(WhichWindow,MouseLoc,DragArea); inContent: If WhichWindow <> FrontWindow then SelectWindow(WhichWindow) else Begin GlobaltoLocal(MouseLoc); {check to see if our Continue/End buttons were clicked} ControlHit := FindControl(MouseLoc,whichWindow,whichControl); If ControlHit > 0 then TakeCareControls(whichControl,Mouseloc) {check to see if click on hourglass, begin the game} Else If (PtInRect(MouseLoc,Hour)) and (PauseStatus = 0) then BeginTheGame;{start the game} end;{else} end; {case of} end;{Else} end; { TakeCareMouseDown } procedure TakeCareActivates(myEvent:EventRecord); var WhichWindow: WindowPtr; Begin WhichWindow := WindowPtr(myEvent.message); SetPort(WhichWindow); End; procedure TakeCareUpdates(Event:EventRecord); var UpDateWindow,TempPort: WindowPtr; Begin UpDateWindow := WindowPtr(Event.message); GetPort(TempPort); SetPort(UpDateWindow); BeginUpDate(UpDateWindow); EraseRect(UpDateWindow^.portRect); DrawWindowContents(UpDateWindow);{we only have one window!} EndUpDate(UpDateWindow); SetPort(TempPort); End; procedure TakeCareSoundStuff;{called from MainEvent} {the current sound is complete if ioResult < 0. Some of our sounds only play thru once so we leave WhichSound = Silence so that the sound is not restarted. BonusSnd is played over and over while a Bonus (bottom target) is displayed so we set WhichSound = BonusSnd.. and each time it is finished this procedure starts another BonusSnd. In the case of our two 'songs' we are changing the notes before the sound is complete (before ioResult < 1 or duration = 0). We check the current TickCount against a variable ToneDelay which equals the TickCount when the note started plus the ticks the note should play... so if TickCount > ToneDelay we need to start another note.. we just change the pitches and reset the Duration field and keep going! Note: the sound i/o decrements the duration field so it must be reset each time!} Begin If SoundParmBlk^.ioResult < 1 then {sound is finished find another/play it} Case WhichSound of BonusSnd:{start another BonusSnd} WriteASoundBlock(ptr(Sound[BonusSnd]),Buff[BonusSnd],TickCount); end; {case whichsound} {we don't use Songs from MainEvent loop but if we did we'd need this Else. All our 'song's are played from closed loops in the AnimateOneLoop} (* Else {sound is still underway if Snake or Walk check for next note} Case WhichSound of SnakeCharmer:begin {snakeCharmer song is underway check for new note} If (TickCount > ToneDelay) then begin {then get next note} inc(NoteCount); SetSnakeSoundStuff; {check to see if this is the last note} If mySongPtr^.noteCount = NoteCount then WhichSound := Silence; end;{if} end;{snake} Walk:begin {Walk song is underway check for new note} If (TickCount > ToneDelay) then begin {need new note} inc(NoteCount); SetSnakeSoundStuff; If myWSongPtr^.noteCount = NoteCount then WhichSound := Silence; end;{if} end;{walk} end;{case & else} *) End;{of procedure} procedure MainEventLoop; var myEvent: EventRecord; i: integer; Begin InitCursor; SetCursor(myCursor[1]^^);{one of 3 custom cursors} Repeat SystemTask; If GetNextEvent(EveryEvent,myEvent) then {handle the event} Case myEvent.What of mouseDown: TakeCareMouseDown(myEvent); KeyDown: TakeCareKeyDown(myEvent); ActivateEvt: TakeCareActivates(myEvent); UpdateEvt: TakeCareUpdates(myEvent); End {of Case} Else {no event pending so lets do some game stuff} If GameUnderWay then begin AnimateOneLoop; If SoundOn then TakeCareSoundStuff; end;{ if GameUnderWay} Until Finished; End; procedure SetUpMenus; var i: integer; Begin myMenus[1] := GetMenu(appleMenu); {get menu info from resources} AddResMenu(myMenus[1],'DRVR'); {add in all the DA's} myMenus[2] := GetMenu(fileMenu); myMenus[3] := GetMenu(optionMenu); myMenus[4] := GetMenu(worksMenu); myMenus[5] := GetMenu(MessageMenu); {this is the backspace message} CheckItem(myMenus[3],4,True); {check the Sound item} SoundOn := True; {sound will start on first begin} For i := 1 to 4 do InsertMenu(myMenus[i],0); DrawMenuBar; End; procedure CloseStuff; Begin ShowCursor; err := PBKillIO(SoundParmBlk,false);{always kill sound before quitting} WriteResource(ScoreHandle);{write out if changes were flagged..HiScore} End; {Main Program begins here} BEGIN InitThings; SetUpMenus; {load the menus from resources} CreateWindow; {load game window and dialogs from resources} CreateSound; {load songs and create sound stuff} CreateCairoStuff; {load Cairo font bitimage and locate table} CreatePictures; {load Pictures from resources} CreateOffScreenBitMap; {load Bitmap from resources} CreateStrings; {load strings from resources} OneTimeGameStuff; {locate all our rects, etc. in game window} MainEventLoop; {let's play a game! get user input} CloseStuff; {done so close} END.