Tic Tac Toe
Volume Number: | | 2
|
Issue Number: | | 10
|
Column Tag: | | Assembly Language Lab
|
Create a Tic Tac Toe Game!
By Mike Scanlin, World Traveler
While reading MacTutor and learning how to program in 68000, I decided to try and write a simple game. I wanted something that was simple enough so that I could concentrate on the coding of it rather than the logic of it. Tic Tac Toe was the obvious choice. It would let me try using menus, windows, dialogs and simple graphics. After the initial game was written, I added some other features just for the sake of making the game a little more interesting. Then I added two text manipulation effects to take some of the boredom out of the "About" dialog. The result is a program that is more complicated than the average Tic Tac Toe program, but (hopefully) simple enough so you can follow the code with relative ease and learn something from the techniques and routines used in it.
The files used are "TicTacToe.asm", a resource file "TicTacToe_rsrc.asm" that contains two icons used in the "About" dialog, two text effects routines "GrowText.asm" and "SnakeText.asm", and the link file "TicTacToe.link". I have found that building a library of common subroutines (like GrowText and SnakeText) that can be linked together and used in different programs is incredibly useful. After 5 years of 6502 programming on the Apple ][ without this feature, I have learned to love the Macintosh Linker. The Tic Tac Toe program that follows could be split up into even more subroutine files if desired.
The Program
The program starts off by initializing all routine manangers that it will be using. I should point that there is a "resume" procedure pointer passed to _InitDialogs (instead of the typical nil pointer). The consequence of this is that if a serious system error should occur, the "resume" button will not be greyed and if you click on it control will be passed to the "resume" procedure. In my case, all the resume procedure does is jump to the Finder (via _exitToShell). I find this quite helpful when developing a program because it is much quicker than rebooting the system. Of course, if the program messed up RAM in some way it could be a big mistake to jump to the Finder and continue work as if all was normal. But due to the nature of the program I was writing (wasn't dealing with disk IO), I decided to use it anyway (and had no problems). Use this with discretion and ALWAYS keep backups. If you're at all unsure (or nervous) about the possible effects of your program, then don't use this method (use "CLR.L -(SP)" in place of "PEA resume" to push a nil pointer. That will grey the "resume" button and force a restart if you get a serious system error). The next thing called is _TEInit. As Inside Macintosh says, it should be called even though you may not use TextEdit so that desk accessories and dialogs will work correctly.
Fig. 1 Our Tic Tac Toe Game!
After the basic initialization, all menus, windows and dialogs are created. The reason for creating all of the windows and dialogs in the beginning of the program is twofold. First, because it's nice having them all together in the source code for debugging purporses. Secondly, and more important, it helps to keep the heap from becoming fragmented if they are all created one after the other. This technique is probably best used for static data structures that are needed throughout the entire program (e.g. global variables). On larger programs with lots of windows and dialogs, it is probably not a good idea to make them all at once (due to RAM limitations). The remainder of the initialization and the main event loop (GetEvent) should be straightforward. It is very similar to previous programs published in MacTutor.
The Dialogs
All of the dialogs use a routine called CenterString. Its function is to calculate the offset from the left edge of a window needed to center a string in a window, given the width of the window (the windowWidth variable) and a pointer to the string. The algorithm is fairly obvious: subtract the length of the string (found from _StringWidth) from windowWidth and divide by two. This value is returned in D0 and is used as the horizontal value for the _MoveTo called just before _DrawString.
Additionally, the "About" dialog uses the two routines GrowText and SnakeText. GrowText starts out by drawing the string at a textSize of 1 point and then redraws it at 2 point, etc., up to 12 point (erasing the previous string with _eraseRect just before drawing the new string) so that it looks like the string is growing. SnakeText is rather more complicated, but works in a similar manner. It doesn't really look like a snake, but for lack of a better name that's what it is called. The best way to understand what it is doing is to watch it perform a couple of times. Basically, it is redrawing characters at different textSizes until they're all at 12 point. By studying the commented source code, you should be able to get some idea of how it works. But you don't need to understand how it works to use it within your own programs (just be sure you set up the input registers correctly). I should mention that the rectangle that you pass a pointer to must be tight fitting around the text it is to draw (i.e. no extra white space around the top,left, or bottom -- the right side isn't too important). If it isn't, the routine will still function, but it may not look too pretty on the screen (like a lot of overdrawn or partially overdrawn characters). The method I used to find the correct dimensions of the rectangle is to draw the string where I wanted it and then use _FrameRect until I found the smallest possible rectangle that would enclose all of the text. It will probably take a little trial and error to get the dimensions perfect, but it's important.
Fig. 2 With some nifty dialog 'about' boxes
The DefaultOutline subroutine is used to draw the 3 pixel wide oval around the "OK" button in the dialogs in the standard way. It is worth pointing out that anything you want in a dialog box that isn't in the dialog's item list has to be put in the dialog box before calling _ModalDialog (when you call _ModalDialog, it draws the things that are in the dialog's item list). So the oval around the "OK" button is actually drawn before the button is drawn by _ModalDialog. This is also true for any dynamic text (or special effects like GrowText or SnakeText) that you want to have in the dialogs.
Game Logic
Every time you select "New Game" from the "Game" menu, the computer randomly decides who goes first. If you're looking at a blank board after you have selected "New Game" then you go first, otherwise you'll see a square somewhere (the computer's first move). The player is always circles, the computer is always squares. The "Reverse Positions" option merely switches all circles for squares and squares for circles -- it does not change the order of turns or who is what piece. It was included as an option mainly because it was easy to code (besides, it does add a little to the interest of the program). It is only enabled when a game is in progress.
If the difficulty is set on "easy" then the computer will always move randomly (which gets boring after about 1 game). The "hard" difficulty should be called "not quite so easy, but not very difficult either" but that is a bit of a long name for a menu option. Face it, this is not a difficult game to play, there's not a whole lot you can do to make the computer (or anyone else, for that matter) play more intelligently. When set on "hard" it makes three checks for intelligent moves before it decides to move randomly: (1) checks if the computer can win in the next move, (2) checks if the computer can block a player win in the next move, (3) checks if the middle square is taken. The "cheating" options were added for fun. As the dialog says, anyone who is allowed to cheat (player or computer) is allowed to move on any square, even if someone else is already there. As far as the coding of this is concerned, all it amounts to is bypassing the usual checks for valid moves.
If there is a mouseDown event anywhere in the game window, the point is checked to see if the player clicked in one of the nine squares. The coordinates of the click are first converted into local coordinates by calling _GlobalToLocal and then checked to see if the point is in any of the nine squares by calling _PtInRect. The coordinates of the nine squares are in stored in the table BoardLocs. These coordinates are used as dimensions for drawing the squares and circles (with _FrameRect and _FrameOval) as well as checking for validity of mouseDown events.
Miscellaneous
The "Show/Hide scores" menu option was included to demonstrate how to change menu items. All that is needed is to set up the parameters on the stack and call _SetItem. Likewise, the checking and unchecking of the difficulty setting and cheat settings can be accomplished with calls to _CheckItem.
One thing I learned while writing this program (after many headaches) is that any variable declared with the "DS" (Define Storage) declarative will have to be referenced with address register A5. This is not true for variables declared with the "DC" (Define Constant) declarative. For instance, if ShowFlag is declared "ShowFlag DS 1" it will be referenced as "MOVE ShowFlag(A5),D0" but if it is declared "ShowFlag DC 1" it will be referenced as "MOVE ShowFlag,D0". This is because all storage declared with DS is put in the application globals space (pointed to by A5 when your program starts) while all storage declared with DC stays within the program code segment (addresses filled in by the Linker). If you use both types of declarations (DS and DC) in your programs, as is done in this one, it is critical to pay attention to which is which and use the correct type of reference or you will end up with random results.
As was mentioned above with respect to SnakeText and GrowText, the routines presented here can be used as part of your own programs. The RandomBounds routine (combined with its initialization of randSeed in the beginning) is a good way to get bounded random numbers into your assembly language programs. It returns a positive integer in the range 1 to D0, where you set D0 before calling it. The CenterString and DefaultOutline routines should be useful, too.
; GrowText 4 March 1986
;--------------
; print a text string starting at 1 point up to 12 point,
; centered in a rectangle (horizontally, not vertically).
; the string gets a little bigger each time through the loop.
; inspired by Lynn Bond, who gets a little bigger each time
; she eats a pound of M&M's.
.TRAP _EraseRect $A8A3
.TRAP _TextSize$A88A
.TRAP _MoveTo $A893
.TRAP _DrawString$A884
Xref GrowText,CenterString,windowWidth
right EQU 6
left EQU 2
;------------
GrowText:
;------------
; before calling, user should push the following onto the stack:
; (in this order)
; address of string to print
; addr of rect that encloses the string
; vertical height to print string
;-------------
;save return address
MOVE.L (SP)+,returnAddress(A5)
;get parameters
MOVE (SP)+,vPosition(A5)
MOVE.L (SP)+,rectPointer(A5)
MOVE.L (SP)+,stringPointer(A5)
;save windowWidth
MOVE windowWidth(A5),-(SP)
;set up windowWidth and leftEdge so we can draw some
; centered text
MOVE.L rectPointer(A5),A0
MOVE right(A0),windowWidth(A5)
MOVE left(A0),D0
MOVE D0,leftEdge(A5)
SUB D0,windowWidth(A5)
;init textSize to 1
MOVE #1,textSize(A5)
@1 MOVE.L rectPointer(A5),-(SP) ;erase old string
_EraseRect
MOVE textSize(A5),-(SP)
_TextSize
MOVE.L stringPointer(A5),A0
JSR CenterString
ADD leftEdge(A5),D0
MOVE D0,-(SP) ;h
MOVE vPosition(A5),-(SP) ;v
_MoveTo
MOVE.L stringPointer(A5),-(SP) ;draw new string
_DrawString
MOVE #32000,D0;delay
@2 DBRA D0,@2
ADDQ #1,textSize(A5) ;inc textSize for
;next iteration
CMPI #13,textSize(A5)
BNE @1
;restore windowWidth
MOVE (SP)+,windowWidth(A5)
MOVE.L returnAddress(A5),-(SP)
RTS
; global variables
textSizeDS1
vPosition DS1
rectPointer DS.L 1
stringPointer DS.L1
leftEdgeDS1
returnAddress DS.L1
; SnakeText 22 February 1986
;----------------
; Do a neat special effect with a string of chars. Print each
; char in string 1 to 11 times (currently set at 6), from 2 pt
; to 12 pt so that it looks really cool.
.TRAP _EraseRect $A8A3
.TRAP _TextSize$A88A
.TRAP _MoveTo $A893
.TRAP _DrawChar$A883
.TRAP _CharWidth $A88D
.TRAP _NewPtr $A100+30
.TRAP _DisposPtr $A000+31
Xref SnakeText
topEQU 0
left EQU 2
bottom EQU 4
tblChar EQU 0
tblTextSize EQU 2
tblTop EQU 4
tblLeft EQU 6
tblBottom EQU 8
tblRightEQU 10
tblRect EQU 4
;-------------
SnakeText:
;-------------
; on entry:
; A0 points to string
; A1 points to rectangle to print string in (left edge should
; be tight)
; D0 = starting v
;--------------
MOVEM.LA2-A4/D3,-(SP) ;save some regs
;save inputs
MOVE.L A0,StringPtr(A5)
MOVE.L A1,StringRectPtr(A5)
MOVE D0,vLoc(A5)
;init textSize to 12 point
MOVE #12,-(SP)
_TextSize
;set up a table (block of mem) to work with. Each char in
; string will occupy 6 words in the table. One word for each of
; the following:
; char, curSizeOfChar, top, left, bottom, right
;Each char has a rectangle defined by top, left, bottom and
; right that encloses the char. This rect is used to erase the old
; char before drawing the new (and larger) char.
MOVE.L StringPtr(A5),A0
;first byte of string is length byte.
;we need 12 bytes (6 words) for each char in string.
CLR.L D0
MOVE.B (A0),D0 ;get length of string
MOVE.L D0,D1
ASL #2,D0
ASL #3,D1
ADD D1,D0 ;D0=12*length(string)
;get a pointer to a block of D0 bytes of free RAM
_NewPtr
MOVE.L A0,A2 ;copy BlockPtr (assume no err)
;initialize some stuff
MOVE.L A2,BlockPtr(A5)
MOVE.L StringRectPtr(A5),A0
MOVE top(A0),TopValue(A5)
MOVE left(A0),LeftValue(A5)
MOVE bottom(A0),BottomValue(A5)
MOVE.L StringPtr(A5),A3
CLR D3
MOVE.B (A3)+,D3 ;get length of string
SUBQ #1,D3
;build the table
; A2 = BlockPtr (pointer to current char in table)
; A3 = StringPtr (points to next char in string to work with)
; D3 = length of string (loop control variable)
@2 MOVE.B (A3)+,D0 ;get a char
MOVE D0,tblChar(A2) ;save char in table
CLR tblTextSize(A2) ;textSize=0 to start
MOVE TopValue(A5),tblTop(A2)
MOVE LeftValue(A5),tblLeft(A2)
MOVE BottomValue(A5),tblBottom(A2)
CLR -(SP) ;space for integer result
MOVE D0,-(SP) ;push char
_CharWidth ;get width of char
MOVE (SP)+,D0
ADD LeftValue(A5),D0 ;add char width
; to previous
MOVE D0,tblRight(A2) ; LeftValue to get
; RightValue
MOVE D0,LeftValue(A5)
ADDA #12,A2 ;inc table pointer
DBRA D3,@2
;save ptr to last char in table
SUBA #12,A2
MOVE.L A2,LastCharPtr(A5)
MOVE.L BlockPtr(A5),A2 ;A2 = FrontPtr
MOVE.L A2,A3 ;A3 = EndPtr
;do the drawing
;-------------------
; A4 = loop control variable
@3 MOVE.L A2,A4 ;start with FrontPtr
;vary the speed by putting an even factor of 12 (i.e. 1,2,3,4,6)
; in the line below.
; 1 = slow (11 char lag)
; 2 = sorta slow (5 char lag) <-- set at 2 now
; 3 = medium (3 char lag)
; 4 = fast (2 char lag)
; * note, if you set it at 5, you'll regret it (hang system)
; 6 = really fast (1 char lag)
;Change the immediate data in the next line (@4):
@4 ADDQ #2,tblTextSize(A4);inc textSize(A4)
CMPA.L A3,A4 ;are we at EndPtr?
BEQ @5
SUBA #12,A4 ;go to previous entry in table
BRA @4
@5 PEA tblRect(A4);erase old char
_EraseRect
MOVE tblLeft(A4),-(SP) ;draw new char
MOVE vLoc(A5),-(SP)
_MoveTo
MOVE tblTextSize(A4),-(SP)
_TextSize
MOVE tblChar(A4),-(SP)
_DrawChar
CMPA.L A2,A4 ;have we reached FrontPtr?
BEQ @6
ADDA #12,A4
BRA @5
@6 CMP #12,2(A3);is EndPtr size=12?
BNE @7
ADDA #12,A3 ;inc EndPtr
@7 MOVE.L LastCharPtr(A5),A0
CMPA.L A0,A2 ;is FrontPtr at last char?
BEQ @8
ADDA #12,A2 ;inc FrontPtr
@8 CMP #12,tblTextSize(A0) ;is last char's
; size=12?
BNE @3
MOVE.L BlockPtr(A5),A0 ;free the memory used
_DisposPtr
MOVEM.L(SP)+,A2-A4/D3 ;restore some regs
RTS
StringPtr DS.L 1
StringRectPtr DS.L1
LastCharPtr DS.L 1
BlockPtrDS.L1
TopValueDS1
BottomValue DS 1
LeftValue DS1
vLoc DS1
; Tic Tac Toe
;===========
; 1 March 1986 Mike Scanlin
Include MacTraps.D
Include ToolEqu.D
true EQU $100
false EQU 0
randSeedEQU -126
Time EQU $20C
everyEventEQU $FFFF
cmdKey EQU 256
right EQU 6
left EQU 2
;codes for board:
empty EQU 0
square EQU 1
circle EQU 2
;external routines
Xref SnakeText,GrowText
;internal routine and data used by GrowText
Xref CenterString,windowWidth
;------
Start:
;------
PEA -4(A5) ;init everything
_InitGraf
_InitFonts
_InitWindows
_InitMenus
PEA resume ;serious system error
; resume procedure
_InitDialogs
_TEInit
MOVE.L #everyEvent,D0
_FlushEvents
_InitCursor
;make Apple menu
CLR.L -(SP) ;space for hndl result
MOVE #1,-(SP) ;menu id
PEA AppleMenuTitle
_NewMenu
;save handle, but leave it on the stack for _AppendMenu
MOVE.L (SP),AppleMenuHndl(A5)
PEA AppleMenuAbout ;add 'about'
_AppendMenu
MOVE.L AppleMenuHndl(A5),-(SP)
CLR -(SP)
_InsertMenu
;make Game menu
CLR.L -(SP) ;space for hndl result
MOVE #2,-(SP) ;menu id
PEA GameMenuTitle
_NewMenu
MOVE.L (SP),GameMenuHndl(A5)
PEA GameMenuNew;add New Game
_AppendMenu
MOVE.L GameMenuHndl(A5),-(SP)
PEA GameMenuRev ;add Reverse Positions
_AppendMenu
MOVE.L GameMenuHndl(A5),-(SP)
PEA GameMenuShow ;add Show Scores
_AppendMenu
MOVE.L GameMenuHndl(A5),-(SP)
PEA GameMenuClear ;add Clear Scores
_AppendMenu
MOVE.L GameMenuHndl(A5),-(SP)
PEA GameMenuLine ;add a line
_AppendMenu
MOVE.L GameMenuHndl(A5),-(SP)
PEA GameMenuQuit ;add Quit
_AppendMenu
MOVE.L GameMenuHndl(A5),-(SP)
CLR -(SP)
_InsertMenu
;disable the line and Reverse Positions
MOVE.L GameMenuHndl(A5),-(SP)
MOVE #5,-(SP)
_DisableItem
MOVE.L GameMenuHndl(A5),-(SP)
MOVE #2,-(SP)
_DisableItem
;make difficulty menu
CLR.L -(SP) ;space for hndl result
MOVE #3,-(SP) ;menu id
PEA DiffMenuTitle
_NewMenu
MOVE.L (SP),DiffMenuHndl(A5)
PEA DiffMenuEasy ;add 'easy'
_AppendMenu
MOVE.L DiffMenuHndl(A5),-(SP)
PEA DiffMenuHard ;add 'hard'
_AppendMenu
MOVE.L DiffMenuHndl(A5),-(SP)
PEA GameMenuLine ;add a line
_AppendMenu
MOVE.L DiffMenuHndl(A5),-(SP)
PEA DiffMenuHelp ;add help
_AppendMenu
MOVE.L DiffMenuHndl(A5),-(SP)
CLR -(SP)
_InsertMenu
MOVE.L DiffMenuHndl(A5),-(SP) ;disable the line
MOVE #3,-(SP)
_DisableItem
;make Cheat menu
CLR.L -(SP) ;space for hndl result
MOVE #4,-(SP) ;menu id
PEA CheatMenuTitle
_NewMenu
MOVE.L (SP),CheatMenuHndl(A5)
PEA CheatMenuYou ;add player cheat
_AppendMenu
MOVE.L CheatMenuHndl(A5),-(SP)
PEA CheatMenuMe ;add computer cheat
_AppendMenu
MOVE.L CheatMenuHndl(A5),-(SP)
PEA GameMenuLine ;add a line
_AppendMenu
MOVE.L CheatMenuHndl(A5),-(SP)
PEA CheatMenuHelp;add help
_AppendMenu
MOVE.L CheatMenuHndl(A5),-(SP)
CLR -(SP)
_InsertMenu
MOVE.L CheatMenuHndl(A5),-(SP) ;disable line
MOVE #3,-(SP)
_DisableItem
_DrawMenuBar
;init the dialogs' item list handles
LEA aboutItemsList,A0
LEA aboutItemsHndl,A1
MOVE.L A0,(A1)
LEA diffItemsList,A0
LEA diffItemsHndl,A1
MOVE.L A0,(A1)
;make the "about" dialog, but don't show it (yet)
CLR.L -(SP) ;space for dialog ptr result
CLR.L -(SP) ;nil ptr (wStorage on heap)
PEA aboutRect;window coordinates
PEA aboutTitle ;title
MOVE #false,-(SP) ;not visible
MOVE #dBoxProc,-(SP) ;window type
MOVE.L #-1,-(SP);window in front
MOVE #false,-(SP) ;no closebox
MOVE.L #1,-(SP) ;reference value
PEA aboutItemsHndl
_NewDialog
MOVE.L (SP)+,aboutPointer(A5) ;save pointer
;make the difficulty dialog, but don't show it (yet).
;this dialog window is the same dialog used by the 'cheat'
; menu option
CLR.L -(SP) ;space for dialog ptr result
CLR.L -(SP) ;nil ptr (wStorage on heap)
PEA diffRect ;window coordinates
PEA diffTitle;title
MOVE #false,-(SP) ;not visible
MOVE #dBoxProc,-(SP) ;window type
MOVE.L #-1,-(SP);window in front
MOVE #false,-(SP) ;no closebox
MOVE.L #1,-(SP) ;reference value
PEA diffItemsHndl
_NewDialog
MOVE.L (SP)+,diffPointer(A5) ;save pointer
;make score window, but don't show it (yet)
CLR.L -(SP) ;space for window ptr result
CLR.L -(SP) ;nil ptr (wStorage on heap)
PEA sRect ;window coordinates
PEA sTitle ;title
MOVE #false,-(SP) ;not visible
MOVE #noGrowDocProc,-(SP) ;window type
MOVE.L #-1,-(SP);window in front
MOVE #false,-(SP) ;no closebox
MOVE.L #2,-(SP) ;reference value
_NewWindow
MOVE.L (SP)+,sPointer(A5);save pointer
;make game window and show it
CLR.L -(SP) ;space for window ptr result
CLR.L -(SP) ;nil ptr (wStorage on heap)
PEA wRect ;window coordinates
PEA wTitle ;title
MOVE #true,-(SP);visible
MOVE #noGrowDocProc,-(SP) ;window type
MOVE.L #-1,-(SP);window in front
MOVE #false,-(SP) ;no closebox
MOVE.L #3,-(SP) ;reference value
_NewWindow
;leave pointer on stack for _SetPort
MOVE.L (SP),wPointer(A5)
_SetPort ;make game window the active window
;set up to draw normal, 12 point Chicago text
_PenNormal
MOVE #0,-(SP) ;chicago
_TextFont
MOVE #0,-(SP) ;normal
_TextFace
MOVE #12,-(SP);12 point
_TextSize
;get a ptr on the heap for a string of length maxNumStrSize.
; the ptr is returned in A0. This string is used later.
; maxNumStrSize = length(max longint) ->
; length('±2147483648') = 11
MOVEQ #11,D0
_NewPtr
MOVE.L A0,strPointer(A5) ;save string ptr
;init randSeed with current time
MOVE.L (A5),A0 ;get QuickDraw globals ptr
MOVE.L Time,randSeed(A0)
;init some other stuff
CLR MeWon(A5)
CLR YouWon(A5)
CLR Cats(A5)
MOVE #true,GameOver(A5);no current game
MOVE #false,ShowFlag(A5) ;score currently
; not shown
MOVE #true,EasyFlag(A5);start off easy
MOVE #false,PlayerCheats(A5) ;no cheating
; to begin with
MOVE #false,ComputerCheats(A5)
;------------
GetEvent
;------------
_SystemTask
;wait for an event
CLR -(SP)
MOVE #everyEvent,-(SP)
PEA EventRecord
_GetNextEvent
MOVE (SP)+,D0
BEQ GetEvent
;what event was it?
MOVE What,D0
CMP #12,D0 ;12 = MaxEvents
BGE GetEvent
ASL #1,D0
MOVE EventTable(D0),D0
JMP EventTable(D0)
EventTable
DCGetEvent-EventTable ;null event #0
DCmouseDown-EventTable ;mDown #1
DCGetEvent-EventTable ;mouse up #2
DCkeyDown-EventTable;key down #3
DCGetEvent-EventTable ;key up #4
DCGetEvent-EventTable ;auto key #5
DCupdate-EventTable ;update event #6
DCGetEvent-EventTable ;disk event #7
DCGetEvent-EventTable ;activate #8
DCGetEvent-EventTable ;abort #9
DCGetEvent-EventTable ;network #10
DCGetEvent-EventTable ;I/O driver #11
;---------------
mouseDown
;---------------
CLR -(SP) ;space for integer result
MOVE.L Where,-(SP)
PEA fndWindow(A5)
_FindWindow;where was mouse pressed?
CLR.L D0
MOVE (SP)+,D0
ASL #1,D0
MOVE WindowTable(D0),D0
JMP WindowTable(D0)
;ignore mouse presses inDsk,inSystemWindow,inDragRegion,
; inGrowRegion and inGoAwayRegion.
WindowTable
DCGetEvent-WindowTable
DCinMenuBr-WindowTable
DCGetEvent-WindowTable
DCinContentRegion-WindowTable
DCGetEvent-WindowTable
DCGetEvent-WindowTable
DCGetEvent-WindowTable
;------------
inMenuBr
;------------
CLR.L -(SP) ;space for longint result
MOVE.L Where,-(SP)
_MenuSelect;which menu and which item?
MOVE (SP)+,menuChoiceID(A5)
MOVE (SP)+,menuItem(A5)
;jump to here for command keypress
DecodeMenuBar
CMPI #1,menuChoiceID(A5)
BEQ inAppleMenu
CMPI #2,menuChoiceID(A5)
BEQ inGameMenu
CMPI #3,menuChoiceID(A5)
BEQ inDiffMenu
CMPI #4,menuChoiceID(A5)
BEQ inCheatMenu
JMP GetEvent
;----------------
inAppleMenu
;----------------
CMPI #1,menuItem(A5) ;is it 'about' ?
BNE ReturnFromMenu
MOVE.L wPointer(A5),-(SP);unhilite current
; front wndw
MOVE #false,-(SP)
_HiliteWindow
;bring about wnds to the front, then show it and make it the
; current port
MOVE.L aboutPointer(A5),-(SP)
_BringToFront
MOVE.L aboutPointer(A5),-(SP)
_ShowWindow
MOVE.L aboutPointer(A5),-(SP)
_SetPort
;set up some static text in the 'about' dialog
MOVE #8,-(SP) ;typeface = outline
_TextFace
;center the text in the 'about' dialog.
;set up the 'windowWidth' variable used by CenterString
LEA aboutRect,A0
MOVE right(A0),windowWidth(A5) ;get right
MOVE left(A0),D0;get left
SUB D0,windowWidth(A5);width= right - left
;print title of program
LEA tttString,A0
JSR CenterString
MOVE D0,-(SP) ;h
MOVE #28,-(SP);v
_MoveTo
PEA tttString
_DrawString
;print 'written by...'
MOVE #0,-(SP) ;type face = normal
_TextFace
LEA wbString,A0
LEA wbRect,A1
MOVEQ #60,D0
JSR SnakeText
;print date written
LEA wwString,A0
LEA wwRect,A1
MOVEQ #80,D0
JSR SnakeText
;outline the default response ("OK") in the standard way
;note: the outline is drawn before the actual button is drawn
; (the button is drawn when _ModalDialog is called)
LEA OKrect,A0
JSR DefaultOutline
;wait for user response
CLR.L -(SP) ;nil ptr (no special filter routine)
PEA itemHit(A5)
_ModalDialog ;wait for click or <ret> keypress
MOVE itemHit(A5),D0
CMPI #1,D0 ;if they clicked 'OK' then leave
BEQ @2
PEA tyString ;else print 'Thank You'
PEA tyRect
MOVE #114,-(SP)
JSR GrowText
MOVEQ #15,D1 ;let them look at it for a while
@4 MOVE #30000,D0
@3 DBRA D0,@3
DBRA D1,@4
@2 MOVE.L aboutPointer(A5),-(SP) ;goodbye dialog
_HideWindow
ReturnFromMenu ;unhilite Apple menu
CLR -(SP)
_HiLiteMenu
JMP GetEvent
tttString DC.B 11,'Tic Tac Toe'
wbStringDC.B24,'written by Mike',170,' Scanlin',0
wbRect DC49,51,65,208
wwStringDC.B35,'28 February 1986 York University'
wwRect DC69,30,85,263
tyStringDC.B9,'Thank you'
tyRect DC103,110,119,179
itemHit DS1
;----------------
inGameMenu
;----------------
CMPI #1,menuItem(A5) ;new game
BEQ NewGame
CMPI #2,menuItem(A5) ;reverse positions
BEQ ReversePositions
CMPI #3,menuItem(A5) ;show/hide scores
BEQ ShowScores
CMPI #4,menuItem(A5) ;clear scores
BEQ ClearScores
; #5 is a line
CMPI #6,menuItem(A5) ;quit
BEQ Finish
JMP GetEvent
;------------
NewGame
;------------
MOVEQ #8,D0 ;clear the old board
LEA BoardCond,A0
@1 CLR (A0)+
DBRA D0,@1
MOVE #false,GameOver(A5)
;enable Reverse Positions
MOVE.L GameMenuHndl(A5),-(SP)
MOVE #2,-(SP)
_EnableItem
MOVEQ #2,D0 ;toss a coin to see who's first
JSR RandomBounds
CMPI #1,D1 ;on a 1, the player starts
BEQ @2;on a 2, the computer starts
CMPI #false,EasyFlag(A5)
BEQ @3;if diff=easy, choose randomly
MOVEQ #4,D1 ; else take middle square
BRA @4
;randomly choose a square to start in
@3 MOVEQ#9,D0
JSR RandomBounds
SUBQ #1,D1 ;number from 0-8
@4 ASL #1,D1 ;D1 = table offset
LEA BoardCond,A0
MOVE #square,(A0,D1)
@2 JSR DrawWindowContents
JMP ReturnFromMenu
;----------------------------
DrawWindowContents
;----------------------------
MOVE.L wPointer(A5),-(SP)
_SetPort
;draw board
MOVE #2,-(SP)
MOVE #2,-(SP)
_PenSize ;pen = 2x2 square
MOVEQ #3,D3 ;number of lines to draw - 1
LEA BoardLines,A2
@1 MOVE (A2)+,-(SP)
MOVE (A2)+,-(SP)
_MoveTo;set pen location
MOVE (A2)+,-(SP)
MOVE (A2)+,-(SP)
_LineTo;draw line
DBRA D3,@1
;draw pieces. Board is numbered like this:
; 0 1 2
;3 4 5
; 6 7 8
MOVEQ #8,D3 ;number of positions to check -1
LEA BoardLocs,A2
LEA BoardCond,A3
@4 MOVE.L A2,-(SP) ;A2 points to rect
_EraseRect ;erase old piece
MOVE (A3)+,D0
BEQ @2;if empty, do next position
MOVE.L A2,-(SP) ;points to which square
CMPI #square,D0 ;is it a square?
BNE @3
_FrameRect ;draw square
BRA @2
@3 _FrameOval ;draw circle
@2 ADDA #8,A2 ;point to next position in table
DBRA D3,@4
MOVE #1,-(SP)
MOVE #1,-(SP)
_PenSize ;restore normal size pen
RTS
BoardCond DCB 9,0 ;0=empty, 1=sqaure, 2=circle
BoardLocs DC30,60,62,92 ;sqaure 0 coordinates
DC30,113,62,145 ;1
DC30,167,62,199 ;2
DC83,60,115,92 ;3
DC83,113,115,145;4
DC83,167,115,199;5
DC137,60,169,92 ;6
DC137,113,169,145 ;7
DC137,167,169,199 ;8
BoardLinesDC49,71,209,71 ;(h1,v1) (h2,v2)
DC49,125,209,125
DC101,19,101,179
DC155,19,155,179
;-----------------------
ReversePositions
;-----------------------
;switch all squares and circles
MOVEQ #8,D0 ;number of positions to check -1
LEA BoardCond,A0
@1 CMPI #empty,(A0)+ ;if empty,
; don't bother switching
BEQ @4
;code for square=1, code for circle=2
; 3 EOR 1 = 2; and 3 EOR 2 = 1
EORI #3,-2(A0);switch pieces
@4 DBRA D0,@1
JSR DrawWindowContents;draw new board
JMP ReturnFromMenu
;----------------
ShowScores
;----------------
; If the scores are not shown, then show them. If they are
; currently shown, then hide them. In either case, toggle the
; menu option from show to hide or visa versa.
MOVE.L sPointer(A5),-(SP);for Show/Hide
CMPI #false,ShowFlag(A5) ;test flag
BEQ @2
_HideWindow
LEA ShowText,A0
BRA @1
@2 _ShowWindow
JSR DrawScoreWindow
LEA HideText,A0
@1 MOVE.L GameMenuHndl(A5),-(SP)
MOVE #3,-(SP) ;item number to change
MOVE.L A0,-(SP) ;pointer to item text
_SetItem
; flip the condition flag.
; true EOR true = false; and true EOR false = true.
EORI #true,ShowFlag(A5)
JMP ReturnFromMenu
ShowFlagDS1
ShowTextDC.B10,'Show Score',0
HideTextDC.B10,'Hide Score',0
;---------------
ClearScores
;---------------
CLR MeWon(A5)
CLR YouWon(A5)
CLR Cats(A5)
JSR DrawScoreWindow
JMP ReturnFromMenu
;-------------
inDiffMenu
;-------------
CMPI #1,menuItem(A5)
BEQ EasyGame
CMPI #2,menuItem(A5)
BEQ HardGame
; #3 is a line
CMPI #4,menuItem(A5)
BEQ DiffHelp
JMP GetEvent
;--------------
EasyGame
;--------------
;if they selected a difficulty level, change the menu
; appearance accordingly
MOVE.L DiffMenuHndl(A5),-(SP) ;check easy
MOVE #1,-(SP)
MOVE #true,-(SP)
_CheckItem
MOVE.L DiffMenuHndl(A5),-(SP) ;uncheck hard
MOVE #2,-(SP)
MOVE #false,-(SP)
_CheckItem
MOVE #true,EasyFlag(A5);difficulty=easy
JMP ReturnFromMenu
;-------------
HardGame
;-------------
MOVE.L DiffMenuHndl(A5),-(SP) ;uncheck easy
MOVE #1,-(SP)
MOVE #false,-(SP)
_CheckItem
MOVE.L DiffMenuHndl(A5),-(SP) ;check hard
MOVE #2,-(SP)
MOVE #true,-(SP)
_CheckItem
MOVE #false,EasyFlag(A5) ;difficulty=hard
JMP ReturnFromMenu
;---------
DiffHelp
;---------
MOVE.L wPointer(A5),-(SP) ;unhilite current
; front wndw
MOVE #false,-(SP)
_HiliteWindow
;bring diff wndw to the front, show it and then make it the
; current port
MOVE.L diffPointer(A5),-(SP)
_BringToFront
MOVE.L diffPointer(A5),-(SP)
_ShowWindow
MOVE.L diffPointer(A5),-(SP)
_SetPort
;set up some static text
MOVE #8,-(SP) ;text face = outline
_TextFace
LEA diffRect,A0
MOVE right(A0),windowWidth(A5) ;get right
MOVE left(A0),D0
SUB D0,windowWidth(A5);subtract left
LEA diffString,A0
JSR CenterString
MOVE D0,-(SP) ;h
MOVE #28,-(SP);v
_MoveTo
PEA diffString
_DrawString
MOVE #0,-(SP) ;text face = normal
_TextFace
MOVE #20,-(SP)
MOVE #70,-(SP)
_MoveTo
PEA easyString
_DrawString
MOVE #20,-(SP)
MOVE #95,-(SP)
_MoveTo
PEA hardString
_DrawString
;outline the default response ("OK") in the standard way
LEA OKrect,A0
JSR DefaultOutline
;get user response
CLR.L -(SP) ;nil ptr (no special filter routine)
PEA itemHit(A5)
_ModalDialog ;wait for response
MOVE.L diffPointer(A5),-(SP)
_HideWindow
JMP ReturnFromMenu
diffStringDC.B 10,'Difficulty',0
easyStringDC.B 30,'Easy: computer moves randomly',0
hardStringDC.B 34,'Hard: computer thinks about
moves',0
;----------------
inCheatMenu
;----------------
CMPI #1,menuItem(A5)
BEQ PlayerCheat
CMPI #2,menuItem(A5)
BEQ ComputerCheat
;#3 is a line
CMPI #4,menuItem(A5)
BEQ CheatHelp
JMP GetEvent
;---------------
PlayerCheat
;---------------
;toggle player cheat flag
EORI #true,PlayerCheats(A5)
;check/uncheck player cheats
MOVE.L CheatMenuHndl(A5),-(SP)
MOVE #1,-(SP)
MOVE PlayerCheats(A5),-(SP)
_CheckItem
JMP ReturnFromMenu
;-------------------
ComputerCheat
;-------------------
;toggle computer cheat flag
EORI #true,ComputerCheats(A5)
MOVE.L CheatMenuHndl(A5),-(SP)
MOVE #2,-(SP)
MOVE ComputerCheats(A5),-(SP)
_CheckItem
JMP ReturnFromMenu
;------------
CheatHelp
;------------
MOVE.L wPointer(A5),-(SP);unhilite current
; front wndw
MOVE #false,-(SP)
_HiliteWindow
;bring cheat wndw (which is the diff wndw) it to the front,
; show it and then make it the current port
MOVE.L diffPointer(A5),-(SP)
_BringToFront
MOVE.L diffPointer(A5),-(SP)
_ShowWindow
MOVE.L diffPointer(A5),-(SP)
_SetPort
;set up some static text
MOVE #8,-(SP) ;text face = outline
_TextFace
LEA diffRect,A0
MOVE right(A0),windowWidth(A5) ;get right
MOVE left(A0),D0
SUB D0,windowWidth(A5);subtract left
LEA cheatString,A0
JSR CenterString
MOVE D0,-(SP) ;h
MOVE #28,-(SP);v
_MoveTo
PEA cheatString
_DrawString
MOVE #0,-(SP) ;text face = normal
_TextFace
;draw some strings
MOVE #30,-(SP)
MOVE #65,-(SP)
_MoveTo
PEA inAnyCase1
_DrawString
MOVE #30,-(SP)
MOVE #90,-(SP)
_MoveTo
PEA inAnyCase2
_DrawString
MOVE #30,-(SP)
MOVE #115,-(SP)
_MoveTo
PEA inAnyCase3
_DrawString
;outline the default response ("OK") in the standard way
LEA OKrect,A0
JSR DefaultOutline
;get user response
CLR.L -(SP) ;nil ptr (no special filter routine)
PEA itemHit(A5)
_ModalDialog ;wait for response
MOVE.L diffPointer(A5),-(SP)
_HideWindow
JMP ReturnFromMenu
cheatString DC.B 8,'Cheating',0
inAnyCase1DC.B 33,'Anyone who is allowed to cheat is'
inAnyCase2DC.B 32,'permitted to move on any square,',0
inAnyCase3DC.B 33,'even if someone is already there.'
;---------------------
inContentRegion
;---------------------
;did they click in the game window?
MOVE.L wPointer(A5),D0
CMP.L fndWindow(A5),D0
BNE @3;no, ignore click
CMPI #true,GameOver(A5);is a game in
; progress?
BEQ @3;no, ignore click
;player makes a move
;----------------------------
@10PEA Where ;where did they click?
_GlobalToLocal
MOVEQ #8,D3 ;check all 9 squares
LEA BoardCond,A2
LEA BoardLocs,A3
@1 CLR -(SP) ;space for boolean result
MOVE.L Where,-(SP)
MOVE.L A3,-(SP) ;square coords
_PtInRect;is pt in this square?
MOVE (SP)+,D0
BNE @2;yes, we have a valid click
ADDA #2,A2 ;update BoardCond pointer
ADDA #8,A3 ;update BoardLocs pointer
DBRA D3,@1
BRA @3;not in square -- ignore click
;if player cheating is allowed, skip check
@2 CMPI #true,PlayerCheats(A5)
BEQ @15
MOVE (A2),D0 ;check if taken
BNE @3;if not empty then ignore click
@15MOVE #circle,(A2) ;put the player there
JSR DrawWindowContents
JSR CheckWin ;check if player has won
CMPI #true,D0 ;if player didn't win, then
BNE @7; computer will make a move
ADDQ #1,YouWon(A5)
BRA @11
;computer makes a move
;---------------------------------
;if the board is full, then cats game
@7 LEA BoardCond,A0
MOVEQ #8,D0
@4 MOVE (A0)+,D1
BEQ @5;if we get one empty, then stop
DBRA D0,@4
BRA @12 ;cats game -- all positions used
@5 CMPI #false,EasyFlag(A5)
BNE @16 ;choose randomly if not thinking
JSR ComputerThinks
CMPI #true,MadeMove(A5);if we didn't move
; when thinking
BEQ @14 ; then choose
; randomly
@16LEA BoardCond,A2
@6 MOVEQ#9,D0
JSR RandomBounds
SUBQ #1,D1 ;get a number in the range 0-8
ASL #1,D1 ;calc table offset
CMPI #true,ComputerCheats(A5)
BNE @18 ;if not cheating,
; make sure it's empty
CMPI #square,(A2,D1) ;if cheating, make
;sure it's not a square.
BEQ @6;yes, it's a square.
; try again.
BRA @17 ;take it (it's a circle or empty)
@18CMPI #empty,(A2,D1)
BNE @6;this space is full. try again.
@17MOVE #square,(A2,D1) ;put a square here.
@14JSR DrawWindowContents
JSR CheckWin ;check if computer has won
CMPI #true,D0
BNE @8
ADDQ #1,MeWon(A5)
BRA @11
@8 MOVEQ#8,D0 ;did comp make last
; possible move?
LEA BoardCond,A0 ;look for empty positions
@9 MOVE (A0)+,D1
BEQ @3;there's still space left,
; game not over
DBRA D0,@9
@12ADDQ #1,Cats(A5);cats game
@11MOVE #true,GameOver(A5)
;disable Reverse Positions
MOVE.L GameMenuHndl(A5),-(SP)
MOVE #2,-(SP)
_DisableItem
JSR DrawScoreWindow ;update score wndw
@3 JMP GetEvent
;---------------------
ComputerThinks
;---------------------
; output A2 = points to BoardCond
; D1 = 2 * square to move to (0-8) [BoardCond table offset]
; priority of checks:
; 1st - check if computer can win in next move
; 2nd - check if computer can block player win in next move
; 3rd - check if middle square is taken
; 4th - decide randomly
; computer's piece = square, player = circle
MOVE #false,MadeMove(A5)
;check 8 ways of winning
MOVEQ #7,D0
LEA offsets,A1
LEA BoardCond,A0
MOVEQ #square,D1
@1 MOVE (A1)+,A2
MOVE (A1)+,A3
MOVE (A1)+,A4
JSR CheckPosition
CMPI #true,MadeMove(A5)
BEQ @5
DBRA D0,@1
;check 8 ways of blocking
MOVEQ #7,D0
LEA offsets,A1
LEA BoardCond,A0
MOVEQ #circle,D1
@2 MOVE (A1)+,A2
MOVE (A1)+,A3
MOVE (A1)+,A4
JSR CheckPosition
CMPI #true,MadeMove(A5)
BEQ @5
DBRA D0,@2
;check if middle square is taken
CMPI #true,ComputerCheats(A5)
BNE @3
CMPI #square,(A0,A3)
BEQ @5
@4 MOVE #square,(A0,A3)
MOVE #true,MadeMove(A5)
BRA @5
@3 CMPI #empty,(A0,A3)
BEQ @4
@5 RTS
offsets DC0,6,12,2,8,14,4,10,16
DC0,2,4,6,8,10,12,14,16
DC0,8,16,4,8,12
MadeMoveDS1
;------------------
CheckPosition
;------------------
; used to see if any 2 of a (A0,A2), b (A0,A3) and c (A0,A4) are
; occupied by the computer. If so, it tries to take the 3rd (to
; make a win). If the computer is allowed to cheat, it takes the
; 3rd regardless of what's already there. On defense, this
; routine is used to check if the computer can take the 3rd
; square to block a player win. The boolean variable MadeMove
; reflects whether a move was made after this procedure has
; finished.
CMP (A0,A2),D1 ;if a and b and (not c)
BNE @2
CMP (A0,A3),D1
BNE @2
CMPI #true,ComputerCheats(A5)
BNE @3
CMPI #square,(A0,A4)
BEQ @2
@4 MOVE #square,(A0,A4)
MOVE #true,MadeMove(A5)
BRA @8
@3 CMPI #empty,(A0,A4)
BEQ @4
@2 CMP (A0,A2),D1 ;if a and c and (not b)
BNE @5
CMP (A0,A4),D1
BNE @5
CMPI #true,ComputerCheats(A5)
BNE @6
CMPI #square,(A0,A3)
BEQ @5
@7 MOVE #square,(A0,A3)
MOVE #true,MadeMove(A5)
BRA @8
@6 CMPI #empty,(A0,A3)
BEQ @7
@5 CMP (A0,A3),D1 ;if b and c and (not a)
BNE @8
CMP (A0,A4),D1
BNE @8
CMPI #true,ComputerCheats(A5)
BNE @9
CMPI #square,(A0,A2)
BEQ @8
@10MOVE #square,(A0,A2)
MOVE #true,MadeMove(A5)
BRA @8
@9 CMPI #empty,(A0,A2)
BEQ @10
@8 RTS
;-----------
keyDown
;-----------
BTST #cmdKey,Modifiers ;was it a
; command key?
BEQ @1;no, ignore keypress
CLR.L -(SP) ;space for longint result
MOVE Message+2,-(SP) ;key that was
; pressed
_MenuKey
MOVE (SP)+,menuChoiceID(A5)
MOVE (SP)+,menuItem(A5)
JMP DecodeMenuBar
@1 JMP GetEvent
;--------
update
;--------
; redraw the contents of the game window
MOVE.L wPointer(A5),-(SP)
_BeginUpdate
JSR DrawWindowContents
MOVE.L wPointer(A5),-(SP)
_EndUpdate
JMP GetEvent
;-------
Finish
;-------
; release memory occupied windows and dialogs
MOVE.L aboutPointer(A5),-(SP)
_DisposDialog
MOVE.L diffPointer(A5),-(SP)
_DisposDialog
MOVE.L sPointer(A5),-(SP)
_DisposWindow
MOVE.L wPointer(A5),-(SP)
_DisposWindow
RTS
;--------
resume
;--------
; if the "resume" button is clicked, return to finder.
; this could be dangerous if the program seriously messed up
; RAM in some way
_ExitToShell
;------------
CheckWin
;------------
; check if someone has won (if there are 3 in a row of any
; non-blank) output: D0 = false if no win, true if won.
LEA BoardCond,A0
MOVE (A0),D0 ;check row 1
AND 2(A0),D0
AND 4(A0),D0
BNE Win
@1 MOVE 6(A0),D0 ;check row 2
AND 8(A0),D0
AND 10(A0),D0
BNE Win
@2 MOVE 12(A0),D0;check row 3
AND 14(A0),D0
AND 16(A0),D0
BNE Win
@3 MOVE (A0),D0 ;check column 1
AND 6(A0),D0
AND 12(A0),D0
BNE Win
@4 MOVE 2(A0),D0 ;check column 2
AND 8(A0),D0
AND 14(A0),D0
BNE Win
@5 MOVE 4(A0),D0 ;check column 3
AND 10(A0),D0
AND 16(A0),D0
BNE Win
@6 MOVE (A0),D0 ;check neg slope diagonal
AND 8(A0),D0
AND 16(A0),D0
BNE Win
@7 MOVE 4(A0),D0 ;check pos slope diagonal
AND 8(A0),D0
AND 12(A0),D0
;at this point, if D0=0 then (D0=false and leave) else set
; D0=true
BEQ NoWin
WinMOVE #true,D0
NoWin RTS
;--------------------
RandomBounds
;--------------------
; input D0 = max number to return
; output D1 = (random mod D0) + 1 (always positive)
MOVE D0,-(SP) ;save D0
CLR -(SP) ;space for integer result
_Random
MOVE (SP)+,D1 ;get random num
ANDI #$7FFF,D1;result = pos 15-bit num
MOVE (SP)+,D0 ;restore D0
DIVS D0,D1
SWAP D1;remainder (MOD) is in low word
ADDQ #1,D1
RTS
;-----------------------
DrawScoreWindow
;-----------------------
MOVE.L sPointer(A5),-(SP);make score
;wndw current port
_SetPort
;erase old scores
LEA scoreRect,A0
MOVE.L A0,-(SP)
_EraseRect
;do computer's score
MOVE #15,-(SP);set pen location
MOVE #15,-(SP)
_MoveTo
PEA 'Me:' ;draw a string
_DrawString
MOVE.L strPointer(A5),A0 ;convert score to
; string
CLR.L D0
MOVE MeWon(A5),D0
JSR NumToString
CLR -(SP) ;right justify the number
MOVE.L strPointer(A5),-(SP)
_StringWidth
MOVEQ #75,D0
SUB (SP)+,D0
MOVE D0,-(SP) ;h
MOVE #15,-(SP);v
_MoveTo
MOVE.L strPointer(A5),-(SP);draw the score
_DrawString
;do player's score
MOVE #15,-(SP)
MOVE #30,-(SP)
_MoveTo
PEA 'You:'
_DrawString
MOVE.L strPointer(A5),A0
CLR.L D0
MOVE YouWon(A5),D0
JSR NumToString
CLR -(SP)
MOVE.L strPointer(A5),-(SP)
_StringWidth
MOVEQ #75,D0
SUB (SP)+,D0
MOVE D0,-(SP) ;h
MOVE #30,-(SP);v
_MoveTo
MOVE.L strPointer(A5),-(SP)
_DrawString
;do cats games
MOVE #15,-(SP)
MOVE #45,-(SP)
_MoveTo
PEA 'Cats:'
_DrawString
MOVE.L strPointer(A5),A0
CLR.L D0
MOVE Cats(A5),D0
JSR NumToString
CLR -(SP)
MOVE.L strPointer(A5),-(SP)
_StringWidth
MOVEQ #75,D0
SUB (SP)+,D0
MOVE D0,-(SP) ;h
MOVE #45,-(SP);v
_MoveTo
MOVE.L strPointer(A5),-(SP)
_DrawString
;set game window to be the active port
MOVE.L wPointer(A5),-(SP)
_SetPort
RTS
scoreRect DC1,50,60,75
;----------------------
GetRandomString
;----------------------
; get a random number, then fall through to NumToString to
; covert to string
CLR -(SP) ;space for integer result
_Random
CLR.L D0;set up D0 as longint for _NumToString
MOVE (SP)+,D0
MOVE.L strPointer(A5),A0 ;addr of string
;----------------
NumToString
;----------------
; convert a number to a string
; input D0 = number
; A0 = addr of string to put number in
MOVE #0,-(SP) ;routine selector
_Pack7 ;_Pack7 = _NumToString
;A0 now points to string with the number (unchanged from
; before)
RTS
;---------------
CenterString:
;---------------
; input A0 = addr of string
; windowWidth = width of window to center in
; (should be set before calling this routine)
CLR -(SP) ;space for integer result
MOVE.L A0,-(SP)
_StringWidth
MOVE windowWidth(A5),D0
SUB (SP)+,D0
; D0 = offset to start printing
ASR #1,D0 ;(windowWidth - StrWidth) div 2
RTS
windowWidth:DS 1
;------------------
DefaultOutline
;------------------
;make a thick oval around a button to show that it is the default
; (the one that will be selected if RETURN or ENTER is
; pressed).
; input: A0 = pointer to the rect to draw around
LEA workRect(A5),A1 ;make a copy of
; the rect
MOVE.L (A0)+,(A1)+
MOVE.L (A0),(A1)
PEA penState(A5) ;save penState
_GetPenState
MOVE #3,-(SP) ;make a fat pen
MOVE #3,-(SP)
_PenSize
PEA workRect(A5) ;make rect bigger
MOVE #-4,-(SP)
MOVE #-4,-(SP)
_InsetRect
PEA workRect(A5) ;make the fat border
MOVE #16,-(SP)
MOVE #16,-(SP)
_FrameRoundRect
PEA penState(A5) ;restore penState
_SetPenState
RTS
workRectDS4
penStateDS9
;--------------
; other stuff
;--------------
wTitle DC.B11,'Tic Tac Toe'
wRect DC60,120,260,380
wPointerDS.L1
sTitle DC.B5,'Score'
sRect DC60,401,112,491
sPointerDS.L1
aboutTitleDC.B 1,' '
aboutRect DC85,107,256,397
aboutPointerDS.L 1
aboutItemsHndl DC.L1
aboutItemsList DC3 ;number of items in list - 1
DC.L 0 ;button #1
OKrect DC145,213,165,278
DC.B 4 ;ctrlItem+butCtrl
DC.B 2,'OK'
DC.L 0 ;button #2
DC145,10,165,130
DC.B 4 ;ctrlItem+butCtrl
DC.B 11,'That',39,'s nice',0
DC.L 0 ;Mike icon
DC1,1,32,32
DC.B 160 ;iconItem+itemDisable
DC.B 2
DC128
DC.L 0 ;Fishtree icon
DC1,258,32,289
DC.B 160 ;iconItem+itemDisable
DC.B 2
DC129
diffTitle DC.B 1,' '
diffRectDC85,107,256,397
diffPointer DS.L 1
diffItemsHndl DC.L1
diffItemsList DC0 ;number of items in list - 1
DC.L 0 ;button #1
DC145,213,165,278
DC.B 4 ;ctrlItem+butCtrl
DC.B 2,'OK'
EventRecord
What DC0
Message DC.L0
When DC.L0
Where DC.L0
Modifiers DC0
AppleMenuHndl DS.L1
AppleMenuTitle DC.B1,20
AppleMenuAbout DC.B17,'About Tic Tac Toe'
GameMenuHndlDS.L 1
GameMenuTitle DC.B4,'Game',0
GameMenuNew DC.B 10,'New Game/N',0
GameMenuRev DC.B 19,'Reverse Positions/R'
GameMenuShowDC.B 12,'Show Score/S',0
GameMenuClear DC.B12,'Clear Scores',0
GameMenuLineDC.B 1,'-'
GameMenuQuitDC.B 6,'Quit/Q',0
DiffMenuHndlDS.L 1
DiffMenuTitle DC.B10,'Difficulty',0
DiffMenuEasyDC.B 6,'Easy!',18,0
DiffMenuHardDC.B 4,'Hard',0
DiffMenuHelpDC.B 25,'What',39,'s with these
levels?'
CheatMenuHndl DS.L1
CheatMenuTitle DC.B6,'Cheats',0
CheatMenuYouDC.B 23,'Player Allowed to Cheat'
CheatMenuMe DC.B 25,'Computer Allowed to Cheat'
CheatMenuHelp DC.B25,'What',39,'s all this
Cheating?'
menuChoiceIDDS 1
menuItemDS1
strPointerDS.L 1
fndWindow DS.L 1
EasyFlagDS1
PlayerCheatsDS 1
ComputerCheats DS1
MeWon DS1
YouWon DS1
Cats DS1
GameOverDS1
resource 'ICON' 128 'MIKEFACE'
DC.L $003FF800,$03FFFF80,$0FFFFFE0,$1FFFFFF0
DC.L $1FFFFFF0,$3E3FF878,$3803C038,$3000001C
DC.L $70FC3F1C,$7303C0DC,$74624C3C,$7DF25F3C
DC.L $7C652C3C,$3C05A03C,$3A08905C,$39F0CF98
DC.L $18004010,$08077010,$0407D820,$04000020
DC.L $04015020,$0406FA20,$04176D20,$021DFF20
DC.L $02100240,$011FFE40,$01080480,$0087F880
DC.L $00400100,$00200200,$00180C00,$0007F000
resource 'ICON' 129 'FISHTREE'
DC.L $00670000,$0098E7C0,$03021830,$04010388
DC.L $0F008C08,$1878B008,$10848008,$13021F04
DC.L $14012084,$08194044,$08641C48,$08842210
DC.L $090C2110,$061010E0,$00100800,$00100800
DC.L $00200400,$0021C400,$00222400,$00200400
DC.L $0021C400,$00222400,$00100400,$00160800
DC.L $00120800,$00088800,$00085000,$00045000
DC.L $00045000,$0002A000,$0001C000,$00008000
RESOURCE FILE
LINK
FILE
[
TicTacToe
SnakeText
GrowText
/RESOURCES
TicTacToe_rsrc
$