在平局的情况下井字游戏不会终止
Posted
技术标签:
【中文标题】在平局的情况下井字游戏不会终止【英文标题】:Tic-tac-toe game does not terminate in case of a draw 【发布时间】:2022-01-17 11:33:15 【问题描述】:我正在使用汇编语言制作井字游戏。平局不会终止。我该如何纠正它? 下面是代码:
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
【问题讨论】:
您是否希望此代码检测到平局并终止?即这是一个调试问题,还是寻求帮助实现更多功能?如果是后者,请用文字解释您的设计;这比我们大多数人想要阅读的代码要多,尤其是在没有 cmet 的情况下,像game_pointer
这样的重要内容,不管是什么。 (其中一些似乎过于复杂:do_check_diagonal:
可能是一个辅助函数,你 call
两次,而不是发明这个 CX 调度逻辑。虽然完全排除一些逻辑是个好主意。)
另外,像je add_1
这样有条件地跳过jmp add_2
的东西应该只是jne add_2
。
【参考方案1】:
纠正初始化
您的 game_pointer 数组由 bytes 组成,而 set_game_pointer 例程写入 words。此错误不会损害程序,因为对 game_draw 字符串的所有引用都碰巧有它们的高字节零,而紧跟在内存中指针列表之后的 win_flag 无论如何都从零开始. 首选的解决方案是将指针列表定义为单词并在汇编时填充列表,因为它的初始化不值得运行时代码。
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$"
改进用户输入
您忽略了验证用户输入!您假设它始终是从“1”到“9”的数字。更糟糕的是,您甚至没有检查运动场上指示的方格是否仍然是空的,因此可以进行修改。 在接下来的代码中,我建议了一个解决方案:
; 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
结束游戏
你说“它不会在平局的情况下终止”。好吧,它甚至比这更糟糕!只有当检测到“胜利”时,程序才会结束。当比赛场地没有更多空位时,您希望球员们做什么?这是要检查的条件。 您可以通过查看构成网格的 9 个字节来找出答案,但更容易的是保留程序接收到的有效键的计数,如果该计数等于 9,您就知道所有的方格都被占用了。
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
最后的 game_over 代码将开始执行无限循环。为什么是这样?终止程序的正常方法是调用专门用于该目的的 DOS 函数:
fim:
mov ax, 4C00h ; DOS.Terminate
int 21h
减少程序的占用空间
check、check_line、check_column和check_diagonal代码太复杂了。而且你重复了太多相同的代码!
您在 check_diagonal 例程中编写的算法也非常适合验证其他算法。
当检测到“胜利”时,这里的困难部分将是pop ax
指令。这个pop
将当前call
的返回地址删除到do_check 子例程。这反过来意味着随后的 ret
将返回整个 check 例程的父级,而这正是您检查 win_flag 以决定'游戏结束'。
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
【讨论】:
“比那更糟”?平局是没有赢家的全盘,因此假设获胜检测有效,平局检测是在检查最后一步不是获胜举措后的全盘检测。但是,是的,移动计数器是一种简单的方法,因为您没有 AI 玩家,因此不需要扫描棋盘以查找(并考虑)空方格的功能。 顺便说一句,这个“game_pointer”额外的间接级别对我来说似乎效率很低而且很麻烦;可能更容易在电路板打印功能中即时格式化,因此其他所有内容都可以直接用整数索引电路板。【参考方案2】:我在 ;set 段寄存器下添加了以下行
mov bp,9
之后;检查游戏是否结束我添加了下面的代码来计算平局位置
;Calculate draw position
sub bp,1
cmp bp,0
je game_drawn
在game_over下面:我添加了game_drawn来打印游戏抽奖信息
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
【讨论】:
以上是关于在平局的情况下井字游戏不会终止的主要内容,如果未能解决你的问题,请参考以下文章