在平局的情况下井字游戏不会终止

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

减少程序的占用空间

checkcheck_linecheck_columncheck_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

【讨论】:

以上是关于在平局的情况下井字游戏不会终止的主要内容,如果未能解决你的问题,请参考以下文章

java井字棋游戏 多人版哦

井字棋游戏升级版 - TopTicTacToe项目 简介

使用 gprof 分析非终止 C 程序

TicTacToe Java 检查抽奖

Optaplanner 在使用自定义过滤器类时不会终止

我不能在不使用指针的情况下返回二维数组!在制作井字游戏时,这是我的上移功能