Я могу подтвердить, что код работает нормально, в этом все дело в сборке, вы можете делать что-то по-разному или даже делать это неправильно разными способами, еще раз спасибо!

отаю над заданием для университета, нам нужно создать простой клон breakout / arkanoid, он идет довольно хорошо, но я нашел ошибку, которая удаляла бы все на экране, эта ошибка случайна, но я подозреваю, что она связана с моя функция DrawPaddle. Может быть, вы можете обнаружить ошибку или иметь представление о том, почему видеопамять делает это.

Игра должна быть сделана с 16-битной сборкой ms-dos, я использую NASM + VAL + Dosbox для ее создания, я компилирую ее:

nasm -f obj test.asm
val test.obj

Игра просто перемещает весло на фиксированный экран с помощью стрелок на клавиатуре, вы также можете выйти из игры, нажав клавишу escape.

Пока все в порядке:https://puu.sh/yeKtG/affc912d4b.png и это выглядит так, когда программа переполняется:http://puu.sh/yeKEy/caeef089d1.png или жеhttp://puu.sh/yeKJH/1106e1e823.png

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

Это код DrawPaddle:

DrawPaddle:
    push di
    mov di, [paddleposition]
    mov cx, 5 ;the paddle will be 5 pixels tall
.p0:
    push cx
    mov cx, paddlesize
.p1:
    mov byte [es:di], bl
    inc di
    loop .p1
    add di, screenweight - paddlesize
    pop cx
    loop .p0
    pop di
    ret

И это полный код, он использует обработчик клавиатуры для чтения ввода и будет записывать непосредственно в видеопамять с использованием 320x200x256.

BITS 16

stacksize       EQU 0200h

;Constantes
;Direccion de inicio de la memoria de video
videobase       EQU 0a000h

;Definicion de colores
black           EQU 0
green           EQU 00110000b

;Screen data
screenweight    EQU 320


;Paddle data
startx      EQU 140
starty      EQU 170
paddlesize      EQU 40
paddlecolor     EQU 00101010b 

;Paddle movement limits
leftlimit       EQU starty * screenweight + 1 + 10 + 1
rightlimit       EQU ((starty + 1) * screenweight) - paddlesize - 10 - 1

segment mystack stack
    resb stacksize
stacktop:   

segment mydata data

;Variables
escpressed  dw 0
leftpressed     dw 0
rightpressed    dw 0
oldintseg       resw 1
oldintoff       resw 1
originalVideoMode resb 1
paddleposition  resw 1

segment mycode code
;Subrutinas

KeybInt:
        push    ds ;guardamos ds:ax       
        push    ax              

        mov ax, mydata ;los re-inicializamos
        mov ds, ax          

        cli

.getstatus:
        in      al, 64h
        test    al, 02h
        loopnz  .getstatus ;esperando a que el puerto esté listo

        in      al,60h ;obtenemos el codigo make o break de la tecla leida

        cmp     al, 01h ;revisamos si es escape
        jne     .revEsc 
        mov     word [escpressed], 1
        jmp     .kbread
.revEsc:
        cmp     al, 81h ;revisamos si el escape fue soltado
        jne     .revIzq
        mov     word [escpressed], 0
        jmp     .kbread
.revIzq:
        cmp     al, 4bh ;revisamos si es la flecha izquierda
        jne     .revDer
        mov     word [leftpressed], 1
        jmp     .kbread
.revDer:
        cmp     al, 4dh ;revisamos si es la flecha derecha
        jne     .revIzq2
        mov     word [rightpressed], 1
        jmp     .kbread
.revIzq2:
        cmp     al, 0cbh ;si se solto la flecha izquierda
        jne     .revDer2
        mov     word [leftpressed], 0
        jmp     .kbread
.revDer2:
        cmp     al, 0cdh ;o la derecha
        jne     .kbread
        mov     word [rightpressed], 0
        jmp     .kbread
.kbread:
        in      al, 61h     
        or      al, 10000000b
        out     61h, al            
        and     al, 01111111b                     
        out     61h, al                      
        mov     al, 20h
        out     20h, al               

        sti 

        pop     ax ;recuperamos ds:ax       
        pop     ds
        iret

DrawStage:
    push di
    push bx
    ;movemos el cursor a la posicion 10,10
    ;que seria en realidad 10*320+10
    mov di, (10 * screenweight) + 10
    ;ahora repetiremos esto 320-20 veces
    mov cx, 300
.h1:
    mov byte [es:di], green
    inc di
    loop .h1

    mov di, (190 * screenweight) + 10
    ;ahora repetiremos esto 320-20 veces
    mov cx, 301
.h2:
    mov byte [es:di], green
    inc di
    loop .h2    

    ;ahora volveremos al primer punto
    ;y dibujaremos hacia abajo
    mov di, (10 * screenweight) + 10
    ;y lo repetiremos 200-20 veces
    mov cx, 180
.v1:
    mov byte [es:di], green
    add di, screenweight
    loop .v1

    mov di, (10 * screenweight) + 310
    mov cx, 180
.v2:
    mov byte [es:di], green
    add di, screenweight
    loop .v2

    pop bx
    pop di
    ret

;Rutina para dibujar el palo
;Recibe en bl el color del mismo
DrawPaddle:
    push di
    mov di, [paddleposition]
    mov cx, 5 ;the paddle will be 5 pixels tall
.p0:
    push cx
    mov cx, paddlesize
.p1:
    mov byte [es:di], bl
    inc di
    loop .p1
    add di, screenweight - paddlesize
    pop cx
    loop .p0
    pop di
    ret

Delay1:
    mov dx, 4
    sub dx, 3
.pause1:
    mov cx, 6000
.pause2:
    dec cx
    jne .pause2
    dec dx
    jne .pause1
    ret

..start:
    mov ax, mydata
    mov ds, ax
    mov ax, mystack
    mov ss, ax
    mov sp, stacktop

    ;guardando el manejador actual
    mov ah, 35h
    mov al, 9h
    int 21h
    mov [oldintseg], es
    mov [oldintoff], bx

    ;instalando el manejador nuevo
    mov ax, mycode
    mov es, ax
    mov dx, KeybInt
    mov ax, cs
    mov ds, ax
    mov ah, 25h
    mov al, 9h
    int 21h

    ;restaurando el segmento de datos
    mov ax, mydata
    mov ds, ax

    ;guardando el modo de video y aplicando el nuevo
    xor ax, ax
    mov ah, 0fh
    int 10h
    mov [originalVideoMode], al
    mov ah, 00h
    mov al, 13h
    int 10h
    ;coordenada de inicio para el palo
    mov ax, (screenweight * starty) + startx
    mov word [paddleposition], ax
    mov ax, videobase
    mov es, ax

    call DrawStage
    mov bl, paddlecolor
    call DrawPaddle
    jmp .main

.main:
    call Delay1

    ;leemos las entradas
    cmp word [escpressed], 1
    je .dosexit
    cmp word [rightpressed], 1
    je .movRight
    cmp word [leftpressed], 1
    je .movLeft
    jmp .main
.movRight:
    mov bl, black
    call DrawPaddle
    cmp word [paddleposition], rightlimit
    je .ending
    inc word [paddleposition]
    jmp .ending
.movLeft:
    mov bl, black
    call DrawPaddle
    cmp word [paddleposition], leftlimit
    je .ending
    dec word [paddleposition]
    jmp .ending
.ending:    
    mov bl, paddlecolor
    call DrawPaddle
    jmp .main

.dosexit:
    ;restaurando el modo de video original
    mov ah, 00h
    mov byte al, [originalVideoMode]
    int 10h

    ;restaurando el manejador de teclado original
    mov dx, [oldintoff]
    mov ax, [oldintseg]
    mov ds, ax
    mov ah, 25h
    mov al, 9h
    int 21h
    mov al, 0
    mov ah, 4ch
    int 21h

Спасибо за чтение!

Ответы на вопрос(1)

Решение Вопроса

cx в вашей клавиатуре прерывание без сохранения.

^^^ этоОТВЕТ (что является причиной вашей ошибки), а не только некоторыесовет

Вот несколько советов:

Также кажется неправильным иметь какой-либо цикл (динамическую задержку) в прерывании, прерывания должны выполняться как можно быстрее.

Я не могу вспомнить из головы, какой правильный способ чтения портов клавиатуры 0x6X (я просто помню этоявляется немного сложно, чтобы он был полностью правильным), поэтому я не собираюсь проверять конкретныеin/out последовательность и ее правильность.

Но если вы установитеXXXpressed в прерывании фактическим текущим состоянием, и основной цикл будет слишком медленным, он может не видеть очень коротких нажатий клавиш (так как вход не буферизуется). Для простой игры, как клон арканоидов, это нормально, и меня это совсем не беспокоит, для меня это звучит как правильное поведение (на самом деле вам нужно быть невероятно быстрым, чтобы держать клавишу такой короткой).

Также вы можете избежатьds установка прерывания путем резервирования некоторого пространства данных рядом с обработчиком кода прерывания (перемещениеescpressed dw 0 в часть кода послеiret), а затем использовать это везде какmov word [cs:escpressed], 1и т.д. Общий штраф за использованиеcs: адресация внутри прерывания будет ниже, чемds настройку, если вы на самом деле устанавливаете флаги памяти более эффективным способом и короткий код прерывания (может быть значительно упрощен).

И это забавно, как широко вы используетемедленныйloop инструкция для всех основных циклов, но затем вdelay подпрограмма вы делаете быстрееdec cx jnz ... альтернатива.

И в конце я проверил, как написать обработчик клавиатуры DOS, так что это мое предложение (к сожалению, я не тестировал его, если он работает):

segment mycode code

escpressed      db 0
leftpressed     db 0
rightpressed    db 0

KeybInt:
        cli
        push    ax      ;guardamos ax

        ; when IRQ1 is fired, int 9 is called to handle it and the input
        ; already waits on port 0x60, no need to validate IBF flag on 0x64

        in      al,60h ;obtenemos el codigo make o break de la tecla leida
        mov     ah,al
        and     al,0x7F ; AL = scan code without pressed/released flag
        shr     ah,7
        xor     ah,1    ; AH = 1/0 pressed/released

        cmp     al, 01h ;revisamos si es escape
        jne     .checkLeft
        mov     [cs:escpressed], ah
        jmp     .kbread
.checkLeft:
        cmp     al, 4bh ;revisamos si es la flecha izquierda
        jne     .checkRight
        mov     [cs:leftpressed], ah
        jmp     .kbread
.checkRight:
        cmp     al, 4dh ;revisamos si es la flecha derecha
        jne     .kbread
        mov     [cs:rightpressed], ah
.kbread:

        in      al, 61h
        mov     ah, al          ; store original value
        or      al, 10000000b
        out     61h, al         ; set "enable kbd" bit
        mov     al, ah
        out     61h, al         ; set original value back

        mov     al, 20h
        out     20h, al         ; send end-of-interrupt signal to 8259 IC

        pop     ax ;recuperamos ax
        sti        ; not needed in real x86 real mode, IRET restores flags
        iret       ; but explicit STI paired with CLI may help some VMs

... тогда в коде игры, чтобы проверить состояние ключа, вы должны использоватьcs слишком:

        ...
        cmp     byte [cs:escpressed], 1
        ...
 cobolatrix04 нояб. 2017 г., 23:05
Я могу подтвердить, что код работает нормально, в этом все дело в сборке, вы можете делать что-то по-разному или даже делать это неправильно разными способами, еще раз спасибо!
 cobolatrix04 нояб. 2017 г., 22:33
Теперь, когда вы упомянули об этом, это было причиной (cx one), я ценю время, которое вы потратили на просмотр моего кода, у меня была эта ошибка в течение примерно 3 дней, и я продолжал работать с игрой, а теперь у меня был запасной раз я хотел его выдернуть, спасибо! PS: Я также проверю другие вещи, которые вы упомянули, но это будет позже: P
 Ped7g04 нояб. 2017 г., 22:48
@cobolatrix Я добавил свою попытку на код прерывания (также перемещает флаги данных вcs сегмент, так что вы должны исправить свой игровой код, чтобы реагировать на новые флаги). Но я на самом деле не запускал его, но он должен работать .... да,должен ... :) (вы, наверное, теперь понимаете, насколько эти вещи хрупкие)
 Ped7g04 нояб. 2017 г., 22:17
«Прерывание клавиатуры работает нормально» - на самом деле, в то время как я проверял некоторые документы, и ваш обработчик совершенно неверный, начальное чтение порта 64h, вероятно, совершенно неверно. ... о, ты не понимаешь, как поврежденcx вызывает оранжевый экран? : -о ... это прерывание, это установит вашcx в0 после того, как вы, например,mov cx, paddlesizeтак что следующийloop .p1 будет работать 65536 раз. (Напротив, ваши процедуры отрисовки на 100% в порядке и корректны, ошибок нет, их можно оптимизировать, но они правильные) .. так что это не СОВЕТЫ, а ОТВЕТ. Если вы не поняли, спросите меня больше, что объяснить :)
 cobolatrix04 нояб. 2017 г., 22:05
Привет, спасибо за ответ, это хороший вызов для сохранения cx, у меня не было никаких проблем из-за этого, но все же это хороший совет. Клавиатурное прерывание работает нормально, на данный момент меня не слишком интересует оптимизация, потому что этот оранжевый экран смертельной ошибки меня очень беспокоит, игра у меня уже завершена, но поскольку ошибка появляется в случайное время, вы не можете завершить игру, и я понятия не имею, почему это происходит

Ваш ответ на вопрос