#  Tom Bombem in x86 Assmbler version 0.32
#
#  by Vince Weaver <vince@deater.net>
#
#  assemble with    "as -o tb_asm.o tb_asm.ix86.s"
#  link with        "ld -o tb_asm tb_asm.o"


# GAME CONSTANTS

.equ SCREEN_WIDTH,	40
.equ SCREEN_HEIGHT,	24
.equ BYTES_PER_PIXEL,	 2
.equ NUM_MISSILES,	 2
.equ NUM_ENEMIES,	 6
.equ SCROLL_SPEED,	 2
.equ UP_SHIELDS,	32
.equ WAVE_SIZE,		16 	# power of 2
.equ WAVES_TILL_BOSS,	 6	# plus level*2	

        # from ~/linux/include/asm/unistd.h
.equ SYSCALL_READ,	    3	
.equ SYSCALL_WRITE,   	    4
.equ SYSCALL_OPEN,	    5
.equ SYSCALL_CLOSE,	    6
.equ SYSCALL_IOCTL,	   54
.equ SYSCALL_FCNTL,	   55
.equ SYSCALL_GETTIMEOFDAY, 78
.equ SYSCALL_NANOSLEEP,	  162

        # from ~/linux/include/asm/ioctls.h
.equ TCGETS,   0x5401
.equ TCSETS,   0x5402

        # from ~/linux/include/asm/fcntl.h
.equ F_GETFL,  	      3
.equ F_SETFL,         4
.equ O_NONBLOCK, 0x0800
.equ O_RDONLY,	  0x000
.equ O_WRONLY,    0x001
.equ O_CREAT,	  0x040
.equ O_TRUNC,	  0x200
.equ SIXSIXSIX,	   0666



        # from ~/linux/include/asm/termbits.h
.equ C_LFLAG_OFF, 12	
.equ VMIN_OFF,	  23
.equ ICANON,	   2
.equ ECHO,	   8

       # struct offsets
.equ ENEMIES_OUT,   		0
.equ ENEMIES_EXPLODING,		1
.equ ENEMIES_XADD,		2
.equ ENEMIES_YADD,		3
.equ ENEMIES_XMIN,		4
.equ ENEMIES_XMAX,		5
.equ ENEMIES_KIND,		6
.equ ENEMIES_X,			7
.equ ENEMIES_Y,			9
.equ ENEMIES_SIZE,		11

.equ MISSILE_OUT,		0
.equ MISSILE_X,			1
.equ MISSILE_Y,			2


      # game_flags bit
.equ PAUSED,	     0x01
.equ SOUND,	     0x02
.equ SOUND_PENDING,  0x04 
.equ PERFECT_SHIELD, 0x08
.equ PERFECT_ENEMY,  0x10
.equ PERFECT_SHOT,   0x20
.equ ALL_THREE,	     PERFECT_SHIELD|PERFECT_ENEMY|PERFECT_SHOT

.include "data.header"

	.globl _start
_start:	

	#####################################
	# decompress data segment
		
# LZSS decompression algorithm implementation
# by Stephan Walter 2002, based on LZSS.C by Haruhiko Okumura 1989
# optimized some more by Vince Weaver

        # we used to fill the buffer with FREQUENT_CHAR
	# but, that only gains us one byte of space in the lzss image.
        # the lzss algorithm does automatic RLE... pretty clever
	# so we compress with NUL as FREQUENT_CHAR and it is pre-done for us


	mov     $(N-F), %bp   	     	# R
	mov  	$tb_data_begin, %esi	# %esi points to logo (for lodsb)
	mov	$DATA_OFFSET, %edi	# point to out_buffer

decompression_loop:	
	lodsb			# load in a byte

	mov 	$0xff, %bh	# re-load top as a hackish 8-bit counter
	mov 	%al, %bl	# move in the flags

test_flags:
	cmp	$tb_data_end, %esi # have we reached the end?
	jge	done_decompress    # if so, exit

	shr 	$1, %ebx	# shift bottom bit into carry flag
	jc	discreet_char	# if set, we jump to discreet char

offset_length:
	lodsw                   # get match_length and match_position
	mov %eax,%edx		# copy to edx
	    			# no need to mask dx, as we do it
				# by default in output_loop
	
	shr $(P_BITS),%eax	
	add $(THRESHOLD+1),%al
    
	mov %al,%cl             # cl = (ax >> P_BITS) + THRESHOLD + 1
                                #                       (=match_length)
		
output_loop:
        and 	$POSITION_MASK,%dh  	# mask it
	mov 	text_buf(%edx), %al	# load byte from text_buf[]
	inc 	%edx	    		# advance pointer in text_buf
store_byte:	
	stosb				# store it
	
	mov     %al, text_buf(%ebp)	# store also to text_buf[r]
	inc 	%ebp 			# r++
	and 	$(N-1), %bp		# mask r

	loop 	output_loop		# repeat until k>j
	
	or	%bh,%bh			# if 0 we shifted through 8 and must
	jnz	test_flags		# re-load flags
	
	jmp 	decompression_loop

discreet_char:
	lodsb				# load a byte
	inc	%ecx			# we set ecx to one so byte
					# will be output once
					# (how do we know ecx is zero?)
					
        jmp     store_byte              # and cleverly store it

done_decompress:
# end of LZSS code


   	#####################################
        # setup non-echo mode 
        #
     
         			    	# ioctl(0,TCGETS,&old_tty)  
				    	# AKA tcgetattr(0,&old_tty); 

 	mov	$old_tty,%edx	    	# save values to retstore at end
	call	tcgetattr
	
	mov	$new_tty,%edx	    	# get values to modify
	call	tcgetattr

        		 	    	# turn off "canonical mode" 
				    	# by clearing ICANON bit
        			    	# new_tty.c_lflag&=~ICANON;
	
	andl    $(~ICANON),(new_tty+C_LFLAG_OFF)
									
				    	# turn off "echo mode" 
				    	# by clearing ECHO bit
				    	# new_tty.c_lflag&=~ECHO;

        andl   $(~ECHO),(new_tty+C_LFLAG_OFF)

	       			        # set minimum number of characters 
				    	# to read as 1
        			    	#   new_tty.c_cc[VMIN]=1;

	movb 	$1,(new_tty+VMIN_OFF)

				     	# apply our new tty values
	call	tcsetattr		# %edx still points to new_tty
	
	
	##################################
        # make stdin non-blocking
        # 
	  				# use fctl rather than fctl64
	push    $SYSCALL_FCNTL		# for compatibility (no need of 2GB)
	pop	%eax
	xor	%ebx,%ebx		# stdin = 0
	push	$F_GETFL		# we want to get flags
	pop	%ecx
	int	$0x80

	mov	%eax,%edx		# copy result of GETFL to edx
	orw	$O_NONBLOCK,%dx		# set the NONBLOCK bit
	
	push	$SYSCALL_FCNTL
	pop	%eax	      		# use fctl again
	xor	%ebx,%ebx		# stdin = 0
	push	$F_SETFL		# want to set flags this time
	pop	%ecx
	int	$0x80

	###############################
        # clear the framebuffer
	
	call	clear_framebuffer

	###############################
        # clear the screen 

	mov     $clear_screen,%ecx	# send the escape code to clear screen
	call 	write_stdout

	##############################
        # draw VMW logo

	mov     $vmw_sprite,%esi        # Load in sprite
	mov	$((0x8<<8)+0x6),%ax     # Load in x and y co-ords
	call	blit_219

		    			# put "A VMW Software Production"
	            			# white, x=7, y=15
	mov	$vmw_string,%esi
	call	put_text_line_inline

        call	dump_to_screen	        # dump framebuffer to screen

	push	$3
	pop	%ecx		        # Pause 3 seconds
	call	pause_a_while
   
   	################################
	# Main Menu Loop
   
opening_screen:  

	###############################
        # clear the framebuffer

	call	clear_framebuffer
 
 	##############################
        # title screen

 	mov     $opener_sprite,%esi    # load the title sprite
	xor	%eax,%eax	       # at 0,0
	call	blit_219
		    		       # Yellow, title_string,x=0 y=5
	mov	$title_string,%esi     # Malicious Marketers
	call	put_text_line_inline
	   
        call	dump_to_screen	       # copy to screen

	#########################
	# pause for 10 seconds
	# or less if key pressed
	
	push    $10
	pop	%ecx
	call	pause_a_while

	#########################
	# set variables

	xor	%ebp,%ebp    	       # menu-item=0

	#########################
	# draw attention block
	#

	xor	%bl,%bl		       # color = 0
	call	put_attention_block

	####################################
	# put the "press F1 for help" string
	
	mov	$menu_help_string,%esi      	# color grey x=0 y=20
	call	put_text_line_inline
	
        ####################################
	# draw the menu
	
	mov	$((5<<16)+(10<<8)+6),%eax       # x=10 y=6
	call 	do_menu

        cmp	$0,%bp
	jne	check_1
	call	new_game
	jmp	opening_screen	
check_1:	
	cmp	$1,%bp
	jne	check_2
	call	do_about
check_2:	
	cmp	$2,%bp
	jne	check_3
	call	do_story
	jmp	opening_screen
check_3:	
	cmp	$3,%bp
	jne	check_4
	call	do_hi_score
	call	wait_until_keypressed        

check_4:		
	cmp	$4,%bp
	jne	check_5
	call	verify_quit
	jnz	exit
	jmp	opening_screen
check_5:	
	cmp	$5,%bp
	jne	end_handle_lf
	call	display_help

end_handle_lf:
	jmp	opening_screen	 
 
 
        #################################
	# Exit
	#################################
exit:

     	mov	$default_colors,%ecx	# restore from line-draw mode
	call	write_stdout

	andb	$(~SOUND),game_flags	# turn sound off
	mov	$(750<<16)+125,%ebx	# default sound=750Hz 125ms
	call	play_sound		# No way I know of to save/restore 
					# actual values

	mov	$old_tty,%edx		# restore the saved tty values
	call	tcsetattr

     	xor     %ebx,%ebx		# set return value as zero
	xor     %eax,%eax
	inc     %eax 	 		# put exit syscall number (1) in eax
        int     $0x80                   # and exit



	#################################
	# verify quit
	#################################
	# zero flag clear if we do want to quit
verify_quit:

	mov 	$15,%bl
	call	put_attention_block	# put a white attention block

	mov     $quit_line,%esi
	call	put_text_line_inline	# put the text

	xor	%ebp,%ebp
	mov	$(5<<24)+(2<<16)+(9<<8)+8,%eax
					# offset 5, 2 lines
					# at 9,8

					# slide into do_menu


	#########################
	# Do menu
	#########################
	# ax= x,y   dx=label_offset,num_options
	# returns %bp=option
	# destroys %ecx
do_menu:	
	push	%eax

write_menu_loop:
	xor	%ecx,%ecx	        # clear line counter
	pop	%eax		        # restore x,y,label_offset,num_options
	push	%eax

do_menu_loop:	
	mov	%eax,%edx
	ror	$16,%edx 	        # move label_offset,num_options -> dx
	
	cmp	%ecx,%ebp	        # are we the selected menu?
	jne	menu_no_arrow	        # if not, no_arrow
	
	mov	$menu_arrow,%esi        # if so, arrow
	mov	$13,%bh		        # set text color pink

	jmp	write_menu_text
	
menu_no_arrow:
        mov	$menu_blank,%esi        # not selected, so blank
        mov	$5,%bh		        # set text color purple
	
write_menu_text:
	mov	$14,%bl		        # yellow arrow
	call	put_text_xy	        # write out the arrow or blank
	
	mov	%bh,%bl		        # swap in text color
	
			                # point to the proper string
				       
	add	%dh,%cl
	lea	menu_new_string(%ecx,%ecx,8),%esi
	sub	%dh,%cl
	
	add	$5,%ah	 	        # text 5 right of arrow

	call	put_text_xy	        # put the text
	
	inc	%al		        # increment y
	sub	$5,%ah		        # restore x
	inc	%ecx		        # increment the count

	cmp	%dl,%cl		        # have we shown them all?
	jne	do_menu_loop	        # if not, loop

        call	dump_to_screen	        # dump results to screen
  
menu_key_loop:  
        call	get_a_char	        # get a character
	jnc	menu_check_key	        # if valid key, check it
	call	milisec_nanosleep
	jmp	menu_key_loop
       
menu_check_key:       
	pop    %edx   		        # convoluted way
	push   %edx		       	# to get num_options into %dl
	shr    $16,%edx	
	xor    %dh,%dh
	
check_q:
	cmp     $'q',%al	        # are we 'q'?
	jne	check_h
	mov	%dx,%bp			# if so move to last menu item
	dec	%bp
check_h:
	cmp	$'h',%al		# are we 'h'?
	jne	check_i
	mov	$5,%bp 			# if so move beyond and 
	mov	$'\n',%al		# trigger enter
	
check_i:
	cmp	$'i',%al		# are we up?
	jne	check_m
dec_menu:
	sub	$1,%bp
	jns	write_menu_loop		# if not, ok
	mov	%dx,%bp			# if so, wrap
	dec	%bp
	jmp	write_menu_loop
	
check_m:
	cmp	$'m',%al		# are we down?
	jne	check_j
inc_menu:
	inc	%bp			# move down
	cmp	%dx,%bp			# are we too far?
	jne	write_menu_loop		# if not ok
	xor	%bp,%bp			# if so, wrap
	jmp	write_menu_loop
	
check_j:
	cmp	$'j',%al		# are we right?
	je	dec_menu		# then pretend we are down
	
check_k:
	cmp	$'k',%al		# are we left?
	je	inc_menu		# then pretend we are up
	
check_LF:
	cmp	$'\n',%al		# check for enter/space
	je	exit_menu
	cmp	$'\r',%al
	je	exit_menu
	cmp	$' ',%al
	je	exit_menu

	jmp 	write_menu_loop		# unknown key, loop
	
exit_menu:
	pop	%eax			# clean up stack
	xor	$0,%ebp			# set zero flag with result
		
	ret





	######################
	# NEW GAME
	######################
	#
	
new_game:
	 
	# re-clear all bss variables to zero

	xor	%eax,%eax
	mov	$(game_vars),%edi
	mov	$(end_game_vars-game_vars),%ecx

	rep 	
	stosb

	call	load_hi_score	        # load hi_score

	###########################
	# init vars that need to be initialized

        movw	$(18<<8),x		# ship's x=18
	movb	$8,shields		# shields start as 8
	movb	$1,level		# level starts as 1

	##########################
	# Starting a new level!

new_level:
	call	clear_framebuffer	# clear screen

	mov	$8,%bl
	call	put_attention_block	# put attention block

	mov	$menu_blank,%esi
	mov	$15,%bl
	mov	$8,%al
	mov	%bl,%ah
	call	put_text_xy
	mov	$menu_blank,%esi
	mov	$20,%ah
	call	put_text_xy		# clear an opening
	

	mov	$level_line+3,%esi
	mov	$16,%ah
	call	put_text_xy		# print "LEVEL:"

	xor	%eax,%eax
        movb    level,%al               # Convert level to ascii
	mov     $level_string,%edi
	call    num_to_ascii
			
	mov     $level_string,%esi      # print level_string
	mov     $((23<<8)+8),%eax
	mov     $15,%bl
	call    put_text_xy
							
	call	dump_to_screen	   	
	
        push	$2	     		# Pause 2 seconds or until keypress
     	pop	%ecx
	call	pause_a_while

	##############################
	# set new_level variables
	# hackish but decreses code size
	
	mov	$enemies_spawned,%edi
	xor     %eax,%eax
	inc	%eax	 		# eax = 1
	stosw				# enemies_spawned = 1
	neg     %ax			# ax=-1
	stosw				# current_init_x = -1
	stosb				# current_enemy_kind = -1
	push 	$30
	pop	%eax			# al=30
	stosb				# enemy_wait=30
	xor  %eax,%eax			# al=0
	
#	add  $6,%al
	
	stosb				# current_enemy_type=0

#	xor  %eax,%eax	
	stosb				# enemy_wave=0
		
	orb	$ALL_THREE,game_flags	# set the three bonuses to ON
	
	movb	level,%al		# mave waves_till_boss
	add	%al,%al			# equal WAVES_TILL_BOSS + 2*level
	addb	$WAVES_TILL_BOSS,%al
	mov	%al,waves_till_boss

	########################
     	# RANDOMIZE TIMER
	
	call randomize_timer

	# Draw the Stars

	xor    	%eax,%eax		# clear eax
	
	mov	$1600,%ecx		# clear the background
	mov	$background,%edi
	push	%edi
	rep	
	stosb
	
	mov     $100,%ecx		# we want to draw 100 stars
draw_stars:
	pop	%edi			# point to "background"
	push	%edi
	push	$40
	pop	%ebx			# move 40 into bx
	call 	random			# call rand()%40 (y)
	imulb	%bl			# multiply result by 40 (screen width)
	add	%eax,%edi		# add to background pointer
	
	call	random			# call rand()%40 again (x)
	add	%eax,%edi		# add this to background pointer
	
	push	$2
	pop	%ebx			# call rand()%2
	call	random
	
	add	$7,%al			# put either light or dark grey star
	
	stosb				# and store it
	
	loop 	draw_stars		# loop
	pop	%edi

main_game_loop:

	############
	# clear screen
	############

	call	clear_framebuffer	# clear the screen
       
        ###########
	# Scroll the Stars
	###########

        mov	scroll_frames,%bx	 # get scroll val from memory
	mov	scroll,%ax		 # get scroll value
	inc	%bx			 # increment frames
	cmp	$SCROLL_SPEED,%bx	 # have we had enough frames to scroll?
	jge	scroll_it      	 	 # if so move ahead
	mov	%bx,scroll_frames	 # else store scroll back
	jmp	show_stars		 # and jump to the stars
	
scroll_it:
        movw	$0,scroll_frames	 # clear scroll_frames
	
	sub	$40,%ax			 # decrement it
	jns	write_scroll		 # is it less than zero?
	mov	$(40*40-40),%ax	    	 # if so wrap to (40*40-40)
write_scroll:
	movw  	%ax,scroll		 # store new scroll value to RAM
        
show_stars:
	mov	$background,%esi	# background as src
        mov	$40,%bl	     		# 40 wide
	mov	$'.',%dl		# stars are dots
		
	cmp	$(20*40),%ax		# do we have to wrap?
	jg	half_scroll		# if so we need 2 blits

	#########
	# plain 40x20 blit

	add	%eax,%esi		# else just offset into background
	xor	%eax,%eax		# put at 0,0
	mov	$20,%dh			# 20 high

	call	blit_already_know_xsize_ysize
	jmp	done_scrolling		# done putting stars

half_scroll:

	##############
	# top half of wrap blit
	
	add	%eax,%esi      		# offset into background
	idiv	%bl			# divide offset by 40 to get scroll
	
	push	%eax			# store this offset
	
	# scroll=%al
	# we want x=0,y=0
	# we want xsize=40,ysize=40-scroll
	# we want framebuffer=background+40*scroll
	
	mov  	$40,%dh
	sub  	%al,%dh  		# ysize=40-scroll
	
	xor	%eax,%eax		# x=0,y=0

	call	blit_already_know_xsize_ysize	

	################
	# bottom half of wrap bit

	mov	$background,%esi 	# start at top of background
	pop	%eax			# restore scroll value
	
	# scroll=%al
	# we want y=40-scroll
	# we want ysize=scroll-20
	
	mov  	%al,%dh
	sub  	$20,%dh  		# ysize=scroll-20
	
	neg	%al
	add	$40,%al			# y=40-scroll		
	xor	%ah,%ah			# x=0

	call	blit_already_know_xsize_ysize	
	
 
done_scrolling:

 	#############
 	# Put out new enemies 
	#############
		
	#################################################
    	# Keep a count of how close to place enemies 
     	# Don't want all to appear at once           
	
	incb	between_enemy_delay 	# between_enemy_delay++
	movb	enemy_wait,%al		# get time to wait
	cmp	%al,between_enemy_delay	# is delay>wait?
	jne	move_enemies		# if not, no enemies this frame

	movb	$0,between_enemy_delay	# reset between_enemy_delay

	###########################
	# See if we want to change the current enemy type

	cmpb    $7,current_enemy_type
	jne	not_boss
	movw	$1,enemies_spawned	# don't keep enemy count w boss
not_boss:	
	  
	# each wave is WAVE_SIZE-1.  Should we make this random?
	  
	mov     enemies_spawned,%ax
	test	$(WAVE_SIZE-1),%al      # See if multiple of 16
	jnz	same_enemy_type
	  
	  
	###############################
	# CHANGE THE ENEMY TYPE
	  
	incw	enemies_spawned		# if we keep at multiple of 16
					# we have problems if enemy
					# not output right away. so inc
	
	push	$6
	pop	%ebx
	call	random	
	mov	%al,current_enemy_type	# current_enemy_type=rand()%6
	
	incb	enemy_wave		# increment which wave we are on
		
	movb	waves_till_boss,%al			
	cmpb	%al,enemy_wave		# have we reached boss yet?
	jl	not_boss_yet		# if not, no boss yet
	
	movb	$6,current_enemy_type	# if so, set type to prepare_for_boss
	
not_boss_yet:	

	#####################################
	# Set some constants
	# These can be over-ridden later?
	
set_enemy_constants:

	#################
	# issue speed
	
	mov	$25,%ah			# enemy wait standard is 25

	mov	level,%al
	dec	%al	 		# 1 -> 0 for better math
	shl	$3,%al
	sub	%al,%ah			# adjust for level
	
	cmp	$5,current_enemy_type	
	jne	check_wait_underflow
	sub	$10,%ah
check_wait_underflow:	
	cmp	$0,%ah
	jge	write_out_enemy_wait
	xor	%ah,%ah
	
write_out_enemy_wait:	
	movb	%ah,enemy_wait


	movb	$-1,current_enemy_kind
	movw	$-1,current_init_x
	
same_enemy_type:

	#############################
	# Find an empty enemy slot
	
	mov	$enemy_0,%ebp		# point to first enemy
	push	$NUM_ENEMIES
	pop	%ecx	    		# loop through all enemies
	
find_enemy_slot:	
	mov	%ebp,%esi
	lodsb				# get enemy[i].out	
	test	$1,%al			# is the enemy out?
	jz	add_enemy		# if not keep going

	add	$ENEMIES_SIZE,%ebp	# move to next enemy struct
	loop	find_enemy_slot		# and loop

	jmp	move_enemies

add_enemy:

	##################
	# first see if need to wait till all enemies gone
	# (types 2 and 6 do this)

	cmpb	$2,current_enemy_type	
	jne	check_type_6
	
	movb	total_enemies_out,%al	# are total enemies_out >0?
	cmp	$0,%al
	jne	move_enemies		# if so, wait longer


	movb	$3,current_enemy_type	# all gone, so move on to type 3

	push	$6
	pop	%ebx
	call	random
	mov	%al,current_enemy_kind	# current_enemy_kind=rand()%6;
	
	push	$36
	pop	%ebx
	call	random
	mov	%al,%ah			# ah is the significant part
	mov	%ax,current_init_x	# current_init_x=rand()%36
	jmp	setup_enemy_defaults

check_type_6:
	cmpb	$6,current_enemy_type	
	jne	check_type_7
	
	################
	# before boss stuff
	
	movb	total_enemies_out,%al		# are total enemies_out >0?
	cmp	$0,%al
	jne	move_enemies			# if so, wait longer
	
	mov	$9,%bl
	call	put_attention_block		# put up a text_box
	
	mov	$bonus_points_line,%esi		# print bonus point title
	call	put_text_line_inline
	
	testb	$(ALL_THREE),game_flags		# see if we have a bonus
	jz	no_bonus_at_all
	
	testb	$(PERFECT_SHIELD),game_flags	# do we have perfect shields?
	jz	no_pshield_bonus

	mov	$no_shield_line,%esi		# if so print message
	call	put_text_line_inline
	addl	$1000,score	    		# add 1000 to score
	
no_pshield_bonus:

	testb	$(PERFECT_ENEMY),game_flags	# did we kill all enemies?
	jz	no_penemy_bonus
	
	mov	$shot_every_line,%esi		# if so print message
	call	put_text_line_inline		
	addl	$5000,score			# add 5000 to score
	
no_penemy_bonus:

	testb	$(PERFECT_SHOT),game_flags	# did we never miss a shot?
	jz	no_pshot_bonus
	
	mov	$perfect_shot_line,%esi		# if so, print message
	call	put_text_line_inline
	addl	$5000,score	    		# add 5000 to score

no_pshot_bonus:
	jmp     done_showing_bonus
	
no_bonus_at_all:
	mov	$no_bonus_line,%esi		# print no bonus message
	call	put_text_line_inline
	
done_showing_bonus:
	call	dump_to_screen
	
        call	wait_long_time
	call	wait_until_keypressed

	#################
	# setup boss

	mov	$boss_x,%edi
	xor	%eax,%eax
	mov	$13,%ah
	stosw	       			# movw $(13<<8),boss_x
	mov	$1,%ah
	stosw	      			# movw	$(1<<8),boss_xadd

	push	$40
	pop	%ebx
	call	random
	add	$10,%ax
	stosw	       			# movw	%ax,boss_count

	xor     %al,%al
	stosb	       			# movb 	$0,boss_smoke
	stosb				# movb	$0,boss_exploding
	inc	%al
	stosb	   			# movb    $1,boss_waiting
	
	mov	level,%al
	shl	$2,%al
	add	$20,%al
	stosb	       			# movb	%al,boss_hits

	movb	$7,current_enemy_type
	movb	$20,enemy_wait
	jmp	move_enemies
	
check_type_7:

	# if boss, and he's waiting, don't produce enemies

	cmpb  	$7,current_enemy_type
	jne	setup_enemy_defaults
	cmpb	$0,boss_waiting
	jne	move_enemies
	 
	
	###############################
	# setup enemy defaults
setup_enemy_defaults:
 	incw	enemies_spawned		    # enemies_spawned++
        incw	total_enemies_out	    # total_enemies_out++;

	mov	%ebp,%edi
	mov	$1,%al
	stosb	      		        # movb	$1,ENEMIES_OUT(%ebp)        
					# enemies[i].out=1
	xor	%al,%al
	stosb  				# movb	$0,ENEMIES_EXPLODING(%ebp)  
					# enemies[i].exploding=0;


	add	$2,%edi			# skip xadd,yadd

	stosb				# movb	$0,ENEMIES_XMIN(%ebp)	    
					# enemies[i].xmin=0;
	mov	$37,%al
	stosb	       			# movb	$37,ENEMIES_XMAX(%ebp)	    
					# enemies[i].xmax=37;
					
	
	
	##########################################
	# if enemy_kind < 0 then kind=random()%6
	# else use enemy_kind value

	movb    current_enemy_kind,%al	# get enemy_kind
	cmp	$0,%al			# is it less than 0?
	jge	no_random_kind		# if not move ahead
	push	$6
	pop	%ebx			# get rand()%6
	call	random
no_random_kind:
	stosb  				# movb	%al,ENEMIES_KIND(%ebp)  
					# enemies[i].kind=enemy_kind

	######################################
	# if init_x < 0 then x=random()%37
	# else, use the "init_x" value

	movw     current_init_x,%ax   	# get init_x
	cmp	 $0,%ax			# is it less than zero?
	jge	 no_random_x		# if so skip ahead
random_x:	
        push	 $36
	pop	 %ebx			# get random%36
	call	 random
	mov	 %al,%ah		# high byte is significant
no_random_x:
	stosw				# movw    %ax,ENEMIES_X(%ebp)	
					# store enemies[i].x
	xor	 %eax,%eax
	stosw	 	  		# movw	$0,ENEMIES_Y(%ebp)	    
					# enemies[i].y=0;
	

	#################################
	# enemy type specific inits

	movb	current_enemy_type,%bl
	movb	level,%dl
	shl	$4,%dl
	cmp	$0,%bl
	jne	enemy_type_1

	#####################
	# ENEMY_TYPE 0
	# diagonal, no wait, movement proportional to level
	
enemy_type_0:	
	movb	%dl,ENEMIES_XADD(%ebp)	# xadd = level*8
	movb	%dl,ENEMIES_YADD(%ebp)	# yadd = level*8
	jmp	move_enemies

	##########################
	# ENEMY_TYPE 1
	# Horizontal, then fall.
	
enemy_type_1:
	sub  	$1,%bl
	jnz	enemy_type_3

	mov	$5*8,%al
	movb	%al,ENEMIES_XADD(%ebp)	# enemies[i].xadd=5*8; 

	push	$100
	pop	%ebx
	call	random
	neg	%al
	movb	%al,ENEMIES_YADD(%ebp)	# enemies[i].yadd=-(rand()%100);

	jmp	move_enemies

	##########################
	# Enemy Type 3
	# type two waits for clear
	# then diagonal, all of same type

enemy_type_3:  		     	     	# skip type 2
	sub  	$2,%bl
	jnz	enemy_type_4

	mov	$5*8,%al
	movb	%al,ENEMIES_XADD(%ebp)	# enemies[i].xadd=5; 

	movb	%dl,ENEMIES_YADD(%ebp)  # enemies[i].yadd=level;

	jmp	move_enemies


	###########################
	# Enemy Type 4
	# "wiggle"

enemy_type_4:
	sub  	$1,%bl
	jnz	enemy_type_5
	
	mov	$4*8,%al
	movb	%al,ENEMIES_XADD(%ebp)	# enemies[i].xadd=4;

	movb	%dl,ENEMIES_YADD(%ebp)  # enemies[i].yadd=level;

	push	$34
	pop	%ebx
	call	random
	add	$2,%al

	movb	%al,ENEMIES_XMIN(%ebp)	# enemies[i].xmin=2+rand()%34;
	shl	$8,%ax
	movw	%ax,ENEMIES_X(%ebp)	# enemies[i].x=enemies[i].xmin*8;
	
	push	$20
	pop	%ebx
	call	random
	mov	%al,%dl
	mov	ENEMIES_XMIN(%ebp),%al
	add	%dl,%al
	
	cmp	$35,%al
	jle	xmax_ok
	mov	$35,%al
	
xmax_ok:
	mov	%al,ENEMIES_XMAX(%ebp)	# enemies[i].xmax=enemies[i].xmin+(rand()%20);
	
	jmp	move_enemies

	#########################
	# Enemy Type 5
	# "rain"

enemy_type_5:
        sub  	$1,%bl
	jnz	enemy_type_7

	push	$4
	pop	%ebx
	call	random 			# get rand()%4
		
	test	$3,%al			# see if bottom two bits 00
	jnz	no_use_own_x		# ak, one chance in 4
	
	mov	x,%ax			# one chance in 4 use 
	mov	%ax,ENEMIES_X(%ebp)	# enemies[i].x=x;
	
no_use_own_x:	
	
	movb	$0,ENEMIES_XADD(%ebp)		# enemies[i].xadd=0;
	movb	%dl,ENEMIES_YADD(%ebp)		# enemies[i].yadd=2;

	jmp	move_enemies

	#########################
	# Enemy Type 7
	# things flung by boss

enemy_type_7:
	movb    $0,ENEMIES_XADD(%ebp)		# enemies[i].xadd=0; 
	movb	%dl,ENEMIES_YADD(%ebp)		# enemies[i].yadd=2;
	
	mov	boss_x,%ax
	add	$5,%ah
	mov	%ax,ENEMIES_X(%ebp)	# enemies[i].x=(boss.x+5)*8;
	movw	$(3<<8),ENEMIES_Y(%ebp)	# enemies[i].y=3*8;

  	#######################
	# Move Enemies
	#######################
	
move_enemies:       
       
	mov	$enemy_0,%ebp		# point to first enemy
	push	$NUM_ENEMIES
	pop	%ecx	    		# loop through all enemies
	
move_enemies_loop:	
	mov	%ebp,%esi
	lodsb				# get enemy[i].out	
	test	$1,%al			# is the enemy out?
	jz	loop_move_enemies	# if so keep looping

move_enemies_x:
	xor     %eax,%eax
	inc	%esi	 		# point to xadd
	
	lodsb
	cmp	$0,%al			# is it zero?
	je	move_enemies_y		# if so, move along to y

	cbw				# sign extend al to ax
	addw	%ax,ENEMIES_X(%ebp)	# enemies[i].x+=enemies[i].xadd
		
	# check x bounds
	
	mov	ENEMIES_X(%ebp),%dx
	cmp	ENEMIES_XMAX(%ebp),%dh	# is enemies[i].x>enemies[i].xmax?
	jg	switch_x_dir		# if so, switch direction
	cmp	ENEMIES_XMIN(%ebp),%dh	# is enemies[i].x<enemies[i].xmin?
	jl	switch_x_dir		# if so, switch direction
	jmp	move_enemies_y
	
switch_x_dir:	
	neg	%al
	mov	%al,ENEMIES_XADD(%ebp)
	cbw
	addw	%ax,ENEMIES_X(%ebp)
	
move_enemies_y:

	xor     %eax,%eax
	lodsb
	
	# check for special case II 
	# if yadd<0 do back and forth until yadd positive
	
	cmp	$0,%al			    # is it less than zero?
	jge	no_special_case		    # if not, do normal thing
	
	addb    $1,ENEMIES_YADD(%ebp)	    # enemies[i].yadd++
#	cmpb	$0,ENEMIES_YADD(%ebp)	    # are we zero?
	jnz     ship_enemy_collision_detect # if not, keep going
	
	mov	level,%al		    # done <-> now fall
	shl	$4,%al			    # 
	mov	%al,ENEMIES_YADD(%ebp)	    # enemies[i].yadd=2*level;
	movb    $0,ENEMIES_XADD(%ebp)	    # enemies[i].xadd=0;

	jmp	ship_enemy_collision_detect

no_special_case:

	add	%ax,ENEMIES_Y(%ebp)	    # enemies[i].y+=yadd

	cmpb	$18,ENEMIES_Y+1(%ebp)	    # is y>18?
	jle	ship_enemy_collision_detect # if not move ahead
	
	decb	total_enemies_out	    # we are off screen.  remove
	movb	$0,ENEMIES_OUT(%ebp)	    # enemies[i].out=0
	andb	$~PERFECT_ENEMY,game_flags

ship_enemy_collision_detect:

	##########################################
	# Ship <-> Enemies Collision Detection
	##########################################
	
	cmpb	$0,ENEMIES_EXPLODING(%ebp)  # if enemy is exploding
	jne	loop_move_enemies	    # don't check for collision
	
	movw	x,%ax			    # check x
	mov	%ah,%al
	add	$6,%al 			    # through x+6

	movw	ENEMIES_X(%ebp),%dx	    # check enemies[i].x
	mov	%dh,%dl
	add	$2,%dl 			    # through enemies[i].x+2
	
	call	check_inside
	jnc	loop_move_enemies
	
	mov	$16,%ah			    # check y
	mov	%ah,%al
	add	$2,%al 			    # through y+2

	movw	ENEMIES_Y(%ebp),%dx	    # check enemies[i].y
	mov	%dh,%dl
	add	$1,%dl 			    # through enemies[i].y+1
	
	call	check_inside
	jnc	loop_move_enemies
	
	movb	$1,ENEMIES_EXPLODING(%ebp)
	decb	shields

	andb	$~PERFECT_SHIELD,game_flags

	movl	$(120<<16+50),sound_freq
	orb	$SOUND_PENDING,game_flags	# set_sound(120,50);
      
loop_move_enemies:
	add	$ENEMIES_SIZE,%ebp	# move to next enemy struct
	sub	$1,%ecx			# loop, but more than 128 away
	jnz	move_enemies_loop	# so have to do it by hand
	
        ###################
	# Draw the Enemies
	###################

	# Make out,exploding flags?
	# out,exploding,kind,x,y
        # xadd,yadd,xmin,xmax
	
	mov	$enemy_0,%ebp		# point to first enemy
	push	$NUM_ENEMIES
	pop	%ecx	    		# loop through all enemies
	
draw_enemies:	
	mov	%ebp,%esi
	lodsb				# get enemy[i].out	
	test	$1,%al			# is the enemy out?
	jz	loop_draw_enemies	# if so keep looping

	xor	%eax,%eax
	lodsb				# load "exploding" info
	cmp	$0,%al
	je	not_exploding	
	
handle_exploding:
	push	%eax
	shr	$1,%al			
					#     *2 for slow-down reasons)
	lea	explosion_sprites(,%eax,8),%edx
					# base+offset*8
	pop	%eax			# restore exploding 
	inc	%al			# increment exploding
	mov	%al,ENEMIES_EXPLODING(%ebp)		# store back exploding
	
	cmp	$7,%al			# is exploding > 7?
	jle	cont_handle_exploding
	
	movb	$0,0(%ebp)	        # enemy[i].out=0
	decb	total_enemies_out	# enemies_out--
	
cont_handle_exploding:
	add	$5,%esi			# skip to enemies_x   
	jmp  	enemies_xy
	
not_exploding:
	xor     %eax,%eax
	add	$4,%esi	 		# skip to kind
	lodsb 				# get kind
	lea	enemy_sprites(,%eax,8),%edx
					# kind muliplied by 8 and added on

enemies_xy:
	lodsw				# load xh
	lodsb				# 
	lodsb				# load yh (little endian) 

	mov	%edx,%esi		# proper sprite

	push	%ecx
	call	blit_219			# actually blit things
	pop	%ecx
		
loop_draw_enemies:
	add	$ENEMIES_SIZE,%ebp	# move to next enemy struct
	loop	draw_enemies		# and loop
	

 	##########################
	# Draw the Boss
	##########################
draw_the_boss:

	cmpb    $7,current_enemy_type	# are we at end-of-level boss?
	jne	done_with_boss		# if not skip all this nonsense
	
	
	cmpb	$1,boss_exploding	# if boss is exploding
	je	skip_draw_boss		# don't draw it
	
	mov	$boss_sprite,%esi
	movw	boss_x,%ax
        xor	%al,%al
	call	blit_219		# blit(boss_sprite,framebuffer,219,boss.x,0,13,3);
	
skip_draw_boss:

	########################
	# Draw Smoke

	cmpb    $0,boss_smoke		# is the boss smoking?
	je	skip_draw_smoke		# if not skip ahead


	xor	%eax,%eax
	movb	boss_smoke,%al
	shr	$2,%al
	lea	smoke_sprites(%eax,%eax,4),%esi
	push	%esi
	movw	boss_x,%ax
	add	$5,%ah
	mov	$3,%al
	push	%eax
	call	blit			# blit(smoke_sprites[boss.smoke/4],framebuffer,219,boss.x+5,3,3,1);
	
	pop	%eax
	subw	boss_xadd,%ax
	mov	$4,%al
	pop	%esi
	call	blit			# blit(smoke_sprites[boss.smoke/4],framebuffer,219,boss.x+5-boss.xadd,4,3,1);
	
	decb 	boss_smoke	        # boss.smoke--;


skip_draw_smoke:

	########################
	# Boss Laser Shoot

	cmpb    $0,boss_shooting	# is the boss shooting?
	je	skip_boss_shooting	# if not skip ahead
	
	decb	boss_shooting		# boss.shooting--

	xor	%eax,%eax
	movb	boss_shooting,%al	# move boss.shooting into %eax
	push	%eax			# saveon stack
	
	add	$200,%ax		# add 200
        movw    %ax,sound_freq+2	# save as sound_freq
	movw    $20,sound_freq	
	orb     $SOUND_PENDING,game_flags 	 # set sound to play

	pop	%eax		       # restore boss_shooting

	and	$1,%eax		       # get low bit of boss shooting
	lea	laser_sprites(,%eax,4),%esi  	 # point to proper laser sprite
	
	xor	%ecx,%ecx	       # clear ecx counter
	
boss_shoot_loop:	

	mov	boss_x,%ax	       # get boss_x into ax
	mov	%cl,%al
	shl	$1,%al
	add	$3,%al
	
	push	%eax
	push	%ecx	
	push	%esi
	call	blit_219  		# blit(laser_sprite[boss.shooting%2],framebuffer,219,boss.x,3+(i*2),1,2);
	pop	%esi
	pop	%ecx
	pop	%eax
	
	add	$12,%ah
	push	%ecx
	push	%esi
	call	blit   			# blit(laser_sprite[boss.shooting%2],framebuffer,219,boss.x+12,3+(i*2),1,2);
	pop	%esi
	pop	%ecx

	add   	$1,%ecx
        cmp     $8,%ecx
	jl	boss_shoot_loop

skip_boss_shooting:

	#######################
	# boss dead

	cmpb    $0,boss_exploding
	je	boss_is_not_exploding

	movb	$1,boss_waiting

	push	$30	       			 # 30 random smokes
	pop	%ecx
big_explosion:
	xor     %eax,%eax
	movb	boss_exploding,%al		 # get exploding amount
	shr	$3,%al				 # div 3
	lea   	smoke_sprites(%eax,%eax,4),%esi	 # and that much smoke

	push	$4
	pop	%ebx
	call	random
	mov	%al,%dl				 # y=rand()%4
	
	push	$11
	pop	%ebx
	call	random
	mov	%al,%dh				 # x=rand()%11
	
	movw	boss_x,%ax
	add	%dh,%ah	  			 # boss_x+x
	mov	%dl,%al
      
	push	%ecx
	push	%esi
	call	blit_219
	pop	%esi
	pop	%ecx

	loop    big_explosion
	
	movl	$(120<<16+20),sound_freq
	orb	$SOUND_PENDING,game_flags	# set_sound(120,20);

	decb	boss_exploding

	cmpb	$0,boss_exploding		# are we done exploding?
	jne	not_dead_yet

	cmpb	$1,level    			# only show ending
	jne	no_ending			# after level1
	
	call 	do_ending
no_ending:
	  
	addl	$1000,score			# 1000 points for killing boss
	
	cmpb	$7,level
	je	new_level			# can't go past level 7!
	
	incb	level				# level++
	
	
	jmp	new_level			# start new level

not_dead_yet:

	jmp	move_boss

boss_is_not_exploding:

        decw	boss_count
	cmpw	$0,boss_count
	jge	move_boss

	#############################################
	# toggle boss waiting state if count is up

	cmpb	$0,boss_waiting
	je	make_boss_wait	
stop_boss_waiting:
	movb	$0,boss_waiting		# boss.waiting=0;
	mov	$320,%ebx
	call	random
	add	$40,%ax
	mov	%ax,boss_count		# boss.count=40+rand()%320;
	jmp	move_boss
	
make_boss_wait:
	movb   	$1,boss_waiting
	push	$40
	pop	%ebx
	call	random
	add	$30,%ax
	mov	%ax,boss_count		# boss.count=30+rand()%40;
	movb	$30,boss_shooting	# boss.shooting=30;

move_boss:
	cmpb	$0,boss_waiting
	jne	laser_collision

	movw	boss_x,%ax
	addw	boss_xadd,%ax		# boss.x+=boss.xadd;
	mov	%ax,boss_x
	
	cmp	$26,%ah
	jg	boss_reverse
boss_under:
	cmp	$0,%ah
	jge	laser_collision

boss_reverse:
	negw 	boss_xadd
	addw	boss_xadd,%ax
	mov	%ax,boss_x
	
laser_collision:
	#######################
	# Collision detection for lasers

	cmpb	$0,boss_shooting
	je	done_with_boss

	movw	boss_x,%dx
	mov	%dh,%dl
	
	movw	x,%ax
	mov	%ah,%al
	add	$6,%al
	call	check_inside
	
	jc	laser_hit
	
	add	$12,%dh
	mov	%dh,%dl
	
	movw	x,%ax
	mov	%ah,%al
	add	$6,%al
	call	check_inside
	
	jnc	done_with_boss
	
laser_hit:
	testb	$3,boss_shooting
	jnz	done_with_boss
	decb	shields	      		# if (boss.shooting%7==0) shields--;

done_with_boss:



    	########################
	# Move the Missiles
	########################

	mov	$missile_0,%ebp		# point to first missile
	push	$NUM_MISSILES
	pop	%ecx	     		# loop through all missiles
	
move_missiles:
	mov	%ebp,%esi
	lodsb				# get missile[i].out	
	test	$1,%al			# is the missile out?
	jz	loop_move_missiles	# if not keep looping

	lodsb				# load x
	lodsb				# load y
	dec  	%al			# y--
	mov	%esi,%edi
	dec	%edi
	stosb	    			# store y back out
	
	cmp	$0,%al				# is y<0?
	jg	missile_collision_detection	# if not keep going

	andb	$(~PERFECT_SHOT),game_flags	# not a perfect shot
	mov	%ebp,%edi		  	# destroy missile
	xor	%al,%al
	stosb
	jmp  	loop_move_missiles

	#################################
	# Missile Collision Detection 
	
missile_collision_detection:	
	push	%ecx			# save missile count
	mov	MISSILE_X(%ebp),%dh	# move missile[i].x -> dh
	mov	MISSILE_Y(%ebp),%dl	# move missile[i].y -> dl
	push	%edx			# save missile info for later
	
	mov	$enemy_0,%ebx
	mov	$NUM_ENEMIES,%ecx
check_missile_enemy:
	cmpb	$0,ENEMIES_OUT(%ebx)
	je	loop_missile_enemy

	cmpb	$0,ENEMIES_EXPLODING(%ebx)
	jne	loop_missile_enemy
	
	pop	%edx		  	    # restore missile x,y
	push	%edx
	
	mov	%dh,%dl			    # missile x is 1 wide
	
	movw	ENEMIES_X(%ebx),%ax	    # get enemies x
	mov	%ah,%al
	add	$2,%al 			    # enemy x is two wide
	call	check_inside	
	jnc	loop_missile_enemy
	
	pop	%edx
	push	%edx
	mov	%dl,%dh			    # only check head of missile?
	
	movw	ENEMIES_Y(%ebx),%ax
	mov	%ah,%al
	add	$1,%al
	call	check_inside
	jnc	loop_missile_enemy
	
	
	# Collision!
	
	movb	$1,ENEMIES_EXPLODING(%ebx)
	addl	$10,score

        movl    $(150<<16+40),sound_freq
	orb     $SOUND_PENDING,game_flags 	 # set_sound(150,40);

	addb	$1,shield_up_count		 # add 1 to shield bonus count
	cmpb	$UP_SHIELDS,shield_up_count	 # have we hit limit?
	jl	no_shield_bonus			 # if not move on
	
	movb	$0,shield_up_count		 # reset shield_up_count to 0
	addb	$1,shields			 # add one to shields
	
	movl    $(220<<16+50),sound_freq
	orb     $SOUND_PENDING,game_flags      	 # set_sound(220,50);
	
	cmpb	$10,shields			 # are we greater than 10?
	jle	no_shield_bonus
	movb	$10,shields     		 # if so, set to 10
	
no_shield_bonus:	

	movb	$0,MISSILE_OUT(%ebp)		 # set missile out to 0
	
loop_missile_enemy:
        add	$ENEMIES_SIZE,%ebx
	dec	%ecx
	cmp	$0,%ecx
	jnz	check_missile_enemy


check_missile_boss:

	pop     %edx			# missile[i].x,missile[i].y
	pop	%ecx			# missile count
	
	# Missile <-> Boss Collision Detection 

	cmpb	$7,current_enemy_type  # see if boss is out
	jne	done_missile_collision
	
	cmpb	$1,boss_exploding      # be sure boss is not exploding
	je	done_missile_collision

	movw	boss_x,%ax
	mov	%ah,%al
	add	$13,%al
	
	push	%edx
	mov	%dh,%dl
	call	check_inside
	pop	%edx
	
	jnc	done_missile_collision

check_boss_y:

	xor  	%eax,%eax
	add	$2,%al
	
	mov	%dl,%dh
	call	check_inside
	
	jnc	done_missile_collision

	movl    $(150<<16+50),sound_freq
	orb     $SOUND_PENDING,game_flags      	 # set_sound(150,50);

	movb	$0,MISSILE_OUT(%ebp)
	movb	$11,boss_smoke
	
	decb	boss_hits     			 # decrement hits left
	cmpb	$0,boss_hits			 # are we done?
	jge	done_missile_collision		 # if not, go on
	
	movb	$23,boss_exploding		 # start boss exploding
	movb	$0,boss_shooting		 # have him stop shooting
	
	# destroy all extant missiles

	push	%ecx
	
	mov	$enemy_0,%ebx		# point to first missile
	mov	$NUM_ENEMIES,%ecx	# loop through all missiles
destroy_enemies:	
	movb	$2,ENEMIES_EXPLODING(%ebx)
	add	$ENEMIES_SIZE,%ebx
	loop	destroy_enemies	
	
	pop	%ecx
	
done_missile_collision:


loop_move_missiles:
	add	$3,%ebp
	dec	%ecx
	cmp	$0,%ecx
	jnz	move_missiles

done_move_missiles:



    	########################
	# Draw the Missiles
	########################

	mov	$missile_0,%ebp		# point to first missile
	push	$NUM_MISSILES
	pop	%ecx	     		# loop through all missiles
	
draw_missiles:	
	mov	%ebp,%esi
	lodsb				# get missile[i].out	
	test	$1,%al			# is the missile out?
	jz	loop_draw_missiles	# if so keep looping

	lodsb				# load x
	shl	$8,%ax
	lodsb				# load y
	
	mov	$missile_sprite,%esi	# missile sprite
	push	%ecx
	call	blit_219			# actually blit things
	pop	%ecx
		
loop_draw_missiles:
	add	$3,%ebp			# move to next missile struct
	loop	draw_missiles		# and loop


	#############################
	# Read the keyboard
	#############################
	
game_read_keyboard:      
 
        call	get_a_char
   	jc	move_ship

game_q:
   	cmp	$'q',%al		# are we 'q'?
	jne	game_j			# if not, move on
	
	call	verify_quit		# pop up "sure you want to quit"
	jnz	done_game		# then game over
	
	jmp	set_pause_flag		# if not act like paused then cont
	
game_j:   
	cmp	$'j',%al		# are we "j" [right] ?
	jne	game_k			# if not keep going
	
	mov	xadd,%bx		# load in xadd from RAM
	cmp	$0,%bx			# are we less than zero?
	jg	game_j_0		# if not go ahead
	add	$(-128),%bx		# add -128 to xadd
	jmp	game_j_done		
game_j_0:	
	xor	%ebx,%ebx		# switching direction, make xadd 0
game_j_done:
	mov 	%bx,xadd		# store back to RAM
	jmp 	game_read_keyboard	# done
	
		
game_k:
        cmp	$'k',%al		# are we 'k'? [left]
        jne	game_p			# if not move ahead

	mov	xadd,%bx		# load in xadd from RAM
	cmp	$0,%bx			# are we less than zero?
	jl	game_j_0		# then changing direction
					# set to 0 ( OPT reuse game_j)
	
	add	$(128),%bx		# add 128 to xadd
	jmp	game_j_done		# (OPT reuse game_j code)
       
game_p:
        cmp	$'p',%al		# did we press 'p'?
	jne	game_s			# if not, move on

	mov	$14,%bl			# put attention block
	call	put_attention_block

	mov	$game_paused_line,%esi	# print "GAME PAUSED!"

	push	$1
	pop	%ecx

	call	text_screen_keypressed_ret
				        # call	put_text_inline
        				# call	dump_to_screen
					# call	wait_until_keypressed
	
	
	jmp	set_pause_flag
	
game_s:
        cmp	$'s',%al	       	# did we press 's'?
	jne	game_h			# if not, move on
	xorb	$SOUND,game_flags	# toggle sound bit
	jmp	game_read_keyboard

game_h:
        cmp	$'h',%al		# Did we press 'h'?
	jne	game_space		# if not, move on
       	call 	display_help		# display help
set_pause_flag:	
	orb	$PAUSED,game_flags	# set the "paused" flag
	jmp	game_read_keyboard

game_space:
	cmp	$' ',%al		# Did we press ' '?
	jne	game_unknown		# If not, move on
	
	mov	$missile_0,%esi		# point to first missile
	push	$NUM_MISSILES
	pop	%ecx	     		# loop through all missiles
fire_missiles:	
	lodsb				# get missile[i].out	
	cmp	$0,%al			# is it empty?
	jne	end_fire_loop		# if not keep looping
	
	mov	%esi,%edi		# point to missile[i].out
	dec	%edi
	mov	$1,%al			# indicate that it's out
	stosb
	
	mov  	x,%ax
	shr	$8,%ax
	add	$3,%al
	stosb	      			# missile[i].x=shipx+3
	
	mov  	$16,%al
	stosb	       			# missile[i].y=shipy
	
	movl    $(110<<16+30),sound_freq
	orb     $SOUND_PENDING,game_flags # set_sound(110,30);
	
	jmp	game_read_keyboard	# finished shooting
	
end_fire_loop:
	add   	$2,%esi			# point to next missile
	loop	fire_missiles		# loop
	
game_unknown:
 	jmp	game_read_keyboard


	########################
	# move the ship
	########################

move_ship:
	mov	x,%ax			# restore x from memory
	mov	xadd,%bx		# restore xadd from memory

	add	%bx,%ax			# x+=xadd

check_x_under:	
	jns	check_x_over		# if not go ahead
	xor	%ax,%ax			# if under clear x and xadd
	xor	%bx,%bx
check_x_over:	
	cmp	$33,%ah			# are we over 33?
	jle	blit_ship		# if not draw ship	
	mov	$33,%ah			# x=33, xadd=0
	xor	%bx,%bx
blit_ship:	
	mov	%ax,x			# write updated values out to RAM
	mov	%bx,xadd

        mov     $ship_sprite,%esi     	# load the ship-centric pic
	mov	$16,%al		      	# x already in ah, 16=y
	call    blit_219
  
 	###########################
	# draw the info bar
 	###########################
	

	cmpb     $0,shields	   	# are shields <0?
	jl	 done_game		# if so, GAME OVER
	
	movb	 $12,shields_line	# make SHIELDS line red
	jg	 draw_shields_bar	# are we at zero shields?
	movb	 $14,shields_line	# if so make SHIELDS line yellow
		
draw_shields_bar:
        xor	 %ecx,%ecx		# clear count
	movb	 shields,%dl		# move shields into dl
	mov	 $21,%al		# y=21
	mov	 $10,%ah		# x starts at 10
	
shield_bar_loop:
        cmp	 %dl,%cl		# is count greater than shields?
	jge      shield_empty		# if so, put empty
	
	mov	 $block,%esi		# if not, put a block
	
	mov	 $4,%bl			# color=dark red
	cmp	 $4,%ecx		# if less than 4
	jl	 draw_shield
	
	mov	 $12,%bl    		# color=light red
	cmp	 $8,%ecx		# if 4<shields<8
	jl	 draw_shield
	
	mov	 $15,%bl   		# color=white if shields>8

	jmp	 draw_shield
	
shield_empty:	

        mov	 $underscore,%esi	# put a dark grey underscore
	mov	 $8,%bl
	
draw_shield:

	call	put_text_xy		# put the char
	add	$2,%ah			# move x over by 2

	inc	%ecx			# increment counter
	cmp	$10,%ecx		# are we less than 10?
	jne	shield_bar_loop		# if so keep looping
  
  
   	#  Should convert to ascii at score changetime
  	#  And not update score string here
  
  	mov	$shields_line,%esi      # print "SHIELDS:","SCORE:",
	push	$4			#       "HISCORE:","LEVEL:"
	pop	%ecx
	call	put_text_inline
	
	mov	score,%eax     		# Convert score to ascii
	mov	$score_string,%edi
	call    num_to_ascii
		
	mov	$score_string,%esi	# print score_string
	mov	$((8<<8)+22),%eax
	mov	$15,%bl
	call	put_text_xy
	
	xor	%eax,%eax
	movb	level,%al     		# Convert level to ascii
	mov	$level_string,%edi
	call    num_to_ascii
		
	mov	$level_string,%esi	# print level_string
	mov	$((8<<8)+23),%eax
	mov	$15,%bl
	call	put_text_xy
	
	mov	hiscore,%eax     	# Convert hiscore to ascii
	mov	$hiscore_string,%edi
	call    num_to_ascii
		
	mov	$hiscore_string,%esi	# print hiscore_string
	mov	$((32<<8)+22),%eax
	mov	$15,%bl
	call	put_text_xy

	testb	$SOUND_PENDING,game_flags   # is a sound pending? 
	jz	no_sound_effect		    # if not, move ahead	
	andb	$~SOUND_PENDING,game_flags  # clear sound_pending flag
	movl	sound_freq,%ebx
	call	play_sound	  	    # play the sound

	
no_sound_effect:
    
        #################################################
	# Finally draw it all to the screen
    	#################################################
	
        call	dump_to_screen
	
	##################################################
	#  Frame limit... keep it at ~30 frames/sec
	##################################################
     	# Assumes we don't lag more than a second 
     	# Seriously, if we lag more than 10ms we are screwed anyway 

frame_limit:
      	mov     $game_time,%ebx               # get current time
        call    gettimeofday
		
	mov	game_time+4,%ebx	      # move usecs to %ebx
  	mov	game_old_time,%eax	      # mov old usecs to %eax

	mov	%ebx,%ecx

  	sub	%eax,%ebx		      # time delta in %ebx
	
	cmp	$0,%ebx
	jge	time_no_adjust
  	add	$1000000,%ebx 		      # correct for underflow
  
time_no_adjust:

	cmp    $30000,%ebx		      # have we waited 30ms?
	jg     done_frame_limit		      # if so, move on

	call   hundred_usec_nanosleep  	      # else, sleep a bit

	jmp    frame_limit


done_frame_limit:
  	mov	%ecx,game_old_time	      # store as old time
  
      
	testb	$PAUSED,game_flags	      # were we paused?
	jz	game_not_paused	

	andb	$(~PAUSED),game_flags	      # clear PAUSED flag
      	mov     $game_time,%ebx               # get current time
        call    gettimeofday
		
	mov	game_time+4,%ebx	      # move usecs to %ebx
	
game_not_paused:

	########################
	# LOOP until FINISHED
	########################

        jmp	main_game_loop		# LOOP until finished

done_game:

	########################
	# GAME OVER
	########################

	mov	$14,%bl			# put attention block
	call	put_attention_block

	mov	$game_over_line,%esi    # print "GAME OVER!"
	call	put_text_line_inline

        call	dump_to_screen

	call	wait_long_time
	
	#############################
	# Play sad music

	cmpb   $0,shields
	jg     no_bum_bum

	testb	$SOUND,game_flags
	jz	no_bum_bum

	call	wait_long_time
	  
        mov     $(164<<16+500),%ebx
	call	play_sound	   	# set_sound(164,500);

	call	dump_to_screen

	call	wait_long_time
	      
        mov     $(130<<16+500),%ebx
	call	play_sound	   	# set_sound(130,500);
   
   	call	dump_to_screen
   
        jmp	show_hi_score   
	
no_bum_bum:
	push	$3
	pop	%ecx			# Pause 3 seconds or until keypress
     	call	pause_a_while
    
show_hi_score:

	call	clear_framebuffer	# clear screen

	mov	score,%eax	 	# get score
	mov	hiscore,%ebx		# get hiscore
	cmp	%ebx,%eax		# is score less than hiscore?
	jle	no_new_hiscore		# if so skip ahead
	
	mov	%eax,hiscore		# score is the new hiscore
	
        call	new_hi_score
	
no_new_hiscore:
	call    save_hi_score
      	call	do_hi_score		# show the hiscore
	call	wait_long_time
    
        push	$5
	pop	%ecx	     		# Pause 5 seconds or until keypress
     	call	pause_a_while

        ret



	######################
	# RANDOMIZE TIMER
	######################
	# We want a seed that is not divisible by 2 or 5
	#    to enhance the quality of subsequent pseudo-random
	#    numbers
	###############
	# destroys eax,ebx,ecx,edx
	
randomize_timer:
	mov	$random_seed,%ebx	# get current time
	call	gettimeofday        

	mov	random_seed,%eax        # use seconds since 1970 as seed

	push	$5
	pop	%ecx			# we will be dividing by 5 later
						
try_again:
	inc	%eax			# hackish way, but keeps logic simple
					# and we only have 50/50 of even anyway
					
	test	$0x1,%al		# check if even
	jz	try_again		# if so inc and try again
	
	xor	%edx,%edx		# clear out top 32 bits
	idivl	%ecx	 		# divide edx:eax by 5
	
	cmp	$0,%edx			# check if remainder is zero
	je	try_again		# if so inc by 1 and try again
	
	and	$0xffffff,%eax		# we're doing fixed point so
					# trim some high-order digits
					
	mov	%eax,random_seed	# store back to memory
	
	ret
	

	######################
	# RANDOM
	######################
	# pseudo-random number generator
	######################
	# bx = mod [aka high range]
	# ax returned with random number

random:

        # The following algorithm from the Hewlett Packard HP-20S 
	# Scientific Calculator manual, September 1998, p75-76    
	#
	# The algorithm  r     = FractionalPart (997r )           
	#                 i+1                        i            
	#                                                         
	# Where r0 is a starting value between 0 and 1.           
	# The generator passes the chi-square frequency test for  
	# uniformity, and serial and run tests for randomness.    
	# The more significant digits are more random than the    
	# less significant.  If r0 * 10e7 is not divisible        
	# by 2 or 5 you get 500,000 numbers w/o repeating.        
	#
	# modified to be fixed-point integer only by Vince Weaver 

	push	%edx
	push	%ecx
	push	%ebx
	
	mov	random_seed,%eax     	# get current seed
		
	mov	$997,%ecx		
	mull	%ecx			# multiply by 997
	
	and	$0xffffff,%eax		# keep to ~7 digits
	mov	%eax,random_seed	# store back the seed


	shr	$8,%eax			# high order most random
	and	$0xffff,%eax		# we limit random nums to 0-65535 range

	pop	%ebx			# get high limit
	cdq				# clear dx
	divw	%bx			# divide by limit
	mov	%dx,%ax			# move remainder to ax

	pop	%ecx
	pop	%edx

    	ret

	########################
	# wait_long_time
	########################
wait_long_time:	
	push	$5
	pop	%ecx
wait_loop:	
	push    %ecx
	call	inflection_point
	pop	%ecx
	loop	wait_loop
	ret


	########################
	# inflection_point
	########################
	# pause hard, and clear keypresses
	# used in a sudden stop of action
inflection_point:
	mov	$150,%ecx
sleep_250ms:	
	call	milisec_nanosleep
	loop	sleep_250ms
	
			   		# slide in and also
					# clear keyboard buffer
	
	##########################
	# clear_keyboard_buffer
	##########################
clear_keyboard_buffer:
	
	call	get_char
	jnc	clear_keyboard_buffer
	
	ret
	

	#################################
	# do_ending
	#################################
do_ending:
	call	wait_long_time		# pause a sop up keyboard in case
					# still firing keys at boss

	call	clear_framebuffer	# clear screen

	push	$16			# want to write 16 lines of text
	pop	%ecx
	
	mov	$ending_line,%esi
	call	put_text_inline
	
	mov	$earth_sprite,%esi	# Draw Picture of Earth
	mov	$(32<<8),%ax
	call	blit_219
	
		    			# draw picture of Susie
					# %esi follows that of earth
	mov	$(20<<8+15),%ax
	call	blit
	
	call	dump_to_screen
	
	call	wait_long_time
	
	call	wait_until_keypressed
	   
	call	clear_framebuffer

	mov	$tom_head_sprite,%esi	# put head picture
	xor	%eax,%eax		# at 0,0
	call	blit_219


	mov	$ending_line_2,%esi
	
	push	$2  			# put two lines of text
	
	jmp	pop_text_screen_keypressed_ret
	
					# insane code reuse
					# pop	%ecx
				      	# call	put_text_inline	   
 					# call    dump_to_screen
					# call	wait_until_keypressed
 					# ret


	#################################
	# do_about
	#################################
do_about:

	call	clear_framebuffer
   
        mov     $vince_sprite,%esi    # load the ego-centric pic
	mov     $(24<<8),%eax         # at 24,0
	call    blit_219
				   
	mov	$about_line_0,%esi
	push	$15
	
	jmp	pop_text_screen_keypressed_ret	
	
					# insane code re-use
				        #pop	%ecx
					#call	put_text_inline
					#call	dump_to_screen
					#call   wait_until_keypressed
					#ret



	#################################
	# do_story
	#################################
do_story:

	call	clear_framebuffer

        mov     $phobos_sprite,%esi   	# load the mars pic
	xor	%eax,%eax             	# at 0,0
	inc	%eax
	call    blit_219

	mov	$story_line_0,%esi    	# write the story text
	push	$3
	pop	%ecx
	call	put_text_inline

	push	%esi
	call	dump_to_screen
	pop	%esi

	push	$8
	pop	%ecx

	call	pause_a_while	        # pause for up to 8 seconds
	
    					# esi points at story_line_1
					# saved from before
	push    $3
	pop	%ecx
	call	put_text_inline

	call	dump_to_screen

	push	$4
	pop	%ecx
	call	pause_a_while		# pause for up to 4 seconds

	mov	$(19<<8+9),%ax
	push	%eax
	
phobos_loop:
        mov     $evil_ship_sprite,%esi  # load the evil ship
	call    blit_219		# and draw it to the screen
	
	call	dump_to_screen

	push	$30
	pop	%ecx
	
phobos_delay:	
	call	milisec_nanosleep	# delay 30ms
	loop	phobos_delay

	mov     $menu_blank+2,%esi	# erase the ship
	pop	%eax
	push	%eax
	call	put_text_xy
	
	pop	%eax	   		# increment x
	inc	%ah
	push	%eax

     	cmp	$37,%ah			# have we reached end of screen?
	jne	phobos_loop		# if not, loop

	pop	%eax			# restore stack

	push	$4
	pop	%ecx
	call	pause_a_while		# pause for up to 4 seconds
	  
	call	clear_framebuffer

        mov     $tom_sprite,%esi      	# load the tom pic
	mov	$(24<<8)+1,%eax	      	# at 24,1
	call    blit_219

	mov	$story_line_2,%esi      # put the tom related story
	push	$9
	pop	%ecx
	call	put_text_inline

	call	dump_to_screen
	
	push	$20
	pop	%ecx			# pause for up to 20 seconds

					# cheat and slide into
					# pause a while


        ############################
	# Pause a While
	############################
	# ecx = seconds
	#
pause_a_while:
	mov	$time_of_day,%ebx	# get starting time
	call	gettimeofday
	
	mov	(time_of_day),%eax	# store usecs in eax
	
pause_loop:
	push	%eax
	push	%ebx
	push	%ecx
	call	get_char
	pop	%ecx
	pop	%ebx
	pop	%eax
	jnc	pause_end		# if keypressed, end
	
	call	milisec_nanosleep	# sleep a bit

	mov	$time_of_day,%ebx	# get current time
	call	gettimeofday
	
	mov	(time_of_day),%ebx	# put usecs in ebx
	
	sub	%eax,%ebx		# ebx=new-old

	cmp	%ebx,%ecx
	jg	pause_loop
pause_end:

        ret


	######################
	# Help
	######################
	#

display_help:
	call	clear_framebuffer

	mov	$help_line_0,%esi      
	push	$18
	
pop_text_screen_keypressed_ret:	
	pop	%ecx
text_screen_keypressed_ret:	
	call	put_text_inline
	
	call	dump_to_screen
	
			      		# cheat and slide into
					# wait until keypressed
						
	#######################
	# Wait until keypressed
	#######################
	#
wait_until_keypressed:

	call	get_char
	jnc	wait_exit
	call	milisec_nanosleep
	jmp	wait_until_keypressed
	
wait_exit:	
	ret	


	##################
	# mov_edi_x_y
	##################
	# makes edi point to framebuffer
	# plus proper x,y offset
	#
	# ah=x al=y
	# %eax,%ecx,%edi destroyed
	
mov_edi_x_y:	
        mov     $framebuffer,%edi	    # point to frame buffer
	xor	%ecx,%ecx		    # clear cx
	mov	%al,%cl			    # want to offset by y
	
	cmp	$0,%cl			    # if at x,0 don't offset
	je	offset_x
	
offset_y:
        add	$(SCREEN_WIDTH*2),%edi	    # offset by y*2
	loop	offset_y
	
offset_x:	
	and	$0xffff,%eax		    # make sure top of eax clear
	shr	$7,%eax			    # add in x*2
	add	%eax,%edi
	
	ret

	##################
	# BLIT
	##################
	# esi=source
	# ah=x al=y	
	# (for already_know_only) bl=xsize,dh=ysize
	# dl=char to write	
	# destroyed: %eax,%ebx,%ecx,%edx,%edi

blit_219:
	mov	$219,%dl

blit:
	push	%eax			# save x,y
        lodsb				# get x-size from sprite
        mov     %al,%bl
        lodsb				# get y-size from sprite
        mov	%al,%dh
	pop	%eax   			# restore x,y
	
blit_already_know_xsize_ysize:	

	and     $0xff,%ebx		# clear high bits of ebx
	
     	call	mov_edi_x_y		# point edi to proper place in framebuf
	
blit_y_loop:
	mov     %bl,%cl			# restore x-size counter

blit_x_loop:

        lodsb				# get color info
	cmp     $0,%al			# is it zero?
	jz	no_color		# if so skip the write

	mov	%dl,%ah			# get character
	stosw				# store char/color
	jmp  	check_x

no_color:	
	inc	%edi			# increment pointer
	inc	%edi
check_x:	
        loop blit_x_loop		# do while x<0

	add    $(SCREEN_WIDTH*2),%edi	# move to next line
	sub    %ebx,%edi		# by adding y-size then
	sub    %ebx,%edi		# subtracting x-offset
	
	sub    $1,%dh			# decrement y counter
	jnz    blit_y_loop		# if not zero we loop

        ret						





	#####################
	# do_hi_score
	#####################

do_hi_score:
	call	load_hi_score		# load hi-score from disk
	
	mov	$12,%bl			# color light-red
	call	put_attention_block	# put an attention block
	
	mov	$high_score_line,%esi	# write "HIGH SCORE"
	call	put_text_line_inline
	
	mov	$hiscore_buffer,%edi	# point to temp buffer
	mov	$' ',%al		# space
	stosb
	
	mov  	$hi_player,%esi		# put the high-players name
	mov	$3,%ecx
	rep	
	movsb
	
	mov	$' ',%al		# pad with '  '
	stosb
	stosb
	
	mov  	hiscore,%eax		# Convert hiscore to ascii 
	call	num_to_ascii
	mov	$' ',%ax
	stosw
	
	mov	$hiscore_buffer,%esi	# point to buffer again
	mov	$15,%bl			# white
	mov	$9,%al			# y=9
	
	mov	$40,%ah			# Center... start with 40
	mov	%esi,%edx
strlen:		
	cmp	$0,0(%edx)		# repeat till end
	je	done_strlen
	inc	%edx
	dec	%ah 			# subtracting from the 40
	
	jmp	strlen
done_strlen:	
	shr	$1,%ah			# divide by 2
       
	call	put_text_xy		# put hiscore name/score

					# cheat and slide into 
					# dump_to_screen


	##################
	# dump_to_screen
	##################
	# eax,ebx,ecx,edx,esi,edi trashed
	# ebp= ^[
	# eax = scratch
	# bl = block_mode
	# dl = old_color
	# dh = ysize
	# ecx = xsize
	
dump_to_screen:	
	push	%ebp			# we save ebp
	
	mov     $framebuffer,%esi       # setup pointers
	mov	$out_buffer,%edi

	mov	$('['<<8+27),%bp	# store ^[[ as it will be useful later
	
	
				        # write ^[[0;0H to home the cursor
	mov	 %ebp,%eax
	stosw
	mov	 $( ('H'<<24)+('0'<<16)+ (';'<<8)+('0')),%eax
	stosl

	xor	 %bl,%bl	        # set block_mode
	mov	 $17,%dl	        # set old_color

	mov	 $(SCREEN_HEIGHT),%dh   # load in y size

dump_y_loop:
       push     $SCREEN_WIDTH
       pop	%ecx	     	        # restore x size
dump_x_loop:

	lodsw			        # load ascii/color
	
	cmp     %dl,%al		        # compare to current color
	je	blit_block	        # if the same, skip ahead

	mov	%al,%dl		        # store color as old_color

	push	%eax		        # save ascii/color on stack

	mov	%ebp,%eax
	stosw			        # print ^[[
	
	mov  	%dl,%al		        # restore color

	shr  	$3,%al		        # get intensity bit
	add	$0x30,%al	        # convert to ascii
	stosb			        # store
	
	mov	%dl,%ah		        # restore color
	and	$7,%ah		        # mask off intensity bit
	
				        # swap bits 3 and 1
				        # we use rgb, ansi does bgr.  annoying
				        # There's probably a better way.
				        # Maybe a lookup table?
	xor	%al,%al
	rcr	$1,%ah
	rcl	$1,%al
	
	rcr	$1,%ah
	rcl	$1,%al
	
	rcr	$1,%ah
	rcl	$1,%al
	
	add	$0x30,%al	        # convert to ascii
		
	mov  	$'m',%ah	        # tack on trailing m
	
	shl	$16,%eax
	mov	$(('3'<<8)+';'),%ax
      
	stosl			        # finish writing out color

	pop	%eax		        # restore ascii/color

blit_block:		
	cmp     $219,%ah	        # are we the block char?
	jne 	regular_char	        # if not skip ahead

	cmp	$1,%bl		        # are we in block mode?
	je	change_block_zero       # if yes, skip ahead
	
	mov	$14,%al		        # switch to vt102 line-draw mode
	stosb			        # with ^N
	mov	$1,%bl		        # set block mode flag

change_block_zero:
	mov	$'0',%ah	        # 0 = block in vt102 line-draw
	jmp	blit_char
	
regular_char:
        cmp    $1,%bl		        # are we in block mode?
	jne    blit_char	        # if we aren't, skip ahead
	
	push   %eax		        # switch to normal mode
	mov    $15,%al		        # with ^O
	stosb
	xor     %bl,%bl		        # clear block mode flag	
	pop     %eax

blit_char:
	shr     $8,%ax		        # write out the ascii char
	stosb

blit_char_done:

	loop    dump_x_loop	        # loop until x done

	mov	$10,%al		        # write a line-feed
	stosb

	sub	$1,%dh		        # loop until y done
	jnz	dump_y_loop

	mov	$0,%al	   	        # store terminating 0 char
	stosb
	
	mov  	$out_buffer,%ecx        # write to stdout
	
	pop	%ebp
	
		    			# hijack our way into
					# write_stdout
					
	

        #################################
	# WRITE_STDOUT
	#################################
	# ecx has string
        # eax,ebx,ecx,edx trashed
write_stdout:

	cdq                             # clear edx (depends on high bit
					#    of eax being 0)
		
str_loop1:
        inc     %edx
	cmpb    $0,(%ecx,%edx)          # repeat till zero
	jne     str_loop1

write_stdout_dx_set:

	push	$SYSCALL_WRITE          # put 4 in eax (write syscall)
        pop     %eax                    # in 3 bytes of code
	
	xor     %ebx,%ebx               # put 1 in ebx (stdout)
	inc     %ebx                    # in 3 bytes of code
	
	int     $0x80                   # run the syscall

	ret




        #########################
	# Clear Framebuffer
	#########################
	# eax,ecx,edi trashed

clear_framebuffer:
	xor	%eax,%eax
	mov	$' ',%ah 			      # clear with black spaces
	mov	$framebuffer,%edi
	mov	$(SCREEN_WIDTH*SCREEN_HEIGHT),%ecx
	rep	
	stosw
	ret

	############################
	# gettimeofday()
	############################
	# ebx points to buffer
gettimeofday:
	push 	%eax
	push	%ecx
	push	$SYSCALL_GETTIMEOFDAY
	pop	%eax
	xor	%ecx,%ecx		# NULL timezone
	int     $0x80                   # run the syscall
	pop	%ecx
	pop	%eax
	ret

	###########################
	# nanosleep()
	###########################
	# time to sleep in buffer pointed by ebx
	
milisec_nanosleep:	
	push	%ebx
	mov	$milisecond,%ebx        
	jmp	mid_nanosecond
	
hundred_usec_nanosleep:
	push    %ebx
	mov	$hundred_microsecond,%ebx
mid_nanosecond:	
	call	nanosleep
	pop	%ebx
	ret
	
nanosleep:
	push	%eax
	push 	%ecx
	push	$SYSCALL_NANOSLEEP
	pop	%eax	
					# ebx=req or &time_we_want_to_sleep
	xor	%ecx,%ecx		# ecx=rem or &remainder (if interrupted)
	int     $0x80                   # run the syscall
	pop	%ecx
	pop	%eax
	ret

	############################
	# tcgetattr
	############################
	# pointer to result in edx
	# eax,ebx,ecx trashed
tcgetattr:
	mov	$TCGETS,%ecx
	jmp	do_ioctl


	############################
	# tcsetattr
	############################
	# pointer to parameters in edx
	# eax,ebx,ecx trashed
tcsetattr:	
	mov	$TCSETS,%ecx		# TCSETS (or SNDCTL_TMR_TIMEBASE :( )
do_ioctl:	
	push	$SYSCALL_IOCTL
        pop	%eax	      		# someone set up us the ioctl
       	xor	%ebx,%ebx	     	# STDIN
	int	$0x80			# syscall!
	ret



	#############################
	# put_text_inline
	#############################
	# esi = data
	# ecx = number of strings
	#
	# first color, then y, then x, then string

put_text_line_inline:
	push	$1   			# convenience function
	pop	%ecx			# for those only wanting 1 line of text
put_text_inline:
	lodsb				# get color
	mov  	%al,%bl
	lodsw	       			# then get x,y
	call 	put_text_xy
	loop	put_text_inline
	ret


	#############################
	# put_text_xy
	#############################
	# esi = string
	# ah=x al=y
	# bl=color

	# ebx trashed  esi points to end of string
	
put_text_xy:
	push	%eax
	push 	%ecx			# edi_x_y destroys this
	call	mov_edi_x_y		# point to x,y in framebuffer
	pop	%ecx
p_loop:
	lodsb		 		# load char
	cmp     $0,%al			# is it zero?
	je	p_done			# if so we are done

        push    %eax
	mov     %bl,%al			# output color
	stosb
	pop  	%eax
	stosb	    			# output character
	jmp p_loop
p_done:	
	pop	%eax
        ret

	#######################
	# get_char
	#######################
	#
	# returns char in al.  carry set if invalid
	# eax,ebx,ecx,edx all trashed
	
get_char:

	push	$SYSCALL_READ
	pop	%eax
	xor	%ebx,%ebx	       # stdin
	mov	$in_char,%ecx	       # in_char
	push	$1
	pop	%edx		       # try to read 1 byte
	int	$0x80

	cmp	$1,%eax
	jl	no_char
	
	movb	(in_char),%al	
	clc	
	ret
no_char:
	stc
	ret


	#######################
	# get_a_char
	#######################
	# cooks up get_char for arrows and F keys
	# carry set it invalid,  or cooked char in al

get_a_char:
	call	get_char	       # grab a character
	jc	get_a_char_unknown

	cmp	$27,%al		       # see if escape
	jne	get_a_char_over	       # if not we are done
	
	call	get_char	       # get another character
	jnc	extended_char
	
	mov	$'q',%al     	       # if was just esc return q
	jmp	get_a_char_over

extended_char:
        cmp	$'[',%al       	       # check if was ^[[
	jne	get_a_char_unknown
	
	call	get_char	       # get yet another character
	jc	get_a_char_unknown	
check_up:
	cmp	$'A',%al
	jne	check_down
	mov	$'i',%al
	jmp	get_a_char_over
	
check_down:
	cmp	$'B',%al
	jne	check_right
	mov	$'m',%al
	jmp	get_a_char_over
	
check_right:
	cmp 	$'C',%al
	jne	check_left
	mov	$'k',%al
	jmp	get_a_char_over
	
check_left:
	cmp	$'D',%al
	jne	check_f1
	mov	$'j',%al
	jmp	get_a_char_over
	
check_f1:	
	cmp	$'[',%al
	jne	get_a_char_unknown
	call	get_char	 	# get yet _another_ char
	jc	get_a_char_unknown
	cmp	$'A',%al
	jne	get_a_char_unknown
	mov	$'h',%al
	jmp	get_a_char_over
get_a_char_over:	
	clc
	ret
	
get_a_char_unknown:
        stc
        ret




 	#######################
	# put_attention_block
	#######################
	# color is in bl
	
put_attention_block:	
	push	$5
	pop	%eax			# x=0 y=5
	call	mov_edi_x_y
    
	mov	$'*',%ah
	mov	%bl,%al

	mov	$(40*7),%ecx

	rep 
	stosw
    
	ret
	

	####################
	# num_to_ascii
	####################
	# eax = value to print
	# edi points to where we want it
	# ebx,ecx,edx trashed
	

num_to_ascii:
	push    $10
	pop     %ebx
	xor     %ecx,%ecx		# clear ecx
 div_by_10:
        cdq				# clear edx
	div     %ebx			# divide
	push    %edx            	# save for later
	inc     %ecx            	# add to length counter
	or      %eax,%eax       	# was Q zero?
	jnz     div_by_10       	# if not divide again

write_out:
        pop     %eax            	# restore in reverse order
	add     $0x30, %al      	# convert to ASCII
	stosb                  		# save digit
	loop    write_out       	# loop till done
	ret

	##########################
	# check_inside
	##########################
	# see if line dh -> dl ovelaps bigger line ah -> al
	# used as half of 2d rectangular collision detection
	# carry flag set if true, unset if not
check_inside:

	cmp  	%ah,%dl			
	jl	ar_not_in
	cmp	%al,%dl
	jle	inside
ar_not_in:
	cmp	%ah,%dh
	jl	outside
	cmp	%al,%dh
	jg	outside
inside:
        stc
	ret
outside:
	clc
	ret


	########################
	# play_sound
	########################
	# ebx=freq(hertz) / duration (ms)
play_sound:	

	##########################
	# To set frequency
	# ESC[10;FFF]   where FFF=frequency in hertz
	######################
	# To set duration
	# ESC[11;DDD] where DDD = Duration

	mov	$sound_buffer,%edi
	push	$'0'
	pop	%ecx

freq_loop:
	mov	%cl,%al
	shl	$24,%eax
	add	$('1'<<16 + '['<<8 + 27),%eax
	stosl
	
	mov  	$';',%al
	stosb

	xor  	%eax,%eax
	ror  	$16,%ebx	
	mov	%bx,%ax

	push	%ebx
	push	%ecx
	call	num_to_ascii
	pop	%ecx
	pop	%ebx

	mov	$']',%al
	stosb

	inc	%cl
	cmp	$'2',%cl
	jne	freq_loop
		
	testb   $SOUND,game_flags	# Check if sound on
	jz	no_bell
	
	
	##################
	# and ^G is BEL as always
	mov     $7,%al
	stosb
no_bell:
	xor	%al,%al
	stosb	       			# NUL string termination
		
	mov  	$sound_buffer,%ecx
	
	jmp 	write_stdout	  	# cheat and jmp instead of call
					# write_stdout will ret for us
	
	


	#########################
	# Load Hi Score
	#########################
	#
load_hi_score:	

	call	check_existing_hi_score

	jnc	done_load_hiscore	# if invalid/no file, jump ahead

	btl	$29,%eax		# is 4th char "S" or "s"?
	jc	hiscore_clear_sound	# if "s" we turn off sound
	
hiscore_set_sound:	
	orb	$SOUND,game_flags	# and turn on sound
	jmp	hiscore_get_initials
	
hiscore_clear_sound:	
       	andb	$(~SOUND),game_flags	# and turn off sound
hiscore_get_initials:	

	movl	hiscore_buffer+4,%eax	# get 4 bytes
	movl	%eax,hi_player		# first 3 are initials
	
	movl	hiscore_buffer+8,%eax	# get next 4 bytes
	movl	%eax,hiscore		# which is 32 bit int

done_load_hiscore:	
	ret


	#####################
	# Check Existing Hi Score
	#####################
	# cf=1 if valid previously existing file
	# cf=0 file problem
	# dl=0 file not found
	# d1=1 file_invalid

check_existing_hi_score:

	push	$SYSCALL_OPEN
	pop	%eax
	mov	$score_file,%ebx	# "/tmp/tb_asm.hsc"
	xor	%ecx,%ecx		# 0 = O_RDONLY <bits/fcntl.h>
	cdq				# clear edx
	int	$0x80
	
	cmp	$0,%eax			# see if fd opened 
	jl	file_not_found		# if not, exit
	
	mov	%eax,%ebx     		# move fd to %ebx
	
	push	$SYSCALL_READ		# read
	pop	%eax
	mov	$hiscore_buffer,%ecx	# into hiscore_buffer
	mov	$12,%dx			# 12 bytes
	int	$0x80
	
	push	%eax
		
	call    close_file		# close the file

	pop	%eax
	
	cmp	$12,%eax		# did we read 12 bytes?
	jne	invalid_file		# if not, invalid

	movl	hiscore_buffer,%eax	# get first 4 bytes into eax
	cmp	$('b'<<8 + 't'),%ax	# are first 2 chars "tb"?
	jne     invalid_file		# if not, close
	
	stc				# valid file
	ret

file_not_found:
	xor     %dl,%dl			# file_not_found
	jmp	file_done
invalid_file:	
	mov	$1,%dl			# invalid file	
file_done:
	clc				# file problem
	ret			     


	###############################
	# New High Score
	###############################
	#
	
new_hi_score:

	mov     $9,%bl
	call	put_attention_block	# make a bright blue attention block

	mov	$new_high_line,%esi	# output the high score directions
	push	$3
	pop	%ecx
	call	put_text_inline
	
	xor	%edx,%edx      		# we use dx as a counter
	movl	$('A'<<16+'A'<<8+'A'),hi_player
					# clear the hi-score initials
	
 hiscore_loop:
 	mov    $menu_blank,%esi		# clear the initials line
 	mov    $15,%bl
        mov    $17,%ah
 	mov    $10,%al
	push   %esi
	call   put_text_xy

 	pop    %esi	  		# clear the pointer line
 	inc    %al			# y=11
	call   put_text_xy
	   
	mov    $hi_player,%esi		# put the initials
	inc    %ah			# x=18
	dec    %al			# y=10
	call   put_text_xy
  
        mov	$menu_pointer,%esi	# "^"
	mov	%dl,%ah			# dl is x
	add	$18,%ah
	inc	%al			# y=11
  	call	put_text_xy
   
   	push	%edx
        call    dump_to_screen		# print to screen
	pop	%edx
	
hiscore_get_char:	
	call	hundred_usec_nanosleep	# sleep so don't use all CPU
	push	%edx
	call	get_a_char		# call get_a_char
	pop	%edx

	jc	hiscore_get_char	# then loop

hiscore_k:
	cmp	$'k',%al		# were we a right?
	jne	hiscore_j		# if not move on
	
	inc	%dl			# x++
	cmp	$2,%dl			# is x > 2?
	jle	hiscore_loop		# if not keep going
	mov	$0,%dl			# otherwise wrap so x=0
	
hiscore_j:	
   	cmp	$'j',%al		# are we left?
	jne	hiscore_i		# if not move on
   
   	sub	$1,%dl			# x--
					# is x < 0?
	jns	hiscore_loop		# if not keep going
	mov	$2,%dl			# otherwise wrap so x=2
   
hiscore_i:    
        cmp     $'i',%al		# are we up?
	jne	hiscore_m		# if not move on
	
	movb	hi_player(%edx),%bl	# get initial
	dec	%bl			# decrement
	cmp	$64,%bl			# are we less than '@'?
	jge	write_initial
	mov	$126,%bl		# if so wrap to '~'
write_initial:
	movb    %bl,hi_player(%edx)	# write back inital
	jmp	hiscore_loop
   
hiscore_m:   
        cmp     $'m',%al		# are we down?
	jne	hiscore_enter		# if not, move on
   
    	movb	hi_player(%edx),%bl	# get initial
	inc	%bl			# incrememnt it
	cmp	$126,%bl		# are we > "~"?
	jle	write_initial
	mov	$64,%bl	     		# if so wrap to "@"
	jmp	write_initial
	   
hiscore_enter:       
        cmp     $'\n',%al		# once we hit enter we are done
        jne	hiscore_loop

	ret

	######################
	# Save Hi Score 
	######################
	# also saves sound on/off
	
save_hi_score:
        call  	check_existing_hi_score
	jc	ok_to_write
	
	cmp	$1,%dl		       	# we won't write if invalid file
	je	done_save_hi		# we will if ours, or not there

ok_to_write:

	push	$SYSCALL_OPEN
	pop	%eax
	mov	$score_file,%ebx	# "/tmp/tb_asm.hsc"
	mov	$(O_WRONLY|O_CREAT|O_TRUNC),%ecx
	mov	$(SIXSIXSIX),%edx
	int	$0x80
	
	cmp	$0,%eax			# see if fd opened 
	jl	done_save_hi		# if not, exit

	mov	%eax,%ebx		# save fd
	
	movl	$('s'<<24+'a'<<16+'b'<<8+'t'),%eax
	
	testb	$SOUND,game_flags
	jz	no_save_sound
	btrl	$29,%eax
no_save_sound:

	mov	%eax,hiscore_buffer
	movl	hi_player,%eax
	mov	%eax,hiscore_buffer+4
	movl	hiscore,%eax
	mov	%eax,hiscore_buffer+8
	
	push	$SYSCALL_WRITE          # put 4 in eax (write syscall)
        pop     %eax                    # in 3 bytes of code

	mov	$hiscore_buffer,%ecx
	mov	$12,%edx
	
	int     $0x80                   # run the syscall
	
					# cheat and run into close_file
	
	
	#####################
	# Close File
	#####################
	# fd=ebx
	
close_file:
	push	$SYSCALL_CLOSE		# close the file
	pop	%eax
	int	$0x80
done_save_hi:	
	ret



################################################
# DATA SEGMENT
################################################

# .data

.include "data.labels"
data_compressed:
.include "data.lzss"


##################################################
# BSS SEGMENT
##################################################

#.bss
.lcomm  text_buf, (N+F-1)
.lcomm  DATA_OFFSET, TB_DATA_SIZE

    		# +1 for any wrap-around blits
.lcomm	framebuffer,	SCREEN_WIDTH*(SCREEN_HEIGHT+1)*BYTES_PER_PIXEL
.lcomm  out_buffer,	SCREEN_WIDTH*(SCREEN_HEIGHT+5)*10

.lcomm	sound_buffer,	12
.lcomm	sound_freq,	 4

.lcomm	hiscore_buffer,	16

.lcomm	in_char, 	1

.lcomm	random_seed,	8

.lcomm  old_time,	8
.lcomm	time_of_day,	8

.lcomm game_old_time,	8
.lcomm game_time,	8


.lcomm game_vars,1

.lcomm	x,		2
.lcomm	xadd,		2
.lcomm	scroll,		2
.lcomm	scroll_frames,	2

.lcomm	missile_0,	3*NUM_MISSILES	# out,x,y
.lcomm	enemy_0,	ENEMIES_SIZE*NUM_ENEMIES  
					# out,exploding,kind,xw,yw
				        # xadd,yadd,xmin,xmax
				       

.lcomm	score,		4
.lcomm  score_string,  10
.lcomm  hiscore_string,10
.lcomm  level,		1
.lcomm	level_string,  10

.lcomm shields,	        1
.lcomm shield_up_count, 1


.lcomm total_enemies_out, 1

# next bunch in order to smallen init code
.lcomm enemies_spawned,  	2
.lcomm current_init_x,	  	2
.lcomm current_enemy_kind,	1
.lcomm enemy_wait,		1
.lcomm current_enemy_type,	1
.lcomm enemy_wave,		1


.lcomm between_enemy_delay,1
.lcomm waves_till_boss,1


# FIXME move some of these to be flags?
.lcomm boss_x,2
.lcomm boss_xadd,2
.lcomm boss_count,2
.lcomm boss_smoke,1
.lcomm boss_exploding,1
.lcomm boss_waiting,1
.lcomm boss_hits,1
.lcomm boss_shooting,1

.lcomm	  background,1600

.lcomm	  end_game_vars,1


# From ~/linux/include/asm/termbits.h  
# struct termios {
#    unsigned int c_iflag;               /* input mode flags */
#    unsigned int c_oflag;               /* output mode flags */
#    unsigned int c_cflag;               /* control mode flags */
#    unsigned int c_lflag;               /* local mode flags */
#    unsigned char c_line;               /* line discipline */
#    unsigned char c_cc[19];             /* control characters */
# };

.lcomm  old_tty	       40
.lcomm  new_tty	       40

