Домашнее задание — КРЕСТИКИ-нолики — решение проблемы

#assembly #mips

#сборка #mips

Вопрос:

  1. Эй, ребята, у меня проблема с пониманием поведения моей программы. Я пытаюсь сохранить адрес для возврата, но он продолжает каким-то образом перезаписываться. Это домашнее задание, так что не давай мне простых ответов! Возможно, просто объяснение того, как jal можно было бы перезаписать мой $s0 реестр. Я не совсем понимаю, как это возможно. Я помещаю комментарии в код, чтобы вы могли видеть, где происходит цикл.

2.

 .data                               # data segment for variables and strings

# These are all the strings that will need to be outputted to the console

title: .asciiz "Tic-Tac-Toe!nn"
oprompt: .asciiz "O player: Please enter row/col, one per line:nn"
xprompt: .asciiz "X player: Please enter row/col, one per line:nn"
invposstr: .asciiz " is an invalid position. Try again.nn"
owinstr: .asciiz "O  won  the  game,nn  exiting. . ."
xwinstr: .asciiz "X  won  the  game,nn  exiting. . ."
catstr: .asciiz "CAT  game,nn  exiting. . ."
border: .asciiz "nn _ _ _ n"

# Initialze the board with 9 bytes

board: .byte 'X','X','X','X','X','_','X','_','_' 

.text                       # text segment for instructions

main:                       # begin the main func

li $v0, 4               #set v0 to print string
la $a0, title               #set string to print (in $a0) as title
syscall                 #execute

li $s1, 0               #use s1 as the game counter (up to 9 turns)


playerx:                #player x will always go first

li $v0, 4               #prepare to print string
la $a0, xprompt         #load the get move prompt
syscall

jal get_valid_move          #go get a valid move address

# I am having trouble returning here

li $t0, 'X'             #load asciiz code for X
sb $t0, ($v0)               #store the X into the board address

addi $s1, 1             #increment the game counter to next turn

jal print_board         #after getting the move, print the board status

# And here  

playero:                #player o will always go second

li $v0, 4               #prepare to print string
la $a0, oprompt             #load the get move prompt
syscall

jal get_valid_move          #go get a valid move address    

# And here

li $t0, 'O'             #load asciiz code for O
sb $t0, ($v0)               #store the O into the board address

addi $s1, 1             #increment the game counter to next turn

jal print_board         #after getting the move, print the board status

# And here

j playerx               #go back to player x turn


# I store the current return address below (first time) 

print_board:                #function to print the status of the board

move $s0, $ra           #save the recent return address

la $t1, board           #load the board address

li $v0, 4           #set v0 to print string
la $a0, border          #set string to print (in $a0) as prompt
syscall             #execute

li $t0, 0           #use as counter for loop


loop:               #use loop to print 1 row at a time

    li $v0, 11      #prepare to print char
    li $a0, 124     #asciiz code for |
    syscall

    lb $a0, ($t1)       #load next byte from board
    li $v0, 11          #prepare to print char
    syscall

    addi $t1, $t1, 1        #increment address of board to next byte/char
    addi $t0, $t0, 1        #increment loop counter

    beq $t0, 3, rowend      #go print special stuff at end of row
    beq $t0, 6, rowend      #go print special stuff at end of row
    beq $t0, 9, rowend      #go print special stuff at end of row

    j loop              #go back to top

rowend:

    li $a0, 124         #asciiz for |
    li $v0, 11          #prepare to print char
    syscall

    li $a0, 10          #ascizz for newline
    li $v0, 11          #prepare to print char
    syscall

    blt $t0, 9, loop        #unless at the last row, loop

    li $a0, 10          #ascizz for newline
    li $v0, 11          #prepare to print char
    syscall

    jr $ra              #return to the gameplay in main


entry_address:                  #function to transform user input (row, col) into board address

move $t0, $a1                   #make a copy of row for use
move $t1, $a2               #make a copy of col for use     
addi $t0, $t0, -1           #use n-1 for accessing storage address
addi $t1, $t1, -1           #use n-1
mul $t0, $t0, 3             #mul the row by 3
la $v0, board               #load base address of the board
add $v0, $v0, $t0           #add the row offset
add $v0, $v0, $t1           #add the col offset
                #v0 now holds the correct board position address

jr $ra                  #now go back to check if valid move


valid_position:

li $v0, 1           #assume valid
blt $a1, 1, inv         #if row is less than 1
bgt $a1, 3, inv         # or if row is greater than 3
blt $a2, 1, inv         # or if col is less than 1
bgt $a2, 3, inv         # or if col is greater than 3 change validity

jr $ra              #go back to get_valid_move at last instruction

inv:
li $v0, 0           #since branched, change to inv
jr $ra              #go back to get_valid_move at last instruction


# I store the current return address into s0 below (the 2nd time)

get_valid_move:

move $s0, $ra           #save the recent return address

li $v0, 5           #prepare to read row int
syscall
move $a1, $v0           #copy the read int into argument to be passed
li $v0, 5           #prepare to read col int
syscall
move $a2, $v0           #copy the read int into argument to be passed

jal valid_position      #go make sure pos is valid
beq $v0, 0, invalid     #go tell user if valid_position returned false 

jal entry_address       #position was good so go get entry_address

# THE JR STATEMENT BELOW IS LOOPING BACK HERE
# I need to return to the last instruction in main which I thought I stored in $s0

li $t0, '_'         #use underscore for comparison
lb $t1, ($v0)           #load the byte from the entry_address
bne $t0, $t1, invalid           #if entry is taken, get another move

jr $s0              #since move was valid go back to gameplay in main

invalid:

    li $v0, 1       #prepare to print int
    move $a0, $a1       #load row numb
    syscall
    li $v0, 11      #prepare to print char
    li $a0, 44      #load comma asciiz
    syscall
    li $v0, 1       #prepare to print int
    move $a0, $a2       #load col numb
    syscall
    li $v0, 4       #prepare to print string
    la $a0, invposstr   #load invalid pos message
    syscall

    j get_valid_move    #repeat the user input loop


check_all_match:
print_winner_and_exit:
row_winner:
col_winner:
diag_winner:
check_for_win:

# After printing all the results, we can exit the program
exit:

li $v0, 10              # set v0 to exit
syscall                 # execute exit
 

Комментарии:

1. У меня такое чувство, что мне нужно начать реализацию стека для моих обратных адресов. В противном случае, возможно, более простым решением было бы прикрепить ярлыки в main, чтобы я мог просто переходить к ним напрямую, когда мне нужно. Какое решение было бы более эффективным / правильным?

2. Стек является более гибким и может использоваться везде одинаково. Если вы когда-нибудь захотите использовать реальные подпрограммы, это будет правильный путь. Но если вы знаете, куда идет каждый переход, вам не нужна гибкость.

3. Я реализовал стек. Мой код, похоже, выполняется в одношаговом режиме точно так, как задумано. Однако при полном запуске я застреваю в том же цикле. Как это вообще возможно?

4. Неважно! Я не открывал стек после восстановления обратного адреса! Черт возьми, это всегда самая маленькая вещь.