Assignment
Assembly ProgrammingYour 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 theboard on alternate turns until one wins, or the game ends in a tie. Game Rules Board: 3x3 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 gameends 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, then allow the other player to do the same in the nextturn. This continues until one player wins or all nine locations are filled, which is considered a draw (tie).
Program guidelines
Name your source file
1 | 2 | 3 |
4 | 5 | 6 |
7 | 8 | 9 |
Output 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.
Remember that gl is running in 64-bit mode and on a much more advanced microprocessor than the8086! This means that you may face new challenges
You can use:
– all instructions in the 8086 summary list .pdf (though it’s not really for the 8086) in BlackBoard,
– 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 shouldalways 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:
– simple macros/defines that will save you some repetitive typing;
– additional/more advanced instructions that provide some advantage over the “normal” ones, but arenot so advanced that make the problem you’re solving too trivial;
– particularly fast/compact/efficient/odd/quirky/funny code or memory constructs provided that they areproperly commented and explained in the report;
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
;
; Assembly program that recreates a Tic-Tac-Toe game between two players
;
;
SECTION .data
board db 32,32,32,32,32,32,32,32,32 ; game board initialized to spaces
playerdb ‘X’
debugdb 0
askdbgdb “Do you want to enable debug information?”,10
lenaskdbgequ $-askdbg
playermsgdb “Player ”
lenplyrmsgequ $-playermsg
promptdb “, choose your location (0-8):”,10
lenpromptequ $-prompt
currentmsgdb “Current board:”,10
lencurmsgequ $-currentmsg
winmsgdb ” wins!”,10
lenwinmsgequ $-winmsg
finalmsgdb “Final board:”,10
lenfinalmsgequ $-finalmsg
errormsgdb “Invalid board location, please try again.”,10
lenerrmsgequ $-errormsg
occupiedmsgdb “The position is occupied, please try a different one.”,10
lenoccmsgequ $-occupiedmsg
drawmsgdb “The game is a draw!”,10
lendrawmsgequ $-drawmsg
nldb 10
player1db ‘X’
player2db ‘O’
dbg1db ” 012345678 “,10
lendbg1equ $-dbg1
dbg2 db ” 0 1 2 3 4 5 6 7 8 “,10
lendbg2equ $-dbg2
par1db ‘[‘
par2db ‘]’
currentmsg2db “Current board (hex):”,10
lencurmsg2 equ $-currentmsg2
currentmsg3db “Current board (mem):”,10
lencurmsg3 equ $-currentmsg3
brdaddrdb “&board = ”
lenbrdaddrequ $-brdaddr
listmsgdb “+offset / hex / ASCII”,10
lenlistmsgequ $-listmsg
hexprefixdb “0x”
sep1db “: ”
sep2db “h ”
SECTION .bss
bufferresb 10
SECTION .text
global _start:
_start:
movrsi,askdbg ; prompt the user for activating debug information
movrax,lenaskdbg
call print
callread_char
cmpal,’Y’ ; activate debugging info when the user enters Y,y,d or D
je setDebug
cmpal,’y’
je setDebug
cmpal,’d’
je setDebug
cmpal,’D’
jnemain_loop
setDebug:
inc BYTE [debug] ; set debug to 1
main_loop:
callprint_player ; print prompt
movrsi,prompt
movrax,lenprompt
call print
movrsi,currentmsg
movrax,lencurmsg
call print
cmp BYTE [debug],0 ; select board print based on debug state
jneprdebug
callprint_board
jmpreadpos
prdebug:
calldebug_board
readpos:
callread_char ; read the position
cmp al,’0′
jl error
cmp al,’8′
jg error
sub al,’0′ ; convert from ascii to integer
movrbx,rax
mov al,[player]
movrsi,board
mov ah,[rsi+rbx] ; see if the location was occupied
cmp ah,32
jne occupied
mov [rsi+rbx],al ; save move in the board
callcheck_winner ; see if someone won or if there’s a tie
cmp al,0 ; if no winner and no tie, continue
je continue
cmp al,1
je tiedGame
jmpwonGame
continue:
callchange_turn ; change player’s turn
jmpmain_loop ; continue playing
error:
movrsi,errormsg
movrax,lenerrmsg
call print
callprint_nl
jmpmain_loop
occupied:
movrsi,occupiedmsg
movrax,lenoccmsg
call print
callprint_nl
jmpmain_loop
tiedGame:
movrsi,drawmsg
movrax,lendrawmsg
call print
movrsi,finalmsg
movrax,lenfinalmsg
call print
jmplastboard
wonGame:
callprint_player
movrsi,winmsg
movrax,lenwinmsg
call print
movrsi,finalmsg
movrax,lenfinalmsg
call print
lastboard:
cmp BYTE [debug],0 ; select board print based on debug state
jneprdbg
callprint_board
jmp exit
prdbg:
calldebug_board
exit:
mov rax,1 ; sys_exit
mov rbx,0
int 80h
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Procedure to print one or more characters to stdout
; rsi = base address of characters to print
; rax = number of chars to print
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
print:
pushrax ; preserve registers used by the procedure
pushrbx
pushrcx
pushrdx
movrdx,rax
movrcx,rsi
mov rax,4
mov rbx,1 ; print to stdout
int 80h ; use sys_readsyscall to print to the screen
poprdx ; restore used registers
poprcx
poprbx
poprax
ret
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Procedure to print a new line
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
print_nl:
pushrax
pushrsi
mov rax,1
movrsi,nl
call print
poprsi
poprax
ret
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Procedure to print a character
; On entry:
; al = character to print
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
print_char:
pushrax
pushrsi
mov [buffer],al
movrsi,buffer
mov rax,1
call print
poprsi
poprax
ret
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Procedure to read one characters from stdin
; On exit:
; rax = read character
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
read_char:
pushrbx ; preserve registers used by the procedure
pushrcx
pushrdx
pushrdi
mov rsi,0
movrdi,buffer
rdloop:
movrax, 3 ; read the selected debug state
movrbx, 2 ; read from stdin
movrcx, rdi
movrdx, 1 ; read 1 byte
int 80h ; use sys_writesyscall to read input from the user
cmp BYTE [rdi],10 ; see if it was an enter
je rdend
incrsi ; read only one char, discard every other char until an enter is found
cmp rsi,1
jgrdloop
incrdi
jmprdloop
rdend:
mov rax,0 ; return read character in rax
mov al,[buffer]
poprdi
poprdx ; restore used registers
poprcx
poprbx
ret
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Procedure to print the current player
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
print_player:
pushrax
pushrsi
movrsi,playermsg ; print “Player”
movrax,lenplyrmsg
call print
mov al,[player]
callprint_char ; print player character
poprsi
poprax
ret
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Procedure to change the player
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
change_turn:
pushrax
moval,BYTE [player1] ; if the current player is player1
cmp BYTE [player],al
jne chg2
moval,BYTE [player2] ; change player to player2
mov [player],al
jmpchgdone
chg2:
moval,BYTE [player1] ; if the current player is player2
mov [player],al ; change player to player1
chgdone:
poprax
ret
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Procedure to print the current board
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
print_board:
movrsi,board ; point to board with rsi
movrdi,buffer
mov BYTE [rdi],’|’
mov rax,0x2d2d2d2d2d ; save five consecutive dashes ‘-‘
mov [rdi+1],rax
mov rax,1
mov rcx,3
prloop:
call print ; print the current board char
incrsi
xchgrsi,rdi ; use di to print the separator
call print ; print the separator
xchgrsi,rdi ; restore rsi
call print ; print the current board char
incrsi
xchgrsi,rdi ; use di to print the separator
call print ; print the separator
xchgrsi,rdi ; restore rsi
call print ; print the current board char
incrsi
callprint_nl
cmp rcx,1 ; don’t print a dash row at the end
je skip
xchgrsi,rdi ; use di to print a dash row
incrsi
mov rax,5 ; print 5 chars
call print ; print the separator
decrsi
xchgrsi,rdi ; restore rsi
mov rax,1 ; print 1 char next
callprint_nl
skip:
loopprloop
ret
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Procedure to see if any player has won or if the
; game is a draw.
; On exit: al = 0 for no event
; al = 1 for tied game (draw)
; al = ‘X’ if the X player has won
; al = ‘O’ if the O player has won
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
check_winner:
movrsi,board
mov rbx,0 ; we will check the three rows
chkrow:
mov al,[rsi+rbx]
cmp al,32 ; if it’s a space
je nxtrow
mov ah,[rsi+rbx+1]
cmpal,ah
jnenxtrow
mov ah,[rsi+rbx+2]
cmpal,ah
je return
nxtrow:
add rbx,3 ; go to next row
cmp rbx,9 ; if we haven’t go beyond the last row, continue
jlchkrow
mov rbx,0 ; we will check the three columns
chkcol:
mov al,[rsi+rbx]
cmp al,32 ; if it’s a space
je nxtcol
mov ah,[rsi+rbx+3]
cmpal,ah
jnenxtcol
mov ah,[rsi+rbx+6]
cmpal,ah
je return
nxtcol:
incrbx ; go to next col
cmp rbx,3 ; if we haven’t go beyond the last col, continue
jlchkcol
chkdiag1: ; check first diagonal
mov al,[rsi]
cmp al,32 ; if it’s a space
je chkdiag2
mov ah,[rsi+4]
cmpal,ah
jne chkdiag2
mov ah,[rsi+8]
cmpal,ah
je return
chkdiag2: ; check second diagonal
mov al,[rsi+2]
cmp al,32 ; if it’s a space
je chktie
mov ah,[rsi+4]
cmpal,ah
jnechktie
mov ah,[rsi+6]
cmpal,ah
je return
chktie: ; no wins, let’s check tie
mov rcx,9
cmpspc:
mov al,[rsi]
incrsi
cmp al,32
je notie
loopcmpspc
mov al,1 ; tie, all places on the board are filled
ret
notie:
mov al,0 ; no win, no tie
return:
ret
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Procedure to print the board as a linear array
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
print_linear_board:
mov rsi,dbg1
mov rax,lendbg1
call print
mov al,[par1]
callprint_char
movrsi,board ; point to board characters
mov rax,9
call print
mov al,[par2]
callprint_char
callprint_nl
ret
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Procedure to print a hexadecimal digit
; On entry:
; al= digit to print, only the 4 lowest bits are used
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
print_hex:
pushrax
and al,0xF
cmp al,9 ; if the number is above 9, print as a letter
jghx
add al,’0′ ; convert from 0-9 to ascii ‘0’-‘9′
jmppx
hx: sub al,10 ; convert from 10-15 to A-F
addal,’A’
px:
callprint_char
poprax
ret
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Procedure to print a character as hexadecimal
; On entry:
; al= character to print
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
print_hex_char:
pushrax
pushrcx
movah,al
mov cl,4
shral,cl ; move upper 4 bits downwards
callprint_hex
moval,ah ; restore old number
callprint_hex
poprcx
poprax
ret
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Procedure to print a 32 bit address as hexadecimal
; On entry:
; eax= address to print
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
print_hex_address:
pushrax
pushrcx
mov rcx,4
movebx,eax
pbytes:
shld eax,ebx,8 ; shift upper 8 bits into eax
shl ebx,8 ; shift ebx to uptate it
callprint_hex_char
looppbytes
poprcx
poprax
ret
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Procedure to print the board as a linear hex array
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
print_linear_hex_board:
mov rsi,dbg2
mov rax,lendbg2
call print
mov al,[par1]
callprint_char
mov rcx,9
movrsi,board ; point to board characters
prhex:
mov al,[rsi]
incrsi
callprint_hex_char
cmp rcx,1 ; if it’s the last number, don’t print a space
jenospc
mov al,32 ; print a space
callprint_char
nospc:
loopprhex
mov al,[par2]
callprint_char
callprint_nl
ret
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Procedure to print the board as a list of addresses
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
print_list_hex_board:
movrsi,brdaddr
movrax,lenbrdaddr
call print
movrsi,hexprefix
mov rax,2
call print
movrax,board ; print the board address
callprint_hex_address
callprint_nl
movrsi,listmsg
movrax,lenlistmsg
call print
mov rcx,9
mov rbx,0
movrdi,board ; point to board characters
prline:
movrsi,hexprefix
mov rax,2
call print
moval,bl
callprint_hex_char
mov rsi,sep1
mov rax,2
call print
mov al,[rdi]
callprint_hex_char
mov rsi,sep2
mov rax,2
call print
mov al,[rdi]
callprint_char
callprint_nl
incrdi
incrbx
loopprline
ret
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Procedure to print the debug board
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
debug_board:
callprint_linear_board
mov rsi,currentmsg2
mov rax,lencurmsg2
call print
callprint_linear_hex_board
mov rsi,currentmsg3
mov rax,lencurmsg3
call print
callprint_list_hex_board
ret