'Tic-tac-toe game does not terminate in case of a draw
I am making a tic-tac-toe game using assembly language. It does not terminate in case of a draw. How can I rectify it?
Below is the code:
data segment
new_line db 13, 10, "$"
game_draw db "_|_|_", 13, 10
db "_|_|_", 13, 10
db "_|_|_", 13, 10, "$"
game_pointer db 9 DUP(?)
win_flag db 0
player db "0$"
game_over_message db "Game ended", 13, 10, "$"
game_start_message db "Welcome", 13, 10, "$"
player_message db "PLAYER $"
win_message db " WIN!$"
type_message db "TYPE A POSITION: $"
tra db 'want to play again? (y/n):$'
drw db "the game is draw! $"
ends
stack segment
dw 128 dup(?)
ends
extra segment
ends
code segment
start:
; set segment registers
mov ax, data
mov ds, ax
mov ax, extra
mov es, ax
; game start
call set_game_pointer
main_loop:
call clear_screen
lea dx, game_start_message
call print
lea dx, new_line
call print
lea dx, player_message
call print
lea dx, player
call print
lea dx, new_line
call print
lea dx, game_draw
call print
lea dx, new_line
call print
lea dx, type_message
call print
; read draw position
call read_keyboard
; calculate draw position
sub al, 49
mov bh, 0
mov bl, al
call update_draw
call check
; check if game ends
cmp win_flag, 1
je game_over ;STEPH CHANGES
call change_player
jmp main_loop
change_player:
lea si, player
xor ds:[si], 1
ret
update_draw:
mov bl, game_pointer[bx]
mov bh, 0
lea si, player
cmp ds:[si], "0"
je draw_x
cmp ds:[si], "1"
je draw_o
draw_x:
mov cl, "x"
jmp update
draw_o:
mov cl, "o"
jmp update
update:
mov ds:[bx], cl
ret
check:
call check_line
ret
check_line:
mov cx, 0
check_line_loop:
cmp cx, 0
je first_line
cmp cx, 1
je second_line
cmp cx, 2
je third_line
call check_column
ret
first_line:
mov si, 0
jmp do_check_line
second_line:
mov si, 3
jmp do_check_line
third_line:
mov si, 6
jmp do_check_line
do_check_line:
inc cx
mov bh, 0
mov bl, game_pointer[si]
mov al, ds:[bx]
cmp al, "_"
je check_line_loop
inc si
mov bl, game_pointer[si]
cmp al, ds:[bx]
jne check_line_loop
inc si
mov bl, game_pointer[si]
cmp al, ds:[bx]
jne check_line_loop
mov win_flag, 1
ret
check_column:
mov cx, 0
check_column_loop:
cmp cx, 0
je first_column
cmp cx, 1
je second_column
cmp cx, 2
je third_column
call check_diagonal
ret
first_column:
mov si, 0
jmp do_check_column
second_column:
mov si, 1
jmp do_check_column
third_column:
mov si, 2
jmp do_check_column
do_check_column:
inc cx
mov bh, 0
mov bl, game_pointer[si]
mov al, ds:[bx]
cmp al, "_"
je check_column_loop
add si, 3
mov bl, game_pointer[si]
cmp al, ds:[bx]
jne check_column_loop
add si, 3
mov bl, game_pointer[si]
cmp al, ds:[bx]
jne check_column_loop
mov win_flag, 1
ret
check_diagonal:
mov cx, 0
check_diagonal_loop:
cmp cx, 0
je first_diagonal
cmp cx, 1
je second_diagonal
ret
first_diagonal:
mov si, 0
mov dx, 4 ;jump size
jmp do_check_diagonal
second_diagonal:
mov si, 2
mov dx, 2
jmp do_check_diagonal
do_check_diagonal:
inc cx
mov bh, 0
mov bl, game_pointer[si]
mov al, ds:[bx]
cmp al, "_"
je check_diagonal_loop
add si, dx
mov bl, game_pointer[si]
cmp al, ds:[bx]
jne check_diagonal_loop
add si, dx
mov bl, game_pointer[si]
cmp al, ds:[bx]
jne check_diagonal_loop
mov win_flag, 1
ret
game_over:
call clear_screen
lea dx, game_start_message
call print
lea dx, new_line
call print
lea dx, game_draw
call print
lea dx, new_line
call print
lea dx, game_over_message
call print
lea dx, player_message
call print
lea dx, player
call print
lea dx, win_message
call print
jmp fim
set_game_pointer:
lea si, game_draw
lea bx, game_pointer
mov cx, 9
loop_1:
cmp cx, 6
je add_1
cmp cx, 3
je add_1
jmp add_2
add_1:
add si, 1
jmp add_2
add_2:
mov ds:[bx], si
add si, 2
inc bx
loop loop_1
ret
print: ; print dx content
mov ah, 9
int 21h
ret
clear_screen: ; get and set video mode
mov ah, 0fh
int 10h
mov ah, 0
int 10h
ret
read_keyboard: ; read keybord and return content in ah
mov ah, 1
int 21h
ret
ret
fim:
jmp fim
code ends
end start
Solution 1:[1]
Correcting the initialization
Your game_pointer array consists of bytes yet the set_game_pointer routine writes words. This error does not harm the program because all references to the game_draw string happen to have their high byte zero and the win_flag that immediately follows the pointer list in memory starts at zero anyway.
The preferred solution is to define the pointer list as words and fill the list at assembly time because its initialization does not deserve runtime code.
game_pointer dw game_draw, game_draw + 2, game_draw + 4
dw game_draw + 7, game_draw + 9, game_draw + 11
dw game_draw + 14, game_draw + 16, game_draw + 18
win_flag db 0
OccupiedSquares db 0
player db "0$"
Improving the user input
You are neglecting to verify the user input! You assume that it will always be a digit from "1" to "9". And worse is that you don't even check if the indicated square on the playfield is still empty and thus available for modification.
In next code I suggest a solution for this:
; Read, calculate, and update draw position
update_draw:
ReDo:
call read_keyboard ; -> AL
sub al, '1'
cmp al, 8
ja ReDo ; Invalid key
mov bh, 0
mov bl, al
shl bx, 1 ; Double because the pointer list contains WORDS
mov bx, game_pointer[bx]
cmp byte ptr [bx], '_'
jne ReDo ; Square is not empty
mov al, 'x'
cmp player, '0'
je update
mov al, 'o' ; If it's not player 0, then it's surely player 1
update:
mov [bx], al
inc OccupiedSquares ; See next paragraph
ret
Ending the game
You say "It does not terminate in case of a draw". Well, it's even worse than that! Only if a 'win' is detected does the program end. What do you expect the players do when the playfield has no more vacant squares? That's the condition to check.
You could find out by looking at the 9 bytes that make up the grid, but much easier is to keep a count of the valid keys that the program received and if that count is equal to 9, you know that all the squares are occupied.
call update_draw
call check
cmp win_flag, 1
je game_over
cmp OccupiedSquares, 9
je game_over
xor player, 1 ; change_player
jmp main_loop
The game_over code in the end will start executing an endless loop. Why is this? The normal way to terminate the program would be to call a DOS function dedicated for that purpose:
fim:
mov ax, 4C00h ; DOS.Terminate
int 21h
Reducing the program's footprint
The check, check_line, check_column, and check_diagonal codes are too complicated. And you repeat too much of the same code!
The algorithm that you wrote in the check_diagonal routine is perfect for verifying the others too.
The difficult part here will be the pop ax instruction when a 'win' is detected. This pop removes the return address of the current call to the do_check subroutine. This in turn means that the ret that follows, will be returning to the parent of the whole check routine and that's exactly where you check your win_flag in order to decide about 'game over'.
check:
; 3 rows
mov dx, 2 ; jump size
mov si, 0
call do_check
mov si, 6
call do_check
mov si, 12
call do_check
; 3 columns
mov dx, 6
mov si, 0
call do_check
mov si, 2
call do_check
mov si, 4
call do_check
; 2 diagonals
mov dx, 8
mov si, 0
call do_check
mov dx, 4
mov si, 4
call do_check ; Must remain a tail-call!
ret
do_check:
mov bx, game_pointer[si]
mov al, [bx]
cmp al, "_"
je cont ; Empty square
add si, dx
mov bx, game_pointer[si]
cmp al, [bx]
jne cont ; Not identical letter
add si, dx
mov bx, game_pointer[si]
cmp al, [bx]
jne cont ; Not identical letter
mov win_flag, 1
pop ax ; Forget `call do_check`
cont:
ret
Solution 2:[2]
I added the line below under ;set segment registers
mov bp,9
After ;check if game ends I added the code below to calculate draw position
;Calculate draw position
sub bp,1
cmp bp,0
je game_drawn
Below game_over: I added game_drawn to print game draw message
game_drawn:
call clear_screen
lea dx, game_start_message
call print
lea dx, new_line
call print
lea dx, game_draw
call print
lea dx, new_line
call print
lea dx, game_draw_message
call print
lea dx, player_message
call print
jmp fim
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | Sep Roland |
| Solution 2 | Stephanie |
