I decided to do some fun with assembly and bare-metal to increase my programming skills for operating system development. So I decided to make game similar to 'space invaders'.
I have made some working code, but the problem comes with Interrupt 0x16 AH=0x01.
From Ralf Brown's Interrupt List we can read that this interrupt does NOT clear keyboard buffer. Because of that after clicking any control key once, my game believe that this key is kept pressed, and other key press are ignored, causing my ship to go infinity into one direction.
I have tried using AH=0x00 and it worked perfectly, however this would stop the game until any key is pressed, and I do not want that because I wont be able to update enemies position without waiting for user input.
I concider it to be problem with int 0x16 AH=0x01 and that the buffer is not cleared, however it may be something with either my lup
or with call keyPressControl
.
By the way I am using bochs to test my code and compiling my code with nasm
This is my code:
org 0x7C00
bits 16
xor ax,ax
mov ds,ax
mov es,ax
mov bx,0x8000
cli
mov ss,bx
mov sp,ax
sti
cld
clc
;clear 0x7E00 so we can check is code loaded later
xor ah,ah
mov BYTE [0x7E00], AH
;this code suppose to load game into memory
mov ah,0x2
mov al,0x4
xor ch,ch
mov cl,0x2
xor dh,dh
xor bx,bx
mov es,bx
mov bx,0x7E00
int 0x13
;code above should load code just above bootloader
;check if code is loaded
mov AH,[0x7E00]
cmp AH,0x0
je error
;Use normal jumpbecause bochs doesnt support Far Jumps (code often crashes)
jmp 0x7E00
;if cpu dont jump somehow then it will execute error and then hopefully halt
error:
;fill the screen with red color - the screen is supposed to run in graphics mode 0x2
mov ah,0x40
mov al,' '
cld
mov bx,0xb800
mov es,bx
xor bx,bx
mov di,bx
mov cx,0x0FA0
rep stosw
stosw
cli
hlt
times 0x1FE - ($ - $$) db 0x0
db 0x55
db 0xAA
;==============================
;game code
;==============================
;the game is designed to run in 16 bit real mode
;hide text mode cursor
mov ah,0x1
mov cx,0x2607
int 0x10
;show 'menu'
call clearTextModeScr
xor ax,ax
mov DS,AX
mov AX,GameName
mov SI,AX
xor AL,AL
mov bl,0x10
call printInTextMode
mov AX,TextInfo01
mov si,ax
mov al,0x6
call printInTextMode
mov AX,TextInfo02
mov si,ax
mov al,0x7
call printInTextMode
mov AX,TextInfo03
mov si,ax
mov al,0x8
call printInTextMode
mov AX,TextInfo04
mov si,ax
mov al,0x9
call printInTextMode
mov AX,TextInfo05
mov si,ax
mov al,0xA
call printInTextMode
mov AX,TextInfo06
mov si,ax
mov al,0xB
call printInTextMode
mov AX,TextInfo07
mov si,ax
mov al,0x17
call printInTextMode
;wait for x to start the game
mov bl,'x'
call waitForKeyPress
;init 0x13 graphics mode
call initGraphicsMode
lup:
;draw ship
xor ax,ax
mov ds,ax
mov si,ship
mov bx, [shipPosX]
mov al, [shipPosY]
call DrawPicture
call KeyPressControl
jmp lup
cli
hlt
;game functions
;this function will update new location of ship on screen according to player keypress
;this function will be using BIOS buffer from keyboard to not suspend the game
KeyPressControl:
pusha
pushf
mov ah,0x1
int 0x16
jz .end
cmp al,'a' ;left
je .left
cmp al,'d' ;right
je .right
cmp al,'s' ;backwards
je .backwards
cmp al,'w' ;forwards
je .forwards
jmp .end
.left:
cmp WORD [shipPosX], 0x00
je .end
dec WORD [shipPosX]
jmp .end
.right:
cmp WORD [shipPosX], 0x12D
je .end
inc WORD [shipPosX]
jmp .end
.backwards:
cmp BYTE [shipPosY], 0xA9
je .end
inc BYTE [shipPosY]
jmp .end
.forwards:
cmp BYTE [shipPosY], 0x00
je .end
dec BYTE [shipPosY]
jmp .end
.end:
popf
popa
ret
;this function winn wait till right key is pressed
;bl - ASCII key
;All registers and flags should be left the same
waitForKeyPress:
pusha
pushf
.loop:
xor ah,ah
int 0x16
cmp al,bl
jne .loop
popf
popa
ret
;this function will clear text mode screen to white text with black background
;All registers should be left the same
clearTextModeScr:
pusha
mov ah,0x0F
mov al,' '
cld
mov bx,0xb800
mov es,bx
xor bx,bx
mov di,bx
mov cx,0x0FA0
rep stosw
stosw
popa
ret
;holy shit
;this function will generate pseudo random value
; Following registers will be changed:
; AX - random generated number
random:
push DX
push BX
;multiplier is get by Read Time Stamp
RDTSC
mov BX,AX
mov AX,[randomSeed]
mul BX
add ax,0xB7FF
and ax,0x7FFF
;ax is random number
mov WORD [randomSeed], ax
pop BX
pop DX
ret
;this function will draw image on screen
;DS:SI - address of the picture start
;AL - Y coordinate (height)
;BX - X coordinate (width)
;Note that this funciton wont check for 0x13 graphics mode
DrawPicture:
pusha
mov cx,0x140
mul cx
add AX,BX
;AX is now offset on the screen
mov bx,0xA000
mov es,bx
mov di,ax
;memory location of screen is now written in ES:DI
;now we need to get the size of picture
lodsw ;get WORD into AX
;AL - X size
;AH - Y size
xor cx,cx
mov CL,AL
cld
.loopY:
push ax
push es
push di
push cx
.loopX:
lodsb
stosb
loop .loopX
pop cx
pop di
pop es
mov ax,0x140
add di,ax
pop ax
dec ah
cmp ah,0x0
jne .loopY
popa
ret
;this function will draw box on screen
;AL - Y coordinate (height)
;BX - X coordinate (width)
;CL - X size
;DL - colour
;DH - Y size
;All registers should be left the same
DrawBox:
pusha
;calculate the start point in memory
;multiply Y coordinate by 0x140 (width of each Y line)
push bx
mov bx,0x140
mul bx
pop bx
;add X
add AX,BX
;AX is now offset on the screen
mov bx,0xA000
mov es,bx
mov di,ax
cld
xor CH,CH
.loopY:
push es
push di
push cx
;.loopX will be repeated `CL` times
.loopX:
stosb
loop .loopX
pop cx
pop di
pop es
mov ax,0x140
add di,ax
;decrease dh
dec dh
;repeat loop `DH` times
cmp dh,0x0
jne .loopY
popa
ret
;this function will return length of string in CX
;DS:SI - address of the text
; Following registers will be changed
; DS:SI - location of last byte of the string in memory
; CX - length of the string
getStringLenght:
push ax
xor cx,cx
.loop:
lodsb
;check is it terminate character
cmp al,'#'
;if yes then finish
je .end
;increase CX (length) and execute loop again
inc cx
jmp .loop
.end:
pop ax
ret
;this function will not check is text mode set
;DS:SI - address of the text
;AL - Y coordinate (start)
;BL - X coordinate (start)
;text should be terminated with # sign
;
;All registers and flags should be left the same
printInTextMode:
pusha
pushf
push bx
mov bl,0x50
mul bl
pop bx
xor bh,bh
add ax,bx
mov bx,0x2
mul bx
mov di,ax
mov bx,0xb800
mov es,bx
mov ah,0x0F ;white colour on black background
cld
.loop:
lodsb
cmp al,'#'
je .end
stosw
jmp .loop
.end:
popf
popa
ret
SYSerror:
;set text mode graphics
call initTextMode
;if text mode graphics could not be set, then halt the CPU straight away
jc .fullerror
;fill the screen with red color
mov ah,0x40
mov al,' '
cld
mov bx,0xb800
mov es,bx
xor bx,bx
mov di,bx
mov cx,0x0FA0
rep stosw
stosw
;halt the CPU
.fullerror:
cli
hlt
;if error occured then carry flag will be set
;if function runned properly then carry flag will be cleared
;
;All registers should be left the same
initTextMode:
pusha
;setgup graphics mode 0x02
xor ah,ah
mov al,0x2
int 0x10
;get graphics mode
mov ah,0x0F
int 0x10
;check if the graphics mode is actually 0x02
cmp al,0x02
jne .err
;clear carry flag and return
popa
clc
ret
;error set carry flag and return
.err:
popa
stc
ret
;if error occured then carry flag will be set
;if function runned properly then carry flag will be cleared
;
;All registers should be left the same
initGraphicsMode:
pusha
;setup mode 0x13
xor ah,ah
mov al,0x13
int 0x10
;get graphics mode
mov ah,0x0F
int 0x10
;check if the graphics mode is actually 0x13
cmp al,0x13
jne .err
popa
clc
ret
.err:
popa
stc
ret
cli
hlt
;game data
;'variables'
randomSeed dw 0xBEAF
shipPosX dw 0x0000
shipPosY db 0x00
;Game texts
GameName db 'Lasers Lasers Lasers... And Even More Lasers#'
TextInfo01 db 'Controls:#'
TextInfo02 db 'A - Left#'
TextInfo03 db 'D - Right#'
TextInfo04 db 'W - Forward#'
TextInfo05 db 'S - Backward#'
TextInfo06 db 'Q - Weapon#'
TextInfo07 db 'Press x to begin game#'
;Game Pictures... YES PICTURES
;structure of pictures
;First WORD is size of picture. It is in following format
;ship 17x29 (0x11 * 0x1D) (1D is height, 11 is width)
ship:
dw 0x1F13
db 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
db 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
db 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
db 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
db 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x2A, 0x1E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
db 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x2A, 0x29, 0x29, 0x1E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
db 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x2A, 0x29, 0x29, 0x1E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
db 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x2A, 0x29, 0x29, 0x1E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
db 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x0F, 0x29, 0x0F, 0x1E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
db 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x0F, 0x0F, 0x0F, 0x1E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
db 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x1E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
db 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x1E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
db 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x1E, 0x00, 0x00, 0x00, 0x00, 0x00
db 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x1E, 0x00, 0x00, 0x00, 0x00, 0x00
db 0x00, 0x00, 0x00, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x1E, 0x00, 0x00, 0x00
db 0x00, 0x00, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x1E, 0x00, 0x00
db 0x00, 0x00, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x1E, 0x00, 0x00
db 0x00, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x1E, 0x00
db 0x00, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x1E, 0x00
db 0x00, 0x0F, 0x0F, 0x0F, 0x2A, 0x2A, 0x2A, 0x1E, 0x0F, 0x0F, 0x0F, 0x0F, 0x2A, 0x2A, 0x2A, 0x1E, 0x0F, 0x1E, 0x00
db 0x00, 0x00, 0x0F, 0x29, 0x2A, 0x2A, 0x2A, 0x29, 0x1E, 0x0F, 0x0F, 0x29, 0x2A, 0x2A, 0x2A, 0x29, 0x1E, 0x00, 0x00
db 0x00, 0x00, 0x0F, 0x29, 0x29, 0x2A, 0x29, 0x29, 0x1E, 0x0F, 0x0F, 0x29, 0x29, 0x2A, 0x29, 0x29, 0x1E, 0x00, 0x00
db 0x00, 0x00, 0x00, 0x29, 0x29, 0x2A, 0x29, 0x29, 0x00, 0x1E, 0x00, 0x29, 0x29, 0x2A, 0x29, 0x29, 0x00, 0x00, 0x00
db 0x00, 0x00, 0x00, 0x29, 0x29, 0x29, 0x29, 0x29, 0x00, 0x1E, 0x00, 0x29, 0x29, 0x29, 0x29, 0x29, 0x00, 0x00, 0x00
db 0x00, 0x00, 0x00, 0x00, 0x29, 0x29, 0x29, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x29, 0x29, 0x29, 0x00, 0x00, 0x00, 0x00
db 0x00, 0x00, 0x00, 0x00, 0x29, 0x29, 0x29, 0x00, 0x00, 0x00, 0x00, 0x00, 0x29, 0x29, 0x29, 0x00, 0x00, 0x00, 0x00
db 0x00, 0x00, 0x00, 0x00, 0x29, 0x29, 0x29, 0x00, 0x00, 0x00, 0x00, 0x00, 0x29, 0x29, 0x29, 0x00, 0x00, 0x00, 0x00
db 0x00, 0x00, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, 0x00, 0x00
db 0x00, 0x00, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, 0x00, 0x00
db 0x00, 0x00, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, 0x00, 0x00
db 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
times 0x1000 - ($ - $$) db 0x0
I think what you're asking for is a way of checking for keypresses and removing them from the buffer without waiting?
Just use int 0x16/AH=01 to check for a key and if a keypress is in the buffer use int 0x16/AH=00 to clear it from the buffer. Your detection of keys will be limited by key repeat rate and delay, however.
In practice, most (old...) games trapped int 9 (I think it was) to detect key press/release, and then queried the keyboard controller directly (using port I/O), rather than using the BIOS routines - which are not really designed with games in mind.