├── .gitignore ├── Makefile ├── README ├── e.bat ├── invaders.asm ├── invaders.com ├── invaders.img └── invaders.png /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | backup/ 3 | 4 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Makefile contributed by jtsiomb 2 | 3 | src = invaders.asm 4 | 5 | .PHONY: all 6 | all: invaders.img invaders.com 7 | 8 | invaders.img: $(src) 9 | nasm -f bin -o $@ $(src) 10 | 11 | invaders.com: $(src) 12 | nasm -f bin -o $@ -Dcom_file=1 $(src) 13 | 14 | .PHONY: clean 15 | clean: 16 | $(RM) invaders.img invaders.com 17 | 18 | .PHONY: rundosbox 19 | rundosbox: invaders.com 20 | dosbox $< 21 | 22 | .PHONY: runqemu 23 | runqemu: invaders.img 24 | qemu-system-i386 -fda invaders.img 25 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | _ _ _ _ _ __ ___ ____ ___ ___ 2 | | |\ | \ / /__\ | \ |__ |__] [__ 3 | | | \| \/ | | |__/ |___ | \ ___] 4 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 5 | 6 | Invaders game in 512 bytes (boot sector or COM file) 7 | by Oscar Toledo G. Jun/04/2019 8 | 9 | http://nanochess.org 10 | https://github.com/nanochess 11 | 12 | This a game recreating the Invaders game in 512 bytes, that can be 13 | run as a COM file or put into a boot sector of a floppy disk to be 14 | run. 15 | 16 | Move with Ctrl key to left, Alt key to right, any Shift key to 17 | shoot. (maybe you'll need to disable StickyKeys in Windows) 18 | 19 | The previous commit worked with left arrow, right arrow and space 20 | key, but you had to press the space key to stop the spaceship. 21 | Also didn't have the barriers. 22 | 23 | Now it's compatible with 8088 (the original IBM PC) but still it 24 | has the option for 80286 code in the source code. So you now have 25 | to look for a 8-bit compatible VGA card if you want to run it over 26 | original hardware ;) 27 | 28 | A small video of the game running over emulation: 29 | 30 | https://www.youtube.com/watch?v=pC976eejfms 31 | 32 | If you want to assemble it, you must download the Netwide Assembler 33 | (nasm) from www.nasm.us 34 | 35 | Use this command line: 36 | 37 | nasm -f bin invaders.asm -Dcom_file=1 -o invaders.com 38 | nasm -f bin invaders.asm -Dcom_file=0 -o invaders.img 39 | 40 | Tested with VirtualBox for Mac OS X running Windows XP running this 41 | game, it also works with DosBox and probably with qemu: 42 | 43 | qemu-system-x86_64 -fda invaders.img 44 | 45 | Enjoy it! 46 | 47 | 48 | >> THE BOOK << 49 | 50 | Do you would like more details on the inner workings? This program 51 | is fully commented in my new book Programming Boot Sector Games 52 | and you'll also find a 8086/8088 crash course! 53 | 54 | Now available from Lulu: 55 | 56 | Soft-cover 57 | http://www.lulu.com/shop/oscar-toledo-gutierrez/programming-boot-sector-games/paperback/product-24188564.html 58 | 59 | Hard-cover 60 | http://www.lulu.com/shop/oscar-toledo-gutierrez/programming-boot-sector-games/hardcover/product-24188530.html 61 | 62 | eBook 63 | https://nanochess.org/store.html 64 | 65 | These are some of the example programs documented profusely 66 | in the book: 67 | 68 | * Guess the number. 69 | * Tic-Tac-Toe game. 70 | * Text graphics. 71 | * Mandelbrot set. 72 | * F-Bird game. 73 | * Invaders game. 74 | * Pillman game. 75 | * Toledo Atomchess. 76 | * bootBASIC language. 77 | -------------------------------------------------------------------------------- /e.bat: -------------------------------------------------------------------------------- 1 | nasm -f bin invaders.asm -Dcom_file=1 -o invaders.com 2 | nasm -f bin invaders.asm -o invaders.img 3 | rem invaders 4 | 5 | -------------------------------------------------------------------------------- /invaders.asm: -------------------------------------------------------------------------------- 1 | ; 2 | ; Invaders in 512 bytes 3 | ; 4 | ; by Oscar Toledo G. 5 | ; 6 | ; (c) Copyright 2015-2019 Oscar Toledo G. 7 | ; 8 | ; Creation: Oct/27/2015. 9 | ; Revision: Nov/06/2015. Adjusted bullet collision. Invaders 10 | ; accelerate. 11 | ; Revision: Apr/03/2019. Invaders now can shoot. Spaceship does 12 | ; explosion. 13 | ; Revision: May/28/2019. Invaders goes down at 11px instead 12px. 14 | ; Now starts another invaders wave more 15 | ; difficult. 16 | ; Revision: Jun/01/2019. Redesigned for 320x200x256 mode. 17 | ; Revision: Jun/02/2019. Now in color. Color carries information 18 | ; about thing being hit. 19 | ; Revision: Jun/03/2019. Optimized, 601 bytes as COM!!! 20 | ; Revision: Jun/04/2019. At last 512 bytes! 21 | ; Revision: Jun/05/2019. By popular demand added pure8088 option. Now 22 | ; the 8088 version also is bootable! so now 23 | ; 8088 is the default. 24 | ; Revision: Jun/06/2019. jtsiomb made the point that the COM file 25 | ; doesn't need to be 512 bytes, so Esc for 26 | ; exiting and also returns to text mode. 27 | ; Revision: Jun/29/2019. Now spaceship moves to left pressing Ctrl, 28 | ; to right pressing Alt, and shoots pressing 29 | ; Shift. Spaceship stops when you depress the 30 | ; direction key. To exit you press Scroll 31 | ; Lock. Used the extra bytes to implement 32 | ; barriers that stop the invaders' bullets. 33 | ; (suggested in Reddit by nils-m-holm). 34 | ; 35 | 36 | ; 37 | ; Using PUSHA and POPA the code can be smaller, not enabled by 38 | ; default. 39 | ; 40 | 41 | %ifndef pure8088 ; Define as 0 to create a 80186/80286 binary 42 | pure8088: equ 1 ; Enabled by default for pure 8088 assembler 43 | 44 | cpu 8086 45 | %endif 46 | 47 | %ifndef com_file ; If not defined create a boot sector 48 | com_file: equ 0 49 | %endif 50 | 51 | base: equ 0xfc80 ; Memory base (same segment as video) 52 | 53 | shots: equ base+0x00 ; Space to contain 4 shots (2 bytes each one) 54 | ; Plus space for a ignored shot (full table) 55 | ; Notice (sprites + SPRITE_SIZE) - (shots + 2) 56 | ; must be divisible by SPRITE_SIZE. 57 | old_time: equ base+0x0c ; Old time 58 | level: equ base+0x10 ; Current level number 59 | lives: equ base+0x11 ; Current lives 60 | sprites: equ base+0x12 ; Space to contain sprite table 61 | 62 | SHIP_ROW: equ 0x5c*OFFSET_X ; Row of spaceship 63 | X_WIDTH: equ 0x0140 ; X-width of video 64 | OFFSET_X: equ X_WIDTH*2 ; X-offset between screen rows (2 pixels) 65 | SPRITE_SIZE: equ 4 ; Size of each sprite in bytes 66 | 67 | ; 68 | ; All colors different (important to distinguish things) 69 | ; 70 | SPACESHIP_COLOR: equ 0x1c ; Must be below 0x20 71 | BARRIER_COLOR: equ 0x0b 72 | SHIP_EXPLOSION_COLOR: equ 0x0a 73 | INVADER_EXPLOSION_COLOR: equ 0x0e 74 | BULLET_COLOR: equ 0x0c 75 | START_COLOR: equ ((sprites+SPRITE_SIZE-(shots+2))/SPRITE_SIZE+0x20) 76 | 77 | %if com_file 78 | org 0x0100 ; Start position for COM files 79 | %else 80 | org 0x7c00 ; Start position for boot sector 81 | %endif 82 | mov ax,0x0013 ; Set mode 0x13 (320x200x256 VGA) 83 | int 0x10 ; Call BIOS 84 | cld 85 | mov ax,0xa000 ; Point to screen memory 86 | mov ds,ax ; Both DS... 87 | mov es,ax ; ...and ES 88 | mov ah,0x04 89 | mov [level],ax ; Level = 0, Lives = 4 90 | restart_game: 91 | xor ax,ax 92 | mov cx,level/2 ; Clear screen and variables (except level/lives) 93 | xor di,di 94 | rep 95 | stosw ; ch is zero from here 96 | 97 | ; 98 | ; Setup descend state 99 | ; 100 | mov ax,[di] ; al now contains level, ah contains lives 101 | inc ax ; Increase by 2 (so invaders descend correctly) 102 | inc ax 103 | stosw ; Advance level 104 | mov ah,al 105 | xchg ax,dx ; Shouldn't damage DX starting here 106 | 107 | ; 108 | ; Setup the spaceship 109 | ; 110 | mov ax,SPACESHIP_COLOR*0x0100+0x00 111 | stosw 112 | mov ax,SHIP_ROW+0x4c*2 113 | stosw 114 | ; 115 | ; Setup the invaders 116 | ; 117 | mov ax,0x08*OFFSET_X+0x28 118 | mov bx,START_COLOR*0x0100+0x10 119 | in1: mov cl,0x0b ; Eleven invaders per row 120 | in5: stosw ; Set invader position 121 | add ax,0x0b*2 ; Go to next column 122 | xchg ax,bx 123 | stosw ; Set invader color and shape 124 | inc ah ; Go to next color 125 | xchg ax,bx 126 | loop in5 ; Loop and also make sure ch is zero 127 | add ax,0x09*OFFSET_X-0x000b*0x000b*2 ; Go to next row 128 | cmp bh,START_COLOR+55 ; Whole board finished? 129 | jne in1 ; No, jump 130 | 131 | ; 132 | ; Draw the barriers 133 | ; 134 | mov di,0x55*0x280+0x10*2 135 | mov cl,5 136 | in48: 137 | mov ax,BARRIER_COLOR*0x0100+0x04 138 | call draw_sprite 139 | add di,0x1e*2 140 | loop in48 141 | 142 | ; CH is zero 143 | 144 | in14: 145 | mov si,sprites+SPRITE_SIZE 146 | 147 | ; 148 | ; Game loop 149 | ; 150 | ; Globals: 151 | ; SI = Next invader to animate 152 | ; DL = state (0=left, 1=right, >=2 down) 153 | ; DH = nstate (next state) 154 | ; CH = dead invaders 155 | ; BP = frame counter 156 | ; 157 | in46: 158 | cmp byte [si+2],0x20 ; Current invader is cosmic debris? 159 | jc in2 ; No, jump 160 | inc ch ; Count another dead invader 161 | cmp ch,55 ; All invaders defeated? 162 | je restart_game ; Yes, jump. 163 | ; 164 | ; Yes, invaders speed up 165 | ; 166 | in6: 167 | lodsw ; Load position in AX 168 | xchg ax,di ; Move to DI 169 | lodsw ; Get type of sprite 170 | cmp al,0x28 ; Destroyed? 171 | je in27 ; Yes, jump 172 | cmp al,0x20 ; Explosion? 173 | jne in29 ; No, jump 174 | mov byte [si-2],0x28 ; Don't draw again 175 | in29: call draw_sprite ; Draw invader on screen 176 | in27: cmp si,sprites+56*SPRITE_SIZE ; Whole board revised? 177 | jne in46 ; No, jump 178 | mov al,dh 179 | sub al,2 ; Going down? 180 | jc in14 ; No, preserve left/right direction 181 | xor al,1 ; Switch direction 182 | mov dl,al 183 | mov dh,al 184 | jmp in14 185 | 186 | in2: 187 | xor byte [si+2],8 ; Invader animation (before possible explosion) 188 | ; 189 | ; Synchronize game to 18.20648 hz. of BIOS 190 | ; 191 | inc bp 192 | and bp,7 ; Each 8 invaders 193 | %if pure8088 194 | push dx 195 | push si 196 | push bp 197 | %else 198 | pusha 199 | %endif 200 | jne in12 201 | in22: 202 | mov ah,0x00 203 | int 0x1a ; BIOS clock read 204 | cmp dx,[old_time] ; Wait for change 205 | je in22 206 | mov [old_time],dx ; Save new current time 207 | in12: 208 | %if 1 209 | ; 210 | ; Handle player bullet 211 | ; 212 | mov si,shots ; Point to shots list 213 | mov cx,4 ; 4 shots at most 214 | lodsw ; Read position (player) 215 | cmp ax,X_WIDTH ; Is it at top of screen? 216 | xchg ax,di 217 | jc in31 ; Erase bullet 218 | ; Doesn't mind doing it all time 219 | call zero ; Remove bullet 220 | sub di,X_WIDTH+2 221 | mov al,[di] ; Read pixel 222 | sub al,0x20 ; Hits invader? 223 | jc in30 ; No, jump 224 | %if pure8088 225 | push si 226 | push di 227 | %else 228 | pusha 229 | %endif 230 | mov ah,SPRITE_SIZE ; The pixel indicates the... 231 | mul ah ; ...invader hit. 232 | add si,ax 233 | lodsw 234 | xchg ax,di 235 | mov byte [si],0x20 ; Erase next time 236 | mov ax,INVADER_EXPLOSION_COLOR*0x0100+0x08 ; But explosion now 237 | call draw_sprite ; Draw sprite 238 | %if pure8088 239 | pop di 240 | pop si 241 | %else 242 | popa 243 | %endif 244 | jmp in31 245 | 246 | ; 247 | ; Handle invader bullets 248 | ; 249 | in24: 250 | lodsw ; Read current coordinate 251 | or ax,ax ; Is it falling? 252 | je in23 ; No, jump 253 | cmp ax,0x60*OFFSET_X ; Pixel lower than spaceship? 254 | xchg ax,di 255 | jnc in31 ; Yes, remove bullet 256 | call zero ; Remove bullet 257 | add di,X_WIDTH-2 ; Bullet falls down 258 | 259 | ; Draw bullet 260 | in30: 261 | mov ax,BULLET_COLOR*0x0100+BULLET_COLOR 262 | mov [si-2],di ; Update position of bullet 263 | cmp byte [di+X_WIDTH],BARRIER_COLOR ; Barrier in path? 264 | jne in7 ; Yes, erase bullet and barrier pixel 265 | 266 | ; Remove bullet 267 | in31: xor ax,ax ; AX contains zero (DI unaffected) 268 | mov [si-2],ax ; Delete bullet from table 269 | 270 | in7: cmp byte [di],SPACESHIP_COLOR ; Check collision with player 271 | jne in41 ; No, jump 272 | mov word [sprites],SHIP_EXPLOSION_COLOR*0x0100+0x38 ; Player explosion 273 | in41: 274 | call big_pixel ; Draw/erase bullet 275 | in23: loop in24 276 | %endif 277 | 278 | ; 279 | ; Spaceship handling 280 | ; 281 | mov si,sprites ; Point to spaceship 282 | lodsw ; Load sprite frame / color 283 | or al,al ; Explosion? 284 | je in42 ; No, jump 285 | add al,0x08 ; Keep explosion 286 | jne in42 ; Finished? No, jump 287 | mov ah,SPACESHIP_COLOR ; Restore color (sprite already) 288 | dec byte [lives] ; Remove one life 289 | js in10 ; Exit if all used 290 | in42: mov [si-2],ax ; Save new frame / color 291 | mov di,[si] ; Load position 292 | call draw_sprite ; Draw sprite (spaceship) 293 | jne in43 ; Jump if still explosion 294 | 295 | mov ah,0x02 ; BIOS Get Keyboard Flags 296 | int 0x16 297 | %if com_file 298 | test al,0x10 ; Test for Scroll Lock and exit 299 | jnz in10 300 | %endif 301 | 302 | test al,0x04 ; Ctrl key? 303 | jz in17 ; No, jump 304 | dec di ; Move 2 pixels to left 305 | dec di 306 | 307 | in17: test al,0x08 ; Alt key? 308 | jz in18 ; No, jump 309 | inc di ; Move 2 pixels to right 310 | inc di 311 | in18: 312 | test al,0x03 ; Shift keys? 313 | jz in35 ; No, jump 314 | cmp word [shots],0 ; Bullet available? 315 | jne in35 ; No, jump 316 | lea ax,[di+(0x04*2)] ; Offset from spaceship 317 | mov [shots],ax ; Start bullet 318 | in35: 319 | xchg ax,di 320 | cmp ax,SHIP_ROW-2 ; Update if not touching border 321 | je in43 322 | cmp ax,SHIP_ROW+0x0132 323 | je in43 324 | in19: mov [si],ax ; Update position 325 | in43: 326 | %if pure8088 327 | pop bp 328 | pop si 329 | pop dx 330 | %else 331 | popa 332 | %endif 333 | 334 | mov ax,[si] ; Get position of current invader 335 | cmp dl,1 ; Going down (state 2)? 336 | jbe in9 ; No, jump 337 | add ax,0x0280 ; Go down by 2 pixels 338 | cmp ax,0x55*0x280 ; Reaches Earth? 339 | jc in8 ; No, jump 340 | in10: 341 | %if com_file 342 | mov ax,0x0003 ; Restore text mode 343 | int 0x10 344 | %endif 345 | int 0x20 ; Exit to DOS 346 | 347 | in9: dec ax ; Moving to left 348 | dec ax 349 | jc in20 350 | add ax,4 ; Moving to right 351 | in20: push ax 352 | shr ax,1 ; Divide position by 2... 353 | mov cl,0xa0 ; ...means we can get column dividing by 0xa0 354 | div cl ; ...instead of 0x0140 (longer code) 355 | dec ah ; Convert 0x00 to 0xff 356 | cmp ah,0x94 ; Border touched? (>= 0x94) 357 | pop ax 358 | jb in8 ; No, jump 359 | or dh,22 ; Goes down by 11 pixels (11 * 2) must be odd 360 | in8: mov [si],ax 361 | add ax,0x06*0x280+0x03*2 ; Offset for bullet 362 | xchg ax,bx 363 | 364 | mov cx,3 ; ch = 0 - invader alive 365 | in al,(0x40) ; Read timer 366 | cmp al,0xfc ; Random event happening? 367 | jc in4 ; No, jump 368 | ; 369 | ; Doesn't work in my computer: 370 | ; 371 | ; mov di,shots+2 372 | ; xor ax,ax 373 | ; repne scasw 374 | ; mov [di-2],bx 375 | ; 376 | mov di,shots+2 377 | in45: cmp word [di],0 ; Search for free slot 378 | je in44 ; It's free, jump! 379 | scasw ; Advance DI 380 | loop in45 ; Until 3 slots searched 381 | in44: 382 | mov [di],bx ; Start invader shot (or put in ignored slot) 383 | in4: 384 | jmp in6 385 | 386 | ; 387 | ; Bitmaps for sprites 388 | ; 389 | bitmaps: 390 | db 0x18,0x18,0x3c,0x24,0x3c,0x7e,0xFf,0x24 ; Spaceship 391 | db 0x00,0x80,0x42,0x18,0x10,0x48,0x82,0x01 ; Explosion 392 | db 0x00,0xbd,0xdb,0x7e,0x24,0x3c,0x66,0xc3 ; Alien (frame 1) 393 | db 0x00,0x3c,0x5a,0xff,0xa5,0x3c,0x66,0x66 ; Alien (frame 2) 394 | db 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 ; Erase 395 | 396 | ; 397 | ; Draw pixel per Carry (use AX if Carry=1 or zero if Carry=0) 398 | ; 399 | bit: jc big_pixel 400 | zero: xor ax,ax 401 | ; Draw a big pixel (2x2 pixels) 402 | big_pixel: 403 | mov [di+X_WIDTH],ax 404 | stosw 405 | ret 406 | 407 | ; ah = sprite color 408 | ; al = sprite (x8) 409 | ; di = Target address 410 | draw_sprite: 411 | %if pure8088 412 | push cx 413 | push di 414 | pushf 415 | %else 416 | pusha 417 | %endif 418 | in3: push ax 419 | mov bx,bitmaps 420 | cs xlat ; Extract one byte from bitmap 421 | xchg ax,bx ; bl contains byte, bh contains color 422 | mov cx,10 ; Two extra zero pixels at left and right 423 | clc ; Left pixel as zero (clean) 424 | in0: mov al,bh ; Duplicate color in AX 425 | mov ah,bh 426 | call bit ; Draw pixel 427 | shl bl,1 428 | loop in0 429 | add di,OFFSET_X-20 ; Go to next video line 430 | pop ax 431 | inc ax ; Next bitmap byte 432 | test al,7 ; Sprite complete? 433 | jne in3 ; No, jump 434 | %if pure8088 435 | popf 436 | pop di 437 | pop cx 438 | %else 439 | popa 440 | %endif 441 | ret 442 | 443 | %if com_file 444 | %else 445 | times 510-($-$$) db 0x4f 446 | db 0x55,0xaa ; Make it a bootable sector 447 | %endif 448 | -------------------------------------------------------------------------------- /invaders.com: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nanochess/Invaders/d27422afdcac01eeec1dd3a887d70ba2ec81238b/invaders.com -------------------------------------------------------------------------------- /invaders.img: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nanochess/Invaders/d27422afdcac01eeec1dd3a887d70ba2ec81238b/invaders.img -------------------------------------------------------------------------------- /invaders.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nanochess/Invaders/d27422afdcac01eeec1dd3a887d70ba2ec81238b/invaders.png --------------------------------------------------------------------------------