TicTacToe Game Simulator
Solution
;-------------------------------------------------------------
; Name: XXXXXXXXX
; Date: 12/8/2019
; Description:
; Program that simulates the game of TicTacToe
;
;-------------------------------------------------------------
TITLE TicTacToe.asm
INCLUDE irvine32.inc
clearBoard PROTO,
pBoard: PTR BYTE
printBoard PROTO,
pBoard: PTR BYTE,
x: DWORD,
Y: DWORD
getPiece PROTO,
pBoard: PTR BYTE,
x: DWORD,
Y: DWORD
setPiece PROTO,
pBoard: PTR BYTE,
x: DWORD,
Y: DWORD,
letter: BYTE
setRandomPos PROTO,
pBoard: PTR BYTE,
letter: BYTE
readPosition PROTO,
pBoard: PTR BYTE,
promptx: PTR BYTE,
prompty: PTR BYTE,
errorMsg: PTR BYTE
isGameDraw PROTO,
pBoard: PTR BYTE
isGameWin PROTO,
pBoard: PTR BYTE,
x: DWORD,
Y: DWORD
printWinningMove PROTO,
pBoard: PTR BYTE,
x: DWORD,
Y: DWORD,
winType: DWORD
PrintStats PROTO,
pStats:PTR STATS,
pStatsMsgs: PTR DWORD
.DATA
; Player types
USER EQU 0
COMPUTER EQU 1
; Winning types
NONE EQU 0
ROW EQU 1
COL EQU 2
DIAG1 EQU 3
DIAG2 EQU 4
; Player structure definition
PLAYER STRUCT
typ BYTE ?
letter BYTE?
num BYTE?
PLAYER ENDS
; Statistics structure
STATS STRUCT
playedPvP DWORD ?
playedPvC DWORD?
played CoC DWORD?
player1Wins DWORD?
player2Wins DWORD?
Does the computer win DWORD?
draws DWORD?
STATS ENDS
init_menu BYTE "1. Player Vs. Computer", 0dh, 0ah
BYTE "2. Player Vs. Player", 0dh, 0ah
BYTE "3. Computer Vs. Computer", 0dh, 0ah
BYTE "4. Show statistics", 0dh, 0ah
BYTE "0. Exit", 0dh, 0ah, 0
init_prompt BYTE "Option?: ", 0
opt_error BYTE "Invalid option. Please try again.", 0dh, 0ah,0
prompt_y BYTE "Row?: ",0
prompt_x BYTE "Column?: ",0
error_pos BYTE "Invalid position. Please try again.", 0dh, 0ah,0
turnMsg BYTE "Player "
turnPlayer BYTE " "
BYTE " turn", 0dh, 0ah,0
drawMsg BYTE "Game Over. The game is a draw.", 0dh, 0ah,0
winMsg BYTE "Player "
winPlayer BYTE " "
BYTE " Wins!", 0dh, 0ah,0
endMsg BYTE "Do you want to play again (y/n)? ",0
statsMsg1 BYTE " Statistics", 0dh, 0ah
BYTE "Games played:", 0dh, 0ah
BYTE " Player vs Player: ",0
statsMsg2 BYTE " Player vs Computer: ",0
statsMsg3 BYTE " Computer vs Computer: ",0
statsMsg4 BYTE "Games won:", 0dh, 0ah
BYTE " Player 1: ",0
statsMsg5 BYTE " Player 2: ",0
statsMsg6 BYTE " Computer: ",0
statsMsg7 BYTE "Games ending in a draw: ",0
statsMsgs DWORD statsMsg1,statsMsg2,statsMsg3,statsMsg4,statsMsg5,statsMsg6,statsMsg7
board BYTE 0,0,0
BYTE 0,0,0
BYTE 0,0,0
player1 PLAYER<,,1>
player2 PLAYER<,,2>
selected DWORD -1
selected DWORD -1
statistics STATS<0,0,0,0,0,0,0>
.CODE
main PROC
call Randomize ; initialize random generator
mainLoop:
call ClrScr
; Print initial menu
mov edx, OFFSET init_menu ; load string address
call WriteString ; print string using irvine library
; Print prompt for option
mov edx, OFFSET init_prompt ; load string address
call WriteString ; print string using irvine library
; Read option
call ReadInt
cmp eax, 0 ; If user selected 0
je terminate ; terminate program
cmp eax, 1 ; If user selected 1
je play1 ; play against computer
cmp eax, 2 ; If user selected 2
je play2 ; play two users
cmp eax, 3 ; If user selected 3
je play3 ; play only computer
cmp eax, 4 ; If user selected 4
je showStats ; show stats
; Print invalid option
mov edx, OFFSET opt_error ; load string address
call WriteString ; print string using irvine library
call WaitMsg
jmp mainLoop ; start again
showStats:
; print statistics
Invoke PrintStats, ADDR statistics, ADDR statsMsgs
jmp mainLoop
play1:
mov player1.typ, USER ; set first player as user
mov player2.typ, COMPUTER ; set second player as computer
jmp startGame
play2:
mov player1.typ, USER ; set first player as user
mov player2.typ, USER ; set second player as user
jmp startGame
play3:
mov player1.typ, COMPUTER ; set first player as computer
mov player2.typ, COMPUTER ; set second player as computer
startGame:
; update stats
cmp player1.typ, USER ; if player 1 is a user
je chkPvP ; check if 2 is also a user
inc DWORD PTR[statistics.playedCvC] ; else, increment cvc games
jmp continueGame
chkPvP:
cmp player2.typ, USER ; if player 2 is a user
jne updPvC ; if not, update pvp
inc DWORD PTR[statistics.playedPvP] ; increment pvp games
jmp continueGame
updPvC:
inc DWORD PTR[statistics.playedPvC] ; increment pvc games
continueGame:
Invoke clearBoard, ADDR board ; clear the board
mov DWORD PTR[selectedx], -1 ; invalidate selected position
mov DWORD PTR[selectedy], -1
call ClrScr
Invoke printBoard, ADDR board, -1, -1 ; print initial board
call Random32 ; generate random number
test eax, 1 ; see if number is odd
jne secondFirst ; if odd, player 2 goes first
mov player1.letter, 'x' ; player1 goes first
mov player2.letter, 'o' ; player2 goes second
mov esi, OFFSET player1 ; set current player as player1
mov edi, OFFSET player2 ; set next player as player2
jmp gameLoop
secondFirst:
mov player2.letter, 'x' ; player2 goes first
mov player1.letter, 'o' ; player1 goes second
mov esi, OFFSET player2 ; set current player as player2
mov edi, OFFSET player1 ; set next player as player1
gameLoop:
cmp (PLAYER PTR[esi]).typ, USER ; see if current player is a user
jne compPlay ; if it's a computer player, make automatic move
mov al, (PLAYER PTR[esi]).num ; get player number
add al,'0' ; convert to ascii
mov [turnPlayer], al ; set player number in message
; Print turn
mov edx, OFFSET turnMsg ; load string address
call WriteString ; print string using irvine library
; read position from the user
Invoke read position, ADDR board, ADDR prompt_x, ADDR prompt_y, ADDR error_pos
xor ebx, ebx ; clear ebx
mov bl, ah ; save y position in ebx
mov ah, 0 ; clear y position to leave only x in eax
mov ecx, eax ; save x position in ecx
mov selectedX, ecx ; set selection
mov selectedY, ebx
; save piece in selected position
mov al, (PLAYER PTR[esi]).letter ; get letter
Invoke setPiece, ADDR board, ecx, ebx, al
jmp chgTurn
compPlay: ; computer play
Invoke getPiece, ADDR board, 1, 1 ; get center board position piece
cmp eax, 0 ; see if position was empty
jne randPos ; if not, generate random position
; save piece in center position
mov al, (PLAYER PTR[esi]).letter ; get letter
Invoke setPiece, ADDR board, 1, 1, al
mov DWORD PTR[selectedX], 1 ; set selected piece
mov DWORD PTR[selectedY], 1
jmp chgTurn ; continue game
randPos:
; set a position at random
mov al, (PLAYER PTR[esi]).letter ; get letter
Invoke setRandomPos, ADDR board, al
xor ebx, ebx ; clear ebx
mov bl, ah ; save y position in ebx
mov ah, 0 ; clear y position to leave only x in eax
mov [selectedX], eax ; set selection
mov [selectedY], ebx
chgTurn:
Invoke isGameWin, ADDR board, selectedX, selectedY ; check if the game is a win
cmp eax, 0
jne gameWin ; if so, got to gameWin
Invoke isGameDraw, ADDR board ; check if the game is a draw
cmp eax, 1
je gameDraw ; if so, got to gamedraw
call ClrScr
Invoke print board, ADDR board selected X, selected; print current board
cmp (PLAYER PTR[esi]).typ, COMPUTER ; see if current player is a computer
jne updTurn ; if not, skip
cmp (PLAYER PTR[edi]).typ, COMPUTER ; see if next player is a computer
jne updTurn ; if not, skip
mov al, (PLAYER PTR[esi]).num ; get player number
add al,'0' ; convert to ascii
mov [turnPlayer], al ; set player number in message
; Print turn
mov edx, OFFSET turnMsg ; load string address
call WriteString ; print string using irvine library
mov ax, 1000; if both are computers, wait for a second
call Delay
updTurn:
xchg esi, edi ; change player turn
jmp gameLoop
gameDraw:
call ClrScr
Invoke printBoard, ADDR board, -1, -1 ; print current board
; Print game draw
mov edx, OFFSET drawMsg ; load string address
call WriteString ; print string using irvine library
; update stats
inc DWORD PTR[statistics.draws]
jmp playAgain
gameWin:
call ClrScr
Invoke printBoard, ADDR board, -1, -1 ; print current board
Invoke printWinningMove, ADDR board, selectedX, selectedY, eax
mov al, (PLAYER PTR[esi]).num ; get player number
add al,'0' ; convert to ascii
mov [winPlayer], al ; set winner number in message
; Print game win
mov edx, OFFSET winMsg ; load string address
call WriteString ; print string using irvine library
; update stats
cmp (PLAYER PTR[esi]).num,1 ; if player 1
jne updPlay2 ; if not, update 2
inc DWORD PTR[statistics.player1Wins] ; increment wins of player 1
jmp updComp
updPlay2:
inc DWORD PTR[statistics.player2Wins] ; increment wins of player 2
updComp:
cmp (PLAYER PTR[esi]).typ, COMPUTER ; if player is a computer
jne playAgain ; if not, skip
inc DWORD PTR[statistics.computerWins] ; increment wins of computer
playAgain:
; Prompt if a new game should be played
mov edx, OFFSET endMsg ; load string address
call WriteString ; print string using irvine library
call ReadChar ; read option
call WriteChar ; echo input char
cmp al, 'y' ; if yes, start new game
je startGame
cmp al, 'Y' ; if yes, start new game
je startGame
jmp mainLoop ; if other option, go to main menu
terminate:
; print statistics for the last time
Invoke PrintStats, ADDR statistics, ADDR statsMsgs
exit ; exit the program
ret
main ENDP
;-------------------------------------------------------------
printBoard PROC,
pBoard: PTR BYTE,
x: DWORD,
Y: DWORD
;
; Print current board, the function needs the board pointer
; if x!=-1, then the position x,y is set to black over red
;-------------------------------------------------------------
push esi ; save registers
push eax
push ebx
push ecx
mov esi, pBoard ; point to start of the board
mov ebx, 0 ; start in row 0
prRow:
mov ecx, 0 ; start in column 0
prCol:
mov al, ' ' ; load space for printing
call WriteChar ; print the char
Invoke getPiece, esi, ecx, ebx ; get a piece from the board
cmp al, 0 ; if empty
je prEmpty ; print empty
push eax; save char
cmp ebx, y
jne prChar ; if not selected y, skip
cmp ecx, x
jne prChar ; if not selected x, skip
mov eax, black + (red*16) ; change to red bg
call SetTextColor ; change char color
prChar:
pop eax
call WriteChar ; else, write char
mov eax, lightGray + (black*16) ; reset to default color
call SetTextColor ; change char color
jmp prNext
prEmpty:
mov al, '-' ; load dash for printing
call WriteChar ; print the char
prNext:
mov al, ' ' ; load space for printing
call WriteChar ; print the char
cmp ecx, 2 ; see if we are in the first two columns
jge prInc ; if not, skip
mov al, '|' ; load line for printing
call WriteChar ; print the char
prInc:
inc ecx ; advance column
cmp ecx, 3 ; if col < 3
jl prCol ; continue loop
call CrLf ; jump to next line
inc ebx ; advance row
cmp ebx, 3 ; if row < 3
jl prRow ; continue loop
pop ecx ; restore registers
pop ebx
pop eax
pop esi
ret
printBoard ENDP
;-------------------------------------------------------------
getPiece PROC,
pBoard: PTR BYTE,
x: DWORD,
Y: DWORD
;
; Get a piece from the board, the function needs the board
; and position in x and y
;-------------------------------------------------------------
push esi ; save registers
push edx
mov esi, pBoard ; point to start of board
mov edx, y ; load y position
imul edx, 3 ; multiply by 3
add edx, x ; add x position
movzx eax, BYTE PTR[esi + edx] ; load piece from board and return it
pop edx ; restore registers
pop esi
ret
getPiece ENDP
;-------------------------------------------------------------
setPiece PROC,
pBoard: PTR BYTE,
x: DWORD,
Y: DWORD,
letter: BYTE
;
; a Set piece on the board, the function needs the board,
; position in x and y and the letter to put at that position
;-------------------------------------------------------------
push esi ; save registers
push edx
push eax
mov esi, pBoard ; point to start of board
mov edx, y ; load y position
imul edx, 3 ; multiply by 3
add edx, x ; add x position
mov al, letter ; load letter in al
mov BYTE PTR[esi + edx], al ; save piece on board
pop eax ; restore registers
pop edx
pop esi
ret
setPiece ENDP
;-------------------------------------------------------------
setRandomPos PROC,
pBoard: PTR BYTE,
letter: BYTE
;
; Set piece on the board at a random position, the function
; needs the board, and the letter to put at that position,
; returns the selected position in ah=y, al=x
;-------------------------------------------------------------
push esi ; save registers
push ebx
push ecx
push edx
rndLoop:
mov eax, 3 ; to generate number between 0 and 2
call RandomRange ; generate row
mov edx, eax ; load y position
mov ebx, eax ; save y position
mov eax, 3 ; to generate number between 0 and 2
call RandomRange ; generate column
mov ecx, eax ; save x position
mov esi, pBoard ; point to start of board
imul edx, 3 ; multiply row by 3
add edx, eax ; add x position
cmp BYTE PTR[esi + edx],0 ; see if position is empty
jne rndLoop ; if not empty, try again
mov al, letter ; load letter in al
mov BYTE PTR[esi + edx], al ; save piece on board
xor eax,eax ; clear eax
mov al, cl ; return position
mov ah, bl
pop edx ; restore registers
pop ecx
pop ebx
pop esi
ret
setRandomPos ENDP
;-------------------------------------------------------------
readPosition PROC,
pBoard: PTR BYTE,
promptx: PTR BYTE,
prompty: PTR BYTE,
errorMsg: PTR BYTE
;
; Read position for user move and returns x in al, y in ah
;-------------------------------------------------------------
push ebx ; save registers
push ecx
push edx
push esi
push edi
rdLoop:
mov edx, promptx ; print prompt for x
call WriteString
call ReadInt ; read x position
dec eax
mov ebx, eax ; save position in ebx
cmp ebx, 0 ; see if x < 0
jl errorPos ; if negative, print error
cmp ebx, 2 ; see if x > 2
jg errorPos ; if greater, print error
mov edx, prompty ; print prompt for Y
call WriteString
call ReadInt ; read y position
dec eax
mov ecx, eax ; save y
cmp eax, 0 ; see if y < 0
jl errorPos ; if negative, print error
cmp eax, 2 ; see if y > 2
jg errorPos ; if greater, print error
Invoke getPiece, pBoard, ebx, eax ; get piece at selected position
cmp eax, 0 ; see if position is empty
je rdEnd ; if valid, return
errorPos:
mov edx, errorMsg ; print error message
call WriteString
jmp rdLoop ; repeat loop
rdEnd:
mov al, bl ; return position
mov ah, cl
pop edi ; restore registers
pop esi
pop edx
pop ecx
pop ebx
ret
readPosition ENDP
;-------------------------------------------------------------
Islam draw PROC,
pBoard: PTR BYTE
;
; Checks if the current board is a draw game, returns 1 if
; it is and zeroes if not
;-------------------------------------------------------------
push ebx
push ecx
mov ebx, 0
gdRow:
mov ecx, 0
gdCol:
Invoke getPiece, pBoard, ebx, ecx ; get piece at selected position
cmp eax, 0 ; see if position is empty
je gdEnd ; if empty, it's not a draw
inc ecx ; increment column
cmp ecx, 3 ; if less than 3
jl gdCol ; repear loop
inc ebx ; increment row
cmp ebx, 3 ; if less than 3
jl gdRow ; repear loop
mov eax,1 ; all positions full, it's a draw
gdEnd:
pop ecx
pop ebx
ret
isGameDraw ENDP
;-------------------------------------------------------------
isGameWin PROC,
pBoard: PTR BYTE,
x: DWORD,
Y: DWORD
;
; Checks if the current board is a winning game, returns winning
; type if win and zero if not, x and y indicate the position of the
; position to check
;-------------------------------------------------------------
push ebx
push ecx
push edx
Invoke getPiece, pBoard, x, y; get a piece at a selected position
mov cl, al; save piece in cl
; check row
mov ebx, 0
chkRow:
Invoke getPiece, pBoard, ebx, y ; get piece at selected position
cmp al, cl ; see if position has the same letter
jne chk2 ; if not, go to next check
inc ebx ; else, increment column
cmp ebx, 3 ; if col <3
jl chkRow ; check next col
mov eax, ROW ; row complete
jmp winDone ; return
chk2:
; check column
mov ebx, 0
chkCol:
Invoke getPiece, pBoard, x, ebx ; get piece at selected position
cmp al, cl ; see if position has the same letter
jne chk3 ; if not, go to next check
inc ebx ; else, increment row
cmp ebx, 3 ; if row<3
jl chkCol ; check next row
mov eax, COL ; column complete
jmp winDone ; return
chk3:
; check diag1
mov ebx, 0
chkDiag1:
Invoke getPiece, pBoard, ebx, ebx ; get piece at selected position
cmp al, cl ; see if position has the same letter
jne chk4 ; if not, go to next check
inc ebx ; else, increment position
cmp ebx, 3 ; if position<3
jl chkDiag1 ; check next position
mov eax, DIAG1 ; diagonal complete
jmp winDone ; return
chk4:
; check diag2
mov ebx, 0
mov edx, 2
chkDiag2:
Invoke getPiece, pBoard, ebx, edx ; get piece at selected position
cmp al, cl ; see if position has the same letter
jne noWin ; if not, no win
inc ebx ; else, increment position in x
dec edx ; decrement position in y
cmp ebx, 3 ; if position<3
jl chkDiag2 ; check next position
mov eax,DIAG2 ; diagonal complete
jmp winDone ; return
noWin:
mov eax, 0 ; not a won game
winDone:
pop edx
pop ecx
pop ebx
ret
isGameWin ENDP
;-------------------------------------------------------------
printWinningMove PROC,
pBoard: PTR BYTE,
x: DWORD,
Y: DWORD,
winType: DWORD
;
; Print winning move, the function needs the board pointer
; the last move position and the winning type
;-------------------------------------------------------------
push ebx ; save registers
push ecx
push edx
mov eax, blue + (lightGray*16) ; change to white bg
call SetTextColor ; change char color
mov eax, winType ; jump to corresponding print depending on win type
cmp eax, ROW
je pwRowWin
cmp eax, COL
je pwColWin
cmp eax, DIAG1
je pwDiag1Win
cmp eax, DIAG2
je pwDiag2Win
pwRowWin:
mov dl, 1 ; position of first char
mov eax, y ; set row
mov dh, al
mov ebx, 0 ; start in column 0
pwCol:
call GotoXY
Invoke getPiece, pBoard, ebx, y ; get piece from board
call WriteChar ; write char
add dl, 4 ; advance cursor
inc ebx ; advance column
cmp ebx, 3 ; if col < 3
jl pwCol ; continue loop
jmp pwEnd
pwColWin:
mov dh, 0 ; position of first char
mov eax, x ; set col
mov dl, al
shl dl, 2 ; multiply by 4
inc dl ; add 1 to get position
mov ebx, 0 ; start in row 0
pwRow:
call GotoXY
Invoke getPiece, pBoard, x, ebx ; get piece from board
call WriteChar ; write char
inc dh ; advance cursor
inc ebx ; advance row
cmp ebx, 3 ; if row < 3
jl pwRow ; continue loop
jmp pwEnd
pwDiag1Win:
mov dl, 1 ; position of first char
mov dh, 0
mov ebx, 0 ; start in column 0
pwDiag1:
call GotoXY
Invoke getPiece, pBoard, ebx, ebx ; get piece from board
call WriteChar ; write char
add dl, 4 ; advance cursor
inc dh
inc ebx ; advance position
cmp ebx, 3 ; if position < 3
jl pwDiag1 ; continue loop
jmp pwEnd
pwDiag2Win:
mov dl, 1 ; position of first char
mov dh, 2
mov ebx, 0 ; start in column 0
mov ecx, 2 ; start in row 2
pwDiag2:
call GotoXY
Invoke getPiece, pBoard, ebx, ecx ; get piece from board
call WriteChar ; write char
add dl, 4 ; advance cursor
dec dh
inc ebx ; advance position
dec ecx
cmp ebx, 3 ; if position < 3
jl pwDiag2 ; continue loop
pwEnd:
mov eax, lightGray + (black*16) ; reset to default color
call SetTextColor ; change char color
mov dh, 3 ; set cursor position just below board
mov dl,0
call GotoXY
pop edx ; restore registers
pop ecx
pop ebx
ret
printWinningMove ENDP
;-------------------------------------------------------------
clearBoard PROC,
pBoard: PTR BYTE
;
; Clears the board contents
;-------------------------------------------------------------
push eax ; save registers
push ecx
push edi
mov ecx, 9 ; clear 9 positions
mov al, 0 ; fill with zeros
mov edi, pBoard ; point to board
rep stosb ; fill all 9 spaces
pop edi ; restore registers
pop ecx
pop eax
ret
clearBoard ENDP
;-------------------------------------------------------------
PrintStats PROC,
pStats:PTR STATS,
pStatsMsgs: PTR DWORD
;
; Prints the game statistics, needs the stats structure and
; a pointer to the message table
;-------------------------------------------------------------
push ecx ; save registers on stack
push edx
push esi
push edi
call CRLF
mov esi, pStatsMsgs ; point to start of message table
mov edi, pStats ; point to start of stats
mov ecx, 7 ; print 7 stats
psLoop:
mov edx, [esi] ; load string address
call WriteString ; print string using irvine library
mov eax, [edi] ; load stat number
call WriteDec ; print
call CrLf ; jump to next line
add esi, 4 ; advance to next message
add edi, 4 ; advance to next statistic
loop psLoop ; repeat for all stats
call CrLf
call WaitMsg
pop edi ; restore registers
pop esi
pop edx
pop ecx
ret
PrintStats ENDP
END main