TicTacToe Game Simulator

Simulating a TicTacToe game using x86 assembly assignment help

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