x86 Tic Tac Toe Game
Your goal is to recreate the game Tic-Tac-Toe in Assembly. That’s it!
This will be a strictly two player game – no AI. Each player (X and O) will place their mark on the
board on alternate turns until one wins, or the game ends in a tie.
Game Rules
Board: 3×3 grid, with all 9 positions initially empty.
Two Players: X and O. X always goes first.
Goal: Each player tries to get 3 of their marks in a row, column, or diagonally. If achieved, the game
ends and the player who got the 3 marks lined up wins.
Gameplay: On their turn, each player will place their mark on an empty location on the board, and,
unless their move is a winning move (see above), then allow the other player to do the same in the next
turn.
This continues until one player wins or all nine locations are filled, which is considered a draw (tie).
Program guidelines
Name your source file <YourName>_TTT.asm, for example: Jorge_TTT.asm. The compiled executable
should be called simply <YourName>_TTT, for example: Jorge_TTT.
You MUST include your name and email as comments near the beginning of the source file.
You MUST store the game board as a 9-element (contiguous) array in memory and label it board.
For every play you MUST store the mark on the user-supplied board location (9 possibilities) on the
memory array (if that board location is still empty) or print an error message “That location is already
taken.” if that board location has been used already, and ask for a new location.
You can label the locations in two ways (choose whichever one you prefer):
0 1 2 |
3 4 5 |
6 7 8 |
1 2 3 |
4 5 6 |
7 8 9 |
Start the game proper by asking player X to enter a board location, then player O, and so on until one
of them wins or there is a draw. Upon a win or a draw, the program should end.
After each move (valid or invalid), you MUST redraw the game board. If the move was valid, draw the
updated board. If the move was invalid, print an error message “That location is out of range or already
taken.”, draw the board as it is, and ask for input again.
Your program MUST have two functions/routines/labels to print the board: print_boardand
debug_board. print_boardMUST display the board in a human readable way (see example
below), while debug_boardMUST print the entire linear array contents (again, see example below),
to aid you in developing and debugging the program. You can add extra debug information to this
function (see Grading section).
The selection of which function to use MUST be done by prompting the user “Do you want to enable
debug information?” at the program’s start. If the first character in the user’s reply is “Y”, “y”, “d”, or
“D” then debugging MUST be enabled. Any other response disables debugging.
Output examples (yours can be formatted different or contain slightly different information, these are
just examples):
print_board:
Current board:
|X|
—–
O| |X
—–
|O|
debug_board:
Current board:
012345678
[ X O X O]
Current board (hex):
0 1 2 3 4 5 6 7 8
[20 58 20 4F 20 58 20 20 4F]
Current board (mem):
&board = 0x0100
+offset / hex / ASCII
0x00: 20h
0x01: 58h X
0x02: 20h
0x03: 4Fh O
0x04: 20h
0x05: 58h X
0x06: 20h
0x07: 20h
0x08: 4Fh O
Other program restrictions and tips
Your program MUST compile and run on gl (under the same NASM command lines as the previous
labs), so make sure you test it before submitting.
Remember that gl is running in 64-bit mode and on a much more advanced microprocessor than the
8086! This means that you may face new challenges (and take advantage of new opportunities) that
were not addressed in class. It is up to you to learn from this experience – there are many resources
online, but myself and the TAs will provide you additional help IF you come to us with a “worthy” bug,
one where you can at least partially explain the reason for failure, and demonstrate that you already
tried different possible solutions to no avail.
You can use:
– all instructions in the 8086 summary list .pdf (though it’s not really for the 8086) in BlackBoard,
except the ones on section 5.1.8, but you can use the REP “family”;
– the natural 64-bit extensions to the instructions allowed above;
– only the registers we covered in class/labs, extended to 64-bit, but no R8-R15, etc.;
– only the program sections we covered in class/labs;
– as much data and code memory as you want (and the architecture and OS allows), though you should
always try to optimize its usage a bit;
You cannot use:
– any syscall or external library, except the sys_read, sys_write and exit that we used in the labs;
– any assembler or linker command-line optimizations, tricks, or extra features;
– any NASM-included macros;
– any undocumented instructions;
You may use (but perhaps ask first):
– simple macros/defines that will save you some repetitive typing;
– additional/more advanced instructions that provide some advantage over the “normal” ones, but are
not so advanced that make the problem you’re solving too trivial (example: no direct memory to
memory copy/move instructions of any kind are allowed);
– particularly fast/compact/efficient/odd/quirky/funny code or memory constructs provided that they are
properly commented and explained in the report (see Grading section);
Example run of the program:
./Jorge_TTT
Do you want to enable debug information?
n
Player X, choose your location (0-8):
Current board:
| |
—–
| |
—–
| |
0
Player O, choose your location (0-8):
Current board:
X| |
—–
| |
—–
| |
4
Player X, choose your location (0-8):
Current board:
X| |
—–
|O|
—–
| |
6
Player O, choose your location (0-8):
Current board:
X| |
—–
|O|
—–
X| |
1
Player X, choose your location (0-8):
Current board:
X|O|
—–
|O|
—–
X| |
7
Player O, choose your location (0-8):
Current board:
X|O|
—–
|O|
—–
X|X|
8
Player X, choose your location (0-8):
Current board:
X|O|
—–
|O|
—–
X|X|O
3
Player X wins!
Final board:
X|O|
—–
X|O|
—–
X|X|O
Solution
Ahmed_TTT.asm
; Name:
; Email:
section .data
msg1db “Do you want to enable debug information?”,0xA,0xD
len1equ $-msg1
msg2db “Player X, choose your location (1-9): “,0xA,0xD
len2equ $-msg2
msg3db “Current board:”,0xA,0xD
len3equ $-msg3
row1db ” | | “,0xA,0xD
db “—–“,0xA,0xD
row2db ” | | “,0xA,0xD
db “—–“,0xA,0xD
row3db ” | | “,0xA,0xD
lenbequ $-row1
dbg1db ” 012345678 “,0xA,0xD
arr1db “[ ]”,0xA,0xD
lendequ $-dbg1
msg5db “That location is out of range or already taken.”,0xA,0xD
len5equ $-msg5
msg6db “The game is a draw!”,0xA,0xD
db “Final board:”,0xA,0xD
len6equ $-msg6
msg7db “Player X wins!”,0xA,0xD
db “Final board:”,0xA,0xD
len7equ $-msg7
eoldb 0xA,0xD
eol_lenequ $-eol
boarddb 0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20
markdb “XO”
player db 0
debugdb 0,0
movedb 0,0
section .bss
rdbuffresb 1024 ; 1K read buffer
section .text
global _start
; print_string subroutine
; prints a string using sys_write
; on entry: edx = number of bytes to print
; ecx = address of string
print_string: ; defining function print_string
movebx, 1 ; print to stdout
moveax, 4 ; sys write function
int 0x80 ; call kernel
ret ; return from subroutine
; read_string subroutine
; reads a string using sys_read
; on entry: edx = number of bytes to read
; ecx = address of read buffer
read_string: ; defining function read_string
moveax, 3 ; sys read function
movebx, 2 ; read from stdin
int 0x80 ; call kernel
ret ; return from subroutine
read_input:
movecx,rdbuff
mov edx,1024
callread_string
ret
read_int:
callread_input ; read all input until enter is pressed
movesi,rdbuff
mov ebx,0
mov ecx,10
cnv: movzxeax,BYTE [esi]
incesi
cmp al,10
je o1
cmp al,’0′
jl err1
cmp al,’9′
jg err1
sub al,’0′
xchgeax,ebx
mulecx
xchgeax,ebx
add ebx,eax
jmpcnv
err1:
mov ebx,-1 ; error reading number
o1: moveax,ebx
ret
read_char:
callread_input
mov al,[rdbuff]
ret
; print_current_board subroutine
; prints the current board using the board template and the current board
print_current_board:
; fill row 1 of the board template with current board contents
mov edi,row1 ; load row1 address in edi
movesi,board ; load board address in esi
mov ecx,3 ; load ecx to loop 3 times (3 chars per row)
setrow1:
mov al,[esi] ; load a char from the board
mov [edi],al ; save char in the template
incesi ; go to next char in board
add edi,2 ; go to next position in template, skipping the column separator
loop setrow1 ; repeat 3 times while ecx is not zero
; fill row 2 of the board template with current board contents
mov edi,row2 ; load row2 address in edi
mov ecx,3 ; load ecx to loop 3 times (3 chars per row)
setrow2:
mov al,[esi] ; load a char from the board
mov [edi],al ; save char in the template
incesi ; go to next char in board
add edi,2 ; go to next position in template, skipping the column separator
loop setrow2 ; repeat 3 times while ecx is not zero
; fill row 3 of the board template with current board contents
mov edi,row3 ; load row3 address in edi
mov ecx,3 ; load ecx to loop 3 times (3 chars per row)
setrow3:
mov al,[esi] ; load a char from the board
mov [edi],al ; save char in the template
incesi ; go to next char in board
add edi,2 ; go to next position in template, skipping the column separator
loop setrow3 ; repeat 3 times while ecx is not zero
; print the template board
mov ecx,row1 ; load address of start of template
movedx,lenb ; load length of board template
callprint_string ; print it on the screen
ret ; return from subroutine
; print_board subroutine
; prints the current board and a message on top of it
print_board:
; print message “Current board”
mov ecx,msg3 ;load address of message in ecx
mov edx,len3 ; load length of message in edx
callprint_string ; print the message in the screen
callprint_current_board ; print the current board using the function
ret ; return from subroutine
; debug_current_board subroutine
; prints the current debug board
debug_current_board:
; fill array 1 with current board contents
mov edi,arr1+1 ; load address of the debug board template in edi, just after the ‘[‘
movesi,board ; load address of board in esi
mov ecx,9 ; load ecx to loop 9 times (9 chars in the board)
copyarr1:
mov al,[esi] ; load a char from the board
mov [edi],al ; save char in the debug template
incesi ; go to next char in board
incedi ; go to next char in debug template
loop copyarr1 ; repeat 9 times while ecx is not zero
; print entire debug board template
mov ecx,dbg1 ; load address of start of template
movedx,lend ; load length of debug board template
callprint_string ; print it on the screen
ret ; return from subroutine
; debug_board subroutine
; prints the current debug board and a message on top of it
debug_board:
; print message “Current board”
mov ecx,msg3 ;load address of message in ecx
mov edx,len3 ; load length of message in edx
callprint_string ; print the message in the screen
calldebug_current_board ; print the current debug board using the function
ret ; return from subroutine
; is_tie subroutine
; if the board is a tie returns eax=1, otherwise returns eax=0
; on exit: eax = 0 if is a tie
; 1 if it’s not a tie
is_tie:
mov eax,0 ; initialize eax to zero to return no tie by default
movesi,board ; load address of board in esi
mov ecx,9 ; load ecx to loop 9 times (9 chars in the board)
find20:
cmp BYTE [esi],0x20 ; see if the curren position in the board has a empty space (0x20)
je nope ; if so, then the board is not yet full, return no tie
incesi ; otherwise, increment esi to go to next character in board
loop find20 ; repeat 9 times while ecx is not zero
mov eax,1 ; if we get here, we have found no spaces on the board so it is full, return tie=1
nope:
ret ; return from subroutine
; is_full subroutine
; if 3 positions on the board separated by ebx are the same and are not spaces
; it returns al = repeated mark, otherwise returns al = 0x20 (space)
; On entry: esi = start address of the characters to compare
; ebx = separation between the characters to compare
; On exit: al = 0x20 if no repeated characters are found or if there was a space
; ‘X’ if there were 3 repeated X characters
; ‘O’ if there were 3 repeated O characters
is_full:
pushrsi ; save rsi register on the stack
mov al,0x20 ; set al to 0x20 to return no repeated characters by default
movah,BYTE [esi] ; load character at given start position into ah
cmp ah,0x20 ; compare loaded character with space
je noteq ; if it was a space, exit, there were no repeated chars
addesi,ebx ; otherwise, go to next character by incrementing the addres by the given separation ebx
cmpah,BYTE [esi] ; compare the initial character with the character at second position
jnenoteq ; if they are not equal, exit, there were no repeated chars
addesi,ebx ; otherwise, go to next character by incrementing the address by the given separation ebx
cmpah,BYTE [esi] ; compare the initial character with the character at third position
jnenoteq ; if they are not equal, exit, there were no repeated chars
; if we get here, the initial char was equal to the second and to the third one
; and there were no spaces so ah has the repeated character
moval,ah ; move it to al to return it as the character repeated 3 times
noteq:
poprsi ; restore rsi register from the stack
ret ; return from subroutine
; is_win subroutine
; if there is a winner returns the mark for the winner, otherwise it returns 0x20
; On exit: al = 0x20 if no 3 repeated characters are found (no winner)
; ‘X’ if there were 3 repeated X characters, winner is X
; ‘O’ if there were 3 repeated O characters, winner is O
is_win:
; try to find repeated marks in the rows first
mov ecx,3 ; load ecx to loop 3 times (3 chars per row)
movesi,board ; load board address in esi
mov ebx,1 ; compare elements separated by 1
cmprows: ; loop through each row
callis_full ; call is_full to see if three characters in a row are equal
cmp al,0x20 ; see if it returned 0x20
jnewinret ; if it wasn’t 0x20, it found a character repeated 3 times in a row, this is the winner
add esi,3 ; advance to next row
loopcmprows ; repeat 3 times while ecx is not zero
; try to find repeated marks in the columns
mov ecx,3 ; load ecx to loop 3 times (3 chars per column)
movesi,board ; load board address in esi
mov ebx,3 ; compare elements separated by 3
cmpcols: ; loop through each column
callis_full ; call is_full to see if three characters in a column are equal
cmp al,0x20 ; see if it returned 0x20
jnewinret ; if it wasn’t 0x20, it found a character repeated 3 times in a row, this is the winner
add esi,1 ; advance to next column
loopcmpcols ; repeat 3 times while ecx is not zero
; try to find repeated marks in the main diagonal
movesi,board ; load board address in esi
mov ebx,4 ; compare elements separated by 4
callis_full ; call is_full to see if three characters in the diagonal are equal
cmp al,0x20 ; see if it returned 0x20
jnewinret ; if it wasn’t 0x20, it found a character repeated 3 times in the diagonal, this is the winner
; try to find repeated marks in the lower diagonal
mov esi,board+2 ; load board address in esi
mov ebx,2 ; compare elements separated by 2
callis_full ; call is_full to see if three characters in the diagonal are equal
; after this we simply return the result from is_full
winret:
ret ; return from subroutine
; main procedure
_start:
; prompt the user for the debug option
mov ecx,msg1 ;load address of message in ecx
mov edx,len1 ; load length of message in edx
callprint_string ; print the message in the screen
; read debug input from user
callread_char ; read the character
mov [debug],al
cmp BYTE [debug],’Y’ ; compare the read debug option with Y
jedebugy ; if the option entered Y, go to debugy
cmp BYTE [debug],’y’ ; compare the read debug option with y
jedebugy ; if the option entered y, go to debugy
cmp BYTE [debug],’D’ ; compare the read debug option with D
jedebugy ; if the option entered D, go to debugy
cmp BYTE [debug],’d’ ; compare the read debug option with d
jedebugy ; if the option entered d, go to debugy
; if we get here, the user entered a character other than Y,y,D,d
mov BYTE [debug],0 ; move zero to debug since the user select no debugging
jmp begin ; jump to start of game
debugy: ; if we get here, the user entered Y,y,D or d
mov BYTE [debug],1 ; move 1 to debug since the user select activate debugging
begin: ; start of game
; prompt the user for the position to play
mov ecx,msg2 ;load address of message in ecx
mov edx,len2 ; load length of message in edx
callprint_string ; print the message on the screen
test BYTE [debug],1 ; see if the debug option was selected by comparing with 1
jnz dbb1 ; if it’s not zero, debug is enabled, jump to dbb1
callprint_board ; otherwise use the print_board subroutine to print the current board
jmp dbb2 ; skip the next instruction to continue
dbb1:
calldebug_board ; if we get here, use the debug_board subroutine to print the current board
dbb2:
; read position from user
callread_int
cmp eax,1
jl outrange
cmp eax,9
jg outrange
deceax
mov [move],al
; see if the board location is free
movzxebx,BYTE [move] ; load the position selected by the user into ebx
movedi,board ; load board address in edi
cmp BYTE [edi+ebx],0x20 ; compare the selected position on the board with an empty space
jne outrange ; if they are not equal the position is taken, go to outrange
; get player mark
movzxecx,BYTE [player] ; load the current player (0 or 1) into ecx
movesi,mark ; load the address of the marks into esi
mov al,[esi+ecx] ; get the mark for the current player into al
mov [edi+ebx],al ; make the move to the selected position by writing the player mark on the board
callis_win ; see if a player has won the game by calling is_win
cmp al,0x20 ; see if the subroutine returned a space (no winner)
jnewinend ; if is not space, it found the winner with mark O or X in al, end the game going to winend
callis_tie ; see if the game is tied by calling the subroutine is_tie
cmp eax,1 ; see if the subroutine returned a 1 (tie)
jetieend ; if it returned 1, the game is a tie, end the game going to tieend
xor BYTE [player],1 ; if no tie and no winner, change turn by using the xor which inverts either 0 to 1 or 1 to 0
; modify the prompt message to use the current player mark
movzxebx,BYTE [player] ; load the current player (0 or 1) into ecx
movesi,mark ; load the address of the marks into esi
mov al,[esi+ebx] ; get the mark for the current player into al
mov edi,msg2 ; load address of the prompt message into edi
mov [edi+7],al ; change the mark shown in the message to the mark for the current player
jmp begin ; go back to the beginning so the new player can make a move
outrange: ; we get here when the read position is out of range or the position is taken
; we simply print an error message to indicate the error and then repeat the prompt
mov ecx,msg5 ;load address of message in ecx
mov edx,len5 ; load length of message in edx
callprint_string ; print message on the screen
jmp begin ; repeat from the start to ask for a new position
tieend: ; we get here when a tie is found, we simply print the game is a draw, print
; the board and exit the game
mov ecx,msg6 ;load address of message in ecx
mov edx,len6 ; load length of message in edx
callprint_string ; print the message on the screen
jmplast_board ; go to last_board to print the last board and exit
winend: ; we get here when a winner is found, we simply print the win message
; and the winner mark, print the last board and exit
; first, change the mark of the winner in the win message
mov edi,msg7 ; load address of win message into edi
mov [edi+7],al ; change the mark for the winner in the win message
; print the game is a win and show the mark for the winner
mov ecx,msg7 ;load address of message in ecx
mov edx,len7 ; load length of message in edx
callprint_string ; print message on the screen
last_board: ; print last board
test BYTE [debug],1 ; see if the debug option was selected by comparing with 1
jnz dbb3 ; if it’s not zero, debug is enabled, jump to dbb3
callprint_current_board ; otherwise use the print_board subroutine to print the last board
jmp endgame ; skip the next instruction to continue
dbb3:
calldebug_current_board ; if we get here, use the debug_board subroutine to print the current board
endgame: ; end of game, exit to os by calling the kernel
mov eax,1 ; system call number
int 0x80 ; call kernel