6

I'm writing a game for the IBM PC (running MS-DOS), complete with fancy cutscenes. Unfortunately, though, the keys pressed during those fancy cutscenes are carried out once the fancy cutscene has finished, leading to the player's inevitable death.

I solved this problem by clearing the BIOS's keyboard buffer just after the cutscene finishes, using this subroutine:

proc ClearKeyboardBuffer
ClearKeyboardBuffer_loop:
    mov ah, 01h
    int 16h                         ; is there a key pressed
        jz ClearKeyboardBuffer_ret  ; if not, return
    mov ah, 00h
    int 16h                         ; "handle" the key
jmp ClearKeyboardBuffer_loop

ClearKeyboardBuffer_ret:
ret
endp ClearKeyboardBuffer

It seems a bit brute-force, though, and takes up many precious bytes that could be better spent containing content. I expected there to be a BIOS call for this or something, but I couldn't find one.

Are there any better (faster and/or shorter) ways to stop the program from paying attention to keys pressed during the cutscene?

  • I assume this is on-topic due to this meta policy; if I'm mistaken, don't hesitate to close-vote. (Also note that my IBM PC is actually just DOSBox in a suit and tie.) – wizzwizz4 Mar 30 at 14:06
  • 1
    Are you only using BIOS services (0x16) to read from the keyboard, or are you using DOS services (0x21)? – Stephen Kitt Mar 30 at 14:15
  • @StephenKitt Currently I'm only using BIOS services, but I can use DOS services (because it's going to be run from DOS, not as a stand-alone OS). – wizzwizz4 Mar 30 at 14:18
  • @wizzwizz4 As long as you stay withing the DOS/BIOS world it'd consider this perfectly fine. Sure, DOS is still not dead, but the job seams pretty confined. – Raffzahn Mar 30 at 19:36
  • 1
    You should consider using your own keyboard driver for games. I can't recall all the details so I won't put this as an answer, but it goes something like this: You take control of the interrupt the keyboard chip generates, register the keypress in your own code and then complete the interrupt with the PIT. Nothing will be buffered by the BIOS and you have complete control. Just remember to re-set the interrupt handler before you exit the program. – UziMonkey Apr 3 at 1:16
6

If you’re using BIOS functions to read from the keyboard in your game, the quickest way to clear the buffer is to make its tail equal to its head: read the value at 0x0041A and write it to 0x0041C:

proc clearkeyboardbuffer
; AX is clobbered
    push ds
    mov ax, 0040h
    mov ds, ax
    mov ax, [001Ah]
    mov [001Ch], ax
    pop ds
    ret
endp clearkeyboardbuffer

(The BIOS keyboard buffer is a circular list starting at 0x0041E, and 0x0041A points at the buffer’s head, and 0x0041C at its tail. If both pointers are equal, the buffer is empty.)

A possibly more compatible approach would be to use the DOS services. Interrupt 0x21 function 0x0C will clear the standard input buffer and can be combined with a subsequent operation in AL, e.g. 0x07 to read the next character without echoing it. This will allow your program to support redirection, different keyboard layouts, and replacement keyboard buffers. Whether that’s appropriate will depend on your specific requirements.

In a PC game you’d typically write your own keyboard handler, hooking IRQ 1 (interrupt 9), as suggested by Ross Ridge. See for example the Wolfenstein 3D keyboard handler, and its “clear keyboard” function.

  • 1
    It can’t be done in one ;-). I’ll write up the assembly in a little while. IIRC DOS 0x210C clears standard input, and you’d then need to use DOS for your keyboard input. – Stephen Kitt Mar 30 at 14:35
  • 1
    @wizzwizz4 Stephen's solution is perfect fine for BIOS handling, as both addresses are published within the BIOS manual. Then again, at the time of the (dead slow) original PC, there where several TSRs to extend the type ahead buffer, some not compatible with this solution. So going the DOS way would be preferable. It also reduces exposition, making it more portable to other OS with some layer of DOS compatibility. So preferable anyway. – Raffzahn Mar 30 at 19:44
  • 1
    @StephenKitt You may want to replace the 'absolute' memory address by the correct BIOS notation of 0040:001A (etc.), as 0040 is the BIOS segment and using these values is the clean way - sorry to be nitpicking here. Still, as long as there is no higher goal (And saving a few bytes on a machine with many KiB isn't), staying strictly within specs is baseline (And yes, I have collected more than a few unfavourable comments from programmers I supervised :)) – Raffzahn Mar 30 at 21:10
  • 1
    @RossRidge fixed, thanks; even though it’s longer, it should be faster, which is what the title is asking for... (I used ES originally because I cribbed this from some code I had which used ES as the BIOS segment.) – Stephen Kitt Mar 30 at 21:41
  • 3
    No need to turn off interrupts if copying head to tail. The BIOS used a lockless mechanism for the keyboard buffer. Being lockless also meant the consumer and the producer wouldn't update the same pointer. The producer (interrupt handler) only updates the tail (not the head). If you copy the head to the tail the interrupt handler will never change the head value so there is no risk of head changing between the instructions mov ax, [001Ah] mov [001Ch], ax so won't need interrupts being disabled. This is not the case if you copy the tail to the head. – Michael Petch Mar 31 at 4:26

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.