This is a program I wrote for my x86 assembly class which is basically the Spade Invaders game in all its glory. It uses Irvine32.inc which came with the textbook.
Space Invaders
title SpaceInvaders (spacegame.asm)
; Dani Horowitz
; CSC111 x86 Assembly Programming
INCLUDE Irvine32.inc
.data ; begin data segment
use_default byte 0 ; don't use user variables?
; ##### USER VARIABLES #####
; ## used if use_default=1 #
; ##########################
dodge_bullets byte 1 ; boolean for whether invaders can dodge bullets
invader_count dword 20 ; number of invaders wanted (min 14)
bottom_row byte 20 ; screen height (console vs fullscreen)
; ##########################
; ##########################
invader_char = 2 ; character symbol to represent
player_char = 1 ; character symbol to represent
bullet_char = 30 ; character symbol to represent
invader_color = red
player_color = lightCyan
bullet_color = yellow
default_color = white
; ##########################
invader_steps byte 1 ; number of rows and columns invaders move at a time
game_running byte 0 ; boolean value for whether game is running or not
; ##########################
bullet_row byte 0 ; current location of bullet (dh)
bullet_col byte 0 ; current location of bullet (dl)
invader_col byte 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70
invader_row byte 2, 5, 8, 5, 2, 5, 8, 5, 2, 5, 8, 5, 2, 5
current_loc byte ? ; player's current column (dl) location
direction byte ? ; player's left/right direction
inv_direction byte 0 ; current direction of invaders
; ##########################
time dword 0 ; iterations of the game loop (loop counter)
delay_time dword 70 ; length of pause time
points dword 0 ; invaders killed
counter dword 0 ; invaders that escaped
number_invaders dword 14 ; current number of invaders
icounter dword 0 ; invader loop counter
score sdword 0
; ##########################
msg_bottom byte 21 ; bottom location for message (dh)
msg_left = 2 ; left location for message (dl)
msg_right = 65 ; right location for score (dl)
; ##########################
stdInHandle dword ? ; keyboard input handler
buffer byte 80 DUP(?) ; keyboard input buffer
bug_fix_counter byte 0
; ##################################################
.code ; begin code segment
; ##################################################
; ##################################################
INIT_KEYBOARD PROC
; get handle to standard keyboard input
; populate the stdInHandle variable
; ##################################################
; abstraction is a wonderful, glorious thing!
INVOKE GetStdHandle, STD_INPUT_HANDLE
mov stdInHandle, eax
ret
; ##################################################
INIT_KEYBOARD ENDP
; ##################################################
; ##################################################
KYBD_HANDLER PROC
; set console flags to appropriate mode to read a single character
; determine if a new character is waiting in the buffer
; if yes, read it in and call the KEY_LISTENER procedure
; reset console flags to their previous values
; bug: all keypresses are executed twice ??
; ##################################################
.data
saveFlags dword ? ; default console flags
bytesRead dword ? ; number of bytes in buffer
.code
INVOKE GetConsoleMode, stdInhandle, saveFlags
INVOKE SetConsoleMode, stdInHandle, 0
; ----------
; peek in buffer and see if anything new is there
INVOKE PeekConsoleInput,
stdInHandle, ADDR buffer, 1, ADDR bytesRead
; if no, skip to the end
cmp bytesRead, 1
jne Done
; ----------
; if a keypress is waiting in the buffer,
; put its value into al and call KEY_LISTENER
; to do something with it
INVOKE ReadConsoleInput,
stdInHandle, ADDR buffer, 1, ADDR bytesRead
xor eax, eax
mov esi, OFFSET buffer
add esi, 14
mov al, [esi]
call KEY_LISTENER
Done:
INVOKE SetConsoleMode, stdInHandle, saveFlags
ret
; ##################################################
KYBD_HANDLER ENDP
; ##################################################
; ##################################################
PRINT_BLANK PROC
; assume dh and dl have already been set
; position cursor
; print a blank space to the screen
; ##################################################
mov al, ' '
call Gotoxy
call WriteChar
ret
; ##################################################
PRINT_BLANK ENDP
; ##################################################
; ##################################################
PRINT_INVADER PROC
; assume dh and dl have already been set
; position cursor
; set cursor color to the invader color
; print the invader character to the screen
; reset cursor color to the default color
; ##################################################
call Gotoxy
mov eax, invader_color
call SetTextColor
mov al, invader_char
call WriteChar
mov eax, default_color
call SetTextColor
ret
; ##################################################
PRINT_INVADER ENDP
; ##################################################
; ##################################################
PRINT_PLAYER PROC
; assume dh and dl have already been set
; position cursor
; set cursor color to the player color
; print the player character to the screen
; reset cursor color to the default color
; ##################################################
call Gotoxy
mov eax, player_color
call SetTextColor
mov al, player_char
call WriteChar
mov eax, default_color
call SetTextColor
ret
; ##################################################
PRINT_PLAYER ENDP
; ##################################################
; ##################################################
PRINT_BULLET PROC
; assume dh and dl have already been set
; position cursor
; set cursor color to the bullet color
; print the bullet character to the screen
; reset cursor color to the default color
; ##################################################
call Gotoxy
mov eax, bullet_color
call SetTextColor
mov al, bullet_char
call WriteChar
mov eax, default_color
call SetTextColor
ret
; ##################################################
PRINT_BULLET ENDP
; ##################################################
; ##################################################
INIT_GAME PROC
; determine if using default variables
; if not using defaults, call INIT_VARS procedure to prompt for them
; screen print that the game is starting
; call INIT_KEYBOARD procedure to set up the keyboard console input handler
; call CREATE_OBJS procedure to print the player and invaders on the screen
; call RUN_GAME procedure to run the game loop
; return to main when finished
; ##################################################
.data
msg byte "Game starting ...", 0
msg_howto_left byte "F to move left ", 0
msg_howto_right byte "J to move right", 0
msg_howto_fire byte "B to fire ", 0
msg_howto_quit byte "E to quit game ", 0
.code
mov eax, default_color ; set cursor to use default text color
call SetTextColor
.IF use_default == 0 ; use user-defined variables?
call INIT_VARS ; initialize global variables
call DumpRegs ; pretty print out what is in the registers
.ENDIF
mov edx, OFFSET msg
call WriteString ; print "Game starting"
call Crlf
; give user directions how to play
mov edx, OFFSET msg_howto_left
call WriteString
call Crlf
mov edx, OFFSET msg_howto_right
call WriteString
call Crlf
mov edx, OFFSET msg_howto_fire
call WriteString
call Crlf
mov edx, OFFSET msg_howto_quit
call WriteString
call Crlf
mov eax, 500
call Delay ; pause
call WaitMsg
call Clrscr
call INIT_KEYBOARD ; set up keyboard handler to get input
call CREATE_OBJS ; create visual objects
call RUN_GAME ; run the game
ret ; game over
; ##################################################
INIT_GAME ENDP
; ##################################################
; ##################################################
INIT_VARS PROC
; this is only invoked if use_default=0
; prompt for and populate user-defined value for dodge_bullets
; prompt for and populate user-defined value for invader_count
; prompt for and populate user-defined value for bottom_row
; use bottom_row value to also set the msg_bottom variable
; return to INIT_GAME when finished
; ##################################################
.data
promptA byte "Allow invaders to dodge bullets? (0=no 1=yes) ", 0
promptB byte "Maximum number of invaders? (minimum 14) ", 0
promptC byte "How many screen rows for your monitor? (20 or 45) ", 0
.code
PromptForA:
mov edx, OFFSET promptA ; prompt for dodge_bullets
call WriteString
call ReadInt ; assume user enters a small value
mov dodge_bullets, al ; and prey this doesn't break
.IF al!=0 && al!=1
jmp PromptForA
.ENDIF
; ----------
PromptForB:
mov edx, OFFSET promptB ; prompt for # of invaders
call WriteString
call ReadInt
mov invader_count, eax
cmp eax, 14
jl PromptForB
; ----------
PromptForC:
mov edx, OFFSET promptC ; prompt for screen resolution
call WriteString
call ReadInt
mov bottom_row, al
.IF al!=20 && al!=45
jmp PromptForC
.ENDIF
inc al
mov msg_bottom, al
ret
; ##################################################
INIT_VARS ENDP
; ##################################################
; ##################################################
RUN_GAME PROC
; expects a bunch of stuff to already be populated
; continuously runs game loop until all invaders accounted for
; by either having been killed or escaped
; call KYBD_HANDLER procedure to check for a key press
; move existing bullet up
; move a random invader down
; when appropriate, call the END_GAME procedure
; return to INIT_GAME when finished
; ##################################################
.data
msg_run_game byte "Game started ", 0
.code
; ##############################
mov dh, msg_bottom
mov dl, msg_left
call Gotoxy
mov edx, OFFSET msg_run_game
call WriteString
; ##############################
call Randomize ; randomize seed
mov game_running, 1 ; set game_running flag on
Step:
mov eax, delay_time ; pause
.IF delay_time<20
mov delay_time, 20
.ENDIF
call Delay
mov dh, 0
mov dl, 0
call Gotoxy ; position cursor at top left of screen
mov eax, counter
add eax, points
cmp eax, number_invaders ; loop if not all invaders accounted for
jge GameOver
call KYBD_HANDLER ; continously run
call MOVE_BULLET ; step bullet, if exists, up
call MOVE_INVADERS ; step invaders down
call PRINT_SCORE
inc time ; increment game time
cmp game_running, 0 ; game over if game_running flag switched to off
je GameOver
jmp Step
GameOver:
call END_GAME ; end game
ret
; ##################################################
RUN_GAME ENDP
; ##################################################
; ##################################################
END_GAME PROC
; assumes points has been populated with # of invaders killed
; assumes counter has been populated with # of invaders escaped
; assumes time has been populated with # of game loop iterations
; clears the screen
; pretty prints points, counter, time variables to the screen
; return to end of RUN_GAME when finished
; ##################################################
.data
msg_points byte "Invader Death Counter: ",0
msg_counter byte "Invaders That Escaped: ",0
msg_time byte "Game length: ", 0
.code
call Clrscr
; ----------
mov dh, 10
mov dl, 10
call Gotoxy
mov edx, OFFSET msg_points ; print points
call WriteString
mov eax, points
call WriteDec
; ----------
mov dh, 15
mov dl, 10
call Gotoxy
mov edx, OFFSET msg_counter ; print counter
call WriteString
mov eax, counter
call WriteDec
; ----------
mov dh, 20
mov dl, 10
call Gotoxy
mov edx, OFFSET msg_time ; print timer
call WriteString
mov eax, time
call WriteDec
; ----------
mov dh, 30
mov dl, 10
call Gotoxy
ret
; ##################################################
END_GAME ENDP
; ##################################################
; ##################################################
PRINT_SCORE PROC
; pretty print current game score at bottom right of screen
; ##################################################
.data
msg_score byte "Score: ", 0
msg_speed byte "Game Speed: ", 0
.code
mov dh, msg_bottom
mov dl, msg_right
call Gotoxy ; position cursor
mov edx, OFFSET msg_score
call WriteString ; write "Score: "
mov eax, score
call WriteInt ; write the value
; ----------
mov dh, msg_bottom
inc dh
mov dl, msg_left
call Gotoxy ; position cursor
mov edx, OFFSET msg_speed ; write "Game Speed: "
call WriteString
mov eax, 70
sub eax, delay_time
call WriteDec ; write the value
ret
; ##################################################
PRINT_SCORE ENDP
; ##################################################
; ##################################################
CREATE_OBJS PROC
; self-explanatory
; return to INIT_GAME when finished
; ##################################################
call Clrscr
call CREATE_PLAYER
call CREATE_INVADERS
ret
; ##################################################
CREATE_OBJS ENDP
; ##################################################
; ##################################################
CREATE_PLAYER PROC
; draw player at bottom center screen location
; populate player coordinate to correct value
; return to CREATE_OBJS when finished
; ##################################################
mov dh, bottom_row ; row current_bottom
dec dh
mov dl, 40 ; column 40
call PRINT_PLAYER
mov current_loc, 40 ; set current_loc variable
ret
; ##################################################
CREATE_PLAYER ENDP
; ##################################################
; ##################################################
CREATE_INVADERS PROC
; traverse a loop to draw 14 invaders at starting locations
; starting locations are read from byte arrays
; return to CREATE_OBJS when finished
; ##################################################
mov icounter, 0 ; initialize loop
PrintInvader:
mov esi, OFFSET invader_col ; get col
add esi, icounter
mov dl, [esi] ; set column value
mov esi, OFFSET invader_row ; get row
add esi, icounter
mov dh, [esi] ; set row value
call PRINT_INVADER ; print invader to the screen
inc icounter ; increment loop counter
cmp icounter, 14 ; did we do all 14 invaders?
jl PrintInvader ; if not, do next invader
ret
; ##################################################
CREATE_INVADERS ENDP
; ##################################################
; ##################################################
MOVE_PLAYER PROC
; invoked from within KEY_LISTENER
; assumes a keyboard char to move the player has been pressed
; prints empty character over player's current location
; moves player's current location variable one space left or right
; prints player character to screen at the new location
; doesn't allow player to move out of bounds
; return to KEY_LISTENER when finished
; ##################################################
.data
temp_dl byte ?
temp_dh byte ?
msg_move_player_left byte "Moving left ", 0
msg_move_player_right byte "Moving right ", 0
.code
inc bug_fix_counter ; bug workaround!!
.IF bug_fix_counter==2
mov bug_fix_counter, 0
jmp Done
.ENDIF ; end bug fix :(
mov dh, bottom_row ; erase from old location
dec dh
mov dl, current_loc
call PRINT_BLANK
mov temp_dh, dh
mov temp_dl, dl
; ----------
.IF direction == 'j' ; determine direction
cmp dl, 70
jge RightBound ; handle out of bounds
inc temp_dl ; increment dl
; ##############################
mov dh, msg_bottom
mov dl, msg_left
call Gotoxy
mov edx, OFFSET msg_move_player_right
call WriteString
; ##############################
.ELSE
cmp dl, 5
jle LeftBound ; handle out of bounds
dec temp_dl ; decrement dl
; ##############################
mov dh, msg_bottom
mov dl, msg_left
call Gotoxy
mov edx, OFFSET msg_move_player_left
call WriteString
; ##############################
.ENDIF
jmp Move
LeftBound:
mov dl, 5
jmp Move
RightBound:
mov dl, 70
Move:
mov dl, temp_dl
mov dh, temp_dh
mov current_loc, dl ; position cursor at new location
call PRINT_PLAYER ; print the player all pretty-like
Done:
ret
; ##################################################
MOVE_PLAYER ENDP
; ##################################################
; ##################################################
KEY_LISTENER PROC
; assumes al is populated with a character pressed from the keyboard
; initiates FIRE procedure if 'b' key pressed
; sets game_running flag to off if 'e' key is pressed
; otherwise, initiates MOVE_PLAYER procedure
; returns to KYBD_HANDLER when finished
; ##################################################
mov direction, al ; stick it in direction
.IF direction == 'b' ; if entered 'b' fire bullet
call FIRE
.ELSEIF direction == 'e'
mov game_running, 0
.ELSE
call MOVE_PLAYER ; else, move player
.ENDIF
Done:
ret
; ##################################################
KEY_LISTENER ENDP
; ##################################################
; ##################################################
FIRE PROC
; assumes 'b' key has been pressed to fire a bullet
; determine starting location of bullet
; draw bullet to screen if another bullet isn't in progress
; return to KEY_LISTENER when finished
; ##################################################
.data
msg_fire byte "Bullet fired ", 0
msg_fire_inprogress byte "Cannot Fire ", 0
.code
; ##############################
mov dh, msg_bottom
mov dl, msg_left
call Gotoxy
mov edx, OFFSET msg_fire
call WriteString
; ##############################
cmp bullet_col, 0 ; is a bullet currently in progress?
jne ExistingBullet ; if yes, skip down and do nothing
mov dh, bottom_row
sub dh, 2
mov bullet_row, dh ; position bullet's starting location
mov al, current_loc ; from player's current location
mov bullet_col, al
mov dh, bullet_row ; set bullet location
mov dl, bullet_col
call PRINT_BULLET ; print bullet to screen
sub score, 50
jmp Done
ExistingBullet:
; ##############################
mov dh, msg_bottom ; only one bullet allowed in the air at a time
mov dl, msg_left
call Gotoxy
mov edx, OFFSET msg_fire_inprogress
call WriteString
; ##############################
Done:
ret
; ##################################################
FIRE ENDP
; ##################################################
; ##################################################
MOVE_BULLET PROC
; determine if there are any bullets in progress
; if yes, move bullet up
; update bullet location
; erase bullet from old location and draw to new location
; initiate procedure to determine if bullet hit an invader
; if bullet reached top of screen, update variables to
; indicate no bullets are in progress
; return to loop in RUN_GAME when finished
; ##################################################
cmp bullet_row, 0 ; determine if bullet is in progress
je NoBullets ; if no, there is nothing to move
mov dh, bullet_row
mov dl, bullet_col
call PRINT_BLANK ; write empty string in current location
mov al, invader_steps ; move bullet_row up
sub bullet_row, al
mov dh, bullet_row
call PRINT_BULLET
call HIT_INVADER ; see if we hit any invaders
.IF dodge_bullets==1
call SHIFT_INVADERS ; shift invaders to the side
.ENDIF
; ----------
cmp bullet_row, 1 ; see if we reached the top of the screen
jle HideBullet
jmp NoBullets
HideBullet:
mov bullet_row, 0 ; set value to default (no bullets in progress)
mov bullet_col, 0 ; set value to default (no bullets in progress)
sub score, 100
NoBullets:
ret
; ##################################################
MOVE_BULLET ENDP
; ##################################################
; ##################################################
HIT_INVADER PROC
; traverse through each column an invader is in
; determine if bullet is in the same column
; if yes, check to see if rows match too
; if everything matches, invoke KILL_INVADER procedure
; return to MOVE_BULLET when finished
; ##################################################
mov icounter, 0
CheckCol:
mov esi, OFFSET invader_col ; find the invader column array
add esi, icounter
mov dl, [esi] ; get column location for invader
cmp bullet_col, dl ; compare to column location for bullet
je CheckRow ; if a match, check the row
inc icounter
cmp icounter, 14 ; loop for all invaders
jl CheckCol
jmp Done
CheckRow:
mov esi, OFFSET invader_row ; find the invader row array
add esi, icounter
mov dh, [esi] ; get row location for invader
mov dl, bullet_row ; get row location for bullet
sub dl, dh ; difference in location
.IF dl<=invader_steps && dl>=0 && dh!=bottom_row
call KILL_INVADER ; if we got here, he's dead
.ENDIF
Done:
ret
; ##################################################
HIT_INVADER ENDP
; ##################################################
; ##################################################
KILL_INVADER PROC
; delete bullet from screen
; draw an X where invader was killed
; pause, and then draw a blank space
; (need to handle this separately in case going at different speeds)
; move bullet location to default (none in progress)
; set invader row to bottom_row
; if we want another invader, create it!
; increase points value
; return to HIT_INVADER when finished
; ##################################################
.data
msg_kill_invader byte "Invader Killed ", 0
.code
; ##############################
mov dh, msg_bottom
mov dl, msg_left
call Gotoxy
mov edx, OFFSET msg_kill_invader
call WriteString
; ##############################
add score, 500
mov dh, bullet_row
mov dl, bullet_col
call PRINT_BLANK ; delete bullet from screen
; ----------
mov esi, OFFSET invader_row ; find the invader row array
add esi, icounter
mov dh, [esi]
mov esi, OFFSET invader_col ; find the invader col array
add esi, icounter
mov dl, [esi]
; ----------
mov al, 'X' ; X marks the spot
call Gotoxy
call WriteChar ; draw an X really quickly
mov eax, 100
call Delay
call PRINT_BLANK ; pause, and then draw a blank space over the X
; ----------
mov bullet_col, 0 ; move bullet column location to default
mov bullet_row, 0 ; move bullet row location to default
mov esi, OFFSET invader_row ; find the invader row array
add esi, icounter
mov dh, bottom_row
mov eax, invader_count
.IF number_invaders < eax ; do we want more invaders?
mov dh, 2 ; if yes, create a new one
inc number_invaders
.ENDIF
mov [esi], dh ; set invader row to bottom_row
inc points ; increase points value
sub delay_time, 2 ; game runs faster as it progresses
ret
; ##################################################
KILL_INVADER ENDP
; ##################################################
; ##################################################
MOVE_INVADERS PROC
; find a random invader
; if the invader is already gone, choose another one
; move the chosen invader down
; if invader reached the bottom, delete the invader
; if we want another invader, create it!
; return to loop in RUN_GAME when finished
; ##################################################
.data
prevent_loop byte 0 ; just in case ...
.code
mov prevent_loop, 0
ChooseInvader: ; prevent an infinite loop if
inc prevent_loop ; no invaders are available
cmp prevent_loop, 14
jg Done
; ----------
mov eax, 14 ; generate random number between 0-13
call RandomRange
mov icounter, eax ; put random number into icounter
PrintInvader:
mov esi, OFFSET invader_col ; get invader row array
add esi, icounter
mov dl, [esi] ; set dl to current column of icounter invader
mov esi, OFFSET invader_row ; get invader column array
add esi, icounter
mov dh, [esi] ; set dh to current row of icounter invader
; ----------
cmp dh, bottom_row ; if invader is gone already, choose another invader
jge ChooseInvader
; ----------
call PRINT_BLANK ; delete invader from current location
; ----------
add dh, invader_steps
mov al, bottom_row
dec al
cmp dh, al ; did we reach bottom?
jge HideInvader ; if yes, delete invader
call PRINT_INVADER
Continue:
mov [esi], dh ; update invader array
jmp Done
HideInvader:
sub score, 100
mov dh, bottom_row ; position below player
inc counter ; add to invaders that escaped counter
call PRINT_BLANK
mov eax, invader_count
.IF number_invaders < eax ; do we want more invaders?
mov dh, 2 ; if yes, create a new one
inc number_invaders
.ENDIF
jmp Continue
Done:
ret
; ##################################################
MOVE_INVADERS ENDP
; ##################################################
; ##################################################
SHIFT_INVADERS PROC
; only when a bullet is in the air:
; every few iterations, move all invaders right or left
; don't let invaders go out of bounds
; return to MOVE_BULLET when finished
; ##################################################
mov icounter, 0
ShiftInvaders:
mov esi, OFFSET invader_row ; find the invader row array
add esi, icounter
mov dh, [esi] ; get row location for invader
mov esi, OFFSET invader_col ; find the invader column array
add esi, icounter
mov dl, [esi] ; get column location for invader
cmp dh, bottom_row
jge NextInvader
; ----------
call PRINT_BLANK ; write a blank space to current location
; ----------
.IF inv_direction == 5 ; wobble back and forth
add dl, invader_steps
.ELSEIF inv_direction == 10
sub dl, invader_steps
.ENDIF
.IF dl < 5 ; don't let invaders go out of bounds
mov dl, 5
.ELSEIF dl > 70
mov dl, 70
.ENDIF
mov [esi], dl
; ----------
mov esi, OFFSET invader_row ; get invader row array
add esi, icounter
mov dh, [esi] ; set dh to current row of icounter invader
mov esi, OFFSET invader_col ; get invader column array
add esi, icounter
mov dl, [esi] ; set dh to current column of icounter invader
call PRINT_INVADER
; ----------
NextInvader:
inc icounter
cmp icounter, 14 ; loop for all invaders
jl ShiftInvaders
.IF inv_direction < 15 ; don't wobble every time
inc inv_direction
.ELSE
mov inv_direction, 0
.ENDIF
ret
; ##################################################
SHIFT_INVADERS ENDP
; ##################################################
;---------------------------------------------------
main proc
; our glorious starting point
;---------------------------------------------------
call Clrscr ; clear the screen
call INIT_GAME ; initialize game variables and start game
call WaitMsg
exit ; game over!
main endp
end main
;---------------------------------------------------
gsbr 0 Newbie Poster
randutty 0 Newbie Poster
Dani 4,310 The Queen of DaniWeb Administrator Featured Poster Premium Member
Dani 4,310 The Queen of DaniWeb Administrator Featured Poster Premium Member
Be a part of the DaniWeb community
We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.