├── .gitignore ├── configure ├── Makefile ├── LICENSE ├── README.md └── tetranglix.asm /.gitignore: -------------------------------------------------------------------------------- 1 | bochsout.txt 2 | .bochsrc 3 | *.img 4 | 5 | totabs.sh 6 | tospaces.sh 7 | -------------------------------------------------------------------------------- /configure: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | cd /usr/include 3 | for i in * 4 | do 5 | echo -n "checking for $i... " 6 | if test z"$i" = z"linux" 7 | then 8 | echo "no" 9 | echo "Error: $i not found." 10 | exit 1 11 | fi 12 | sleep $(expr index $(date | cksum | sed 's/ ..$//') 5) 13 | echo "yes" 14 | done 15 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # OUTIMG. 2 | OUTIMG := tetranglix.img 3 | 4 | ASMSRC := $(shell find ./ -type f -name "*.asm") 5 | 6 | # Make related files. 7 | MAKEDEPS := Makefile 8 | 9 | # The default target. 10 | all: $(OUTIMG) 11 | 12 | # List phony targets. 13 | .PHONY: all clean dog 14 | 15 | $(OUTIMG): $(ASMSRC) $(MAKEDEPS) 16 | nasm $(ASMSRC) -fbin -o $(OUTIMG) 17 | 18 | # Clean. 19 | clean: 20 | -$(RM) $(wildcard $(OUTIMG)) 21 | 22 | # Dog. 23 | dog: 24 | $(warning Experimental dog generator. Don't try it out; the default size isn't set, so-) 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Tetranglix 2 | 3 | ![Screenshot](http://i.imgur.com/HaHrpoU.png) 4 | 5 | Tetranglix, a 512-byte OS, is intended to be a playable and bootable Tetris clone. While the project aims at fitting it 6 | in 512-bytes, it tries not to compromise with the gameplay, that which is important to the gamer. 7 | 8 | ## Why? 9 | 10 | The project originates from `#osdev-offtopic` (on Freenode, `irc.freenode.net`), where sortiecat provided inspiration to 11 | the co-authors to write a 512-byte Tetris clone. It was released on the 16th of September, 2013, to mark the 1-year 12 | anniversary of the channel. 13 | 14 | And, of course, "Why not?" 15 | 16 | ## Testing It Out 17 | 18 | ### Building It Yourself 19 | 20 | The build system only supports *nix hosts, and as such hasn't been tested elsewhere. After cloning the repository, you can run: 21 | 22 | ./configure && make dog 23 | make 24 | 25 | Note that `./configure` isn't absolutely necessary, and might fail on some hosts. After performing the above operations, 26 | you'll obtain a floppy disk, `tetranglix.img`, in the build directory. 27 | 28 | ### Pre-built Images 29 | 30 | Pre-built images can be obtained from the Releases section, on the GitHub repository. 31 | 32 | ### Running 33 | 34 | The floppy image works with all major emulators, and any 1.44MiB floppy disk. Gameplay on Bochs might be a bit difficult to handle, 35 | due to the way Bochs handles timing. 36 | 37 | As an example, `qemu-system-i386 -fda tetranglix.img` can be used to test it via QEMU. 38 | 39 | ## Gameplay 40 | 41 | Anyone familiar with Tetris wouldn't have much difficulty adapting to Tetranglix. The left and right cursor controls 42 | work exactly as expected. The up cursor control rotates the current tetramino in the clockwise direction. 43 | 44 | Due to a lack of space, scoring was left out. The only challenge in the game is to survive, and the game (unofficially) 45 | ends when any newly spawned tetramino collides with an already existing one. 46 | 47 | ## Hacking It 48 | 49 | The game was written by, and for hackers. For starters, clone the repository and tinker around. Simple changes can 50 | include modifying line 86 to decrease or increase the difficulty. 51 | 52 | As an open challenge, we invite you all to add scoring support. 53 | 54 | *UPDATE*: Thanks to Peter (peterferrie), scoring has now been added, and the game halts on end. 55 | 56 | ## Authors 57 | 58 | While only nortti (JuEeHa) and shikhin have pushed to the repository, XgF (erincandescent) has helped immensely by optimizing 59 | several parts of the game. sortie was responsible for testing it out regularly, and giving the original inspiration. 60 | 61 | All authors hang out in `#offtopia` (Libera.Chat). 62 | -------------------------------------------------------------------------------- /tetranglix.asm: -------------------------------------------------------------------------------- 1 | ; 16 bits, starting at 0x7C00. 2 | BITS 16 3 | ORG 0x7C00 4 | 5 | BSS EQU 0x504 ; The byte at 0x500 is also used, so align on next dword bound. 6 | BSS_SIZE EQU 438 7 | 8 | CUR_TETRAMINO EQU BSS ; 16 bytes. 9 | ROT_TETRAMINO EQU BSS + 16 ; 16 bytes. 10 | OFFSET EQU BSS + 32 ; 2 bytes. 11 | STACK EQU BSS + 38 ; 4 bytes reserved in beginning, 400 bytes. 12 | 13 | LEFT_SCANCODE EQU 75 14 | RIGHT_SCANCODE EQU 77 15 | 16 | UP_SCANCODE EQU 72 17 | DOWN_SCANCODE EQU 80 18 | 19 | SCORE_DIGITS EQU 5 20 | 21 | CPU 686 22 | 23 | ; Entry point. 24 | ; cs:ip -> linear address (usually 0x7C00, but irrelevant because we are position independent). 25 | start: 26 | ; Set up segments. 27 | xor ax, ax 28 | 29 | ; Stack. 30 | mov ss, ax 31 | mov sp, 0xB800 ;why not 32 | 33 | mov ds, ax 34 | mov es, ax 35 | 36 | ; Clear direction flag. 37 | cld 38 | 39 | ; Clear BSS 40 | mov di, BSS 41 | mov cx, di ;at least BSS_SIZE 42 | rep stosb 43 | 44 | ; Set to mode 0x03, or 80x25 text mode (ah is zero from above). 45 | mov al, 0x03 46 | int 0x10 47 | 48 | ; Hide the hardware cursor. 49 | mov ch, 0x26 50 | mov ax, 0x103 ; Some BIOS crash without the 03. 51 | int 0x10 52 | 53 | mov es, sp 54 | mov fs, sp 55 | 56 | ; White spaces on black background. 57 | xor di, di 58 | mov ax, 0x0F00 59 | mov cx, ax ; At least 80x25x2. 60 | rep stosw 61 | call pop_check 62 | 63 | ; Detects if CUR_TETRAMINO at OFFSET is colliding with any thing. 64 | ; si -> OFFSET. 65 | ; Output: 66 | ; Carry set if colliding. 67 | tetramino_collision_check: 68 | 69 | lea bx, [bp + check_collision - tetramino_collision_check] 70 | 71 | ; Processes the current tetramino, calling bx per "tetramino pixel". 72 | ; bx -> where to call to; al contains tetramino pixel, di the address into stack. 73 | tetramino_process: 74 | pusha 75 | 76 | ; Gets the offset into stack (i.e., address) into di. 77 | ; si -> points at OFFSET. 78 | ; Output: 79 | ; si -> points at CUR_TETRAMINO. 80 | ; di -> address into stack. 81 | ; Trashes ax. 82 | 83 | ; Calculate first index into screen. 84 | lodsw 85 | aad 0x10 86 | cmp byte [si-1], 0x10 87 | sbb ah, ah 88 | xchg bx, ax 89 | lea di, [si + (STACK - OFFSET) + 0xFE + bx] 90 | xchg bx, ax 91 | 92 | mov si, CUR_TETRAMINO 93 | 94 | mov cl, 0x10 95 | 96 | .loop: 97 | test cl, 0x13;0b1011 98 | jnz .load_loop 99 | 100 | ; Go to next line in stack. 101 | add di, 16 - 4 102 | 103 | .load_loop: 104 | lodsb 105 | 106 | ; Call wherever the caller wants us to go. 107 | call bx 108 | 109 | inc di 110 | loop .loop 111 | 112 | popa 113 | ret 114 | 115 | check_collision: 116 | cmp al, 0xDB 117 | jnz .clear_carry 118 | 119 | cmp di, STACK + 400 120 | jae .colliding 121 | 122 | cmp al, [di] 123 | 124 | .clear_carry: 125 | clc 126 | 127 | jne .next_iter 128 | 129 | ; Colliding! 130 | .colliding: 131 | 132 | stc 133 | mov cl, 1 134 | .next_iter: 135 | ret 136 | 137 | ; Used by the stack joining part. 138 | merge: 139 | or [di], al 140 | ret 141 | 142 | ; All tetraminos in bitmap format. 143 | tetraminos: 144 | db 0xF0;0b11110000 ; I 145 | db 0xE2;0b11100010 ; J 146 | db 0x2E;0b00101110 ; L 147 | db 0x66;0b01100110 ; O 148 | db 0x36;0b00110110 ; S 149 | db 0xE4;0b11100100 ; T 150 | db 0x63;0b01100011 ; Z 151 | 152 | pop_check: 153 | pop bp ; Save some bytes. 154 | 155 | .borders: 156 | mov si, STACK - 3 157 | mov ax, 0xDBDB 158 | 159 | .borders_init: 160 | mov [si], ax 161 | mov [si + 2], ax 162 | mov [si + 4], ax 163 | 164 | add si, 16 165 | cmp si, STACK + 400 - 3 166 | jbe .borders_init 167 | 168 | ; Cleared dl implies "load new tetramino". 169 | xor dl, dl 170 | 171 | .event_loop: 172 | mov si, OFFSET 173 | 174 | mov bx, [0x046C] 175 | inc bx 176 | inc bx ; Wait for 2 PIT ticks. 177 | 178 | .busy_loop: 179 | cmp [0x046C], bx 180 | jne .busy_loop 181 | 182 | ; If we don't need to load a new tetramino, yayy! 183 | test dl, dl 184 | jnz .input 185 | 186 | ; Load a tetramino to CUR_TETRAMINO, from the compressed bitmap format. 187 | 188 | .choose_tetramino: 189 | rdtsc 190 | 191 | ; Only 7 tetraminos, index as 1-7. 192 | and ax, 7 193 | je .choose_tetramino 194 | 195 | ; Get the address of the tetramino (in bitmap format). 196 | cwd 197 | xchg di, ax 198 | 199 | ; Load tetramino bitmap in dl. 200 | mov dl, [cs:bp + di + (tetraminos - tetramino_collision_check) - 1] 201 | shl dx, 4 202 | 203 | ; Convert from bitmap to array. 204 | mov di, CUR_TETRAMINO 205 | mov cl, 0x10 206 | 207 | .loop_bitmap: 208 | 209 | shl dx, 1 210 | 211 | ; If the bit we just shifted off was set, store 0xDB. 212 | sbb al, al 213 | and al, 0xDB 214 | mov [di], al 215 | inc di 216 | 217 | loop .loop_bitmap 218 | 219 | ; Loaded. 220 | mov dl, 6 221 | 222 | mov word [si], dx 223 | jmp .link_next_iter 224 | 225 | ; Check for input. 226 | .input: 227 | ; Check for keystroke. 228 | mov ah, 0x01 229 | int 0x16 230 | 231 | ; If no keystroke, increment vertical offset. 232 | jz .vertical_increment 233 | 234 | ; Clear the keyboard buffer. 235 | xor ah, ah 236 | int 0x16 237 | 238 | ; Go left. 239 | .left: 240 | cmp ah, LEFT_SCANCODE 241 | jne .right 242 | 243 | dec byte [si] 244 | jmp .call_bp 245 | 246 | ; Go right. 247 | .right: 248 | cmp ah, RIGHT_SCANCODE 249 | jne .rotate 250 | 251 | inc byte [si] 252 | 253 | .call_bp: 254 | xor ah, LEFT_SCANCODE ^ RIGHT_SCANCODE 255 | call bp 256 | jc .left 257 | 258 | ; Rotate it. 259 | .rotate: 260 | cmp ah, UP_SCANCODE 261 | jne .vertical_increment 262 | 263 | inc cx 264 | 265 | .rotate_loop: 266 | ; Rotates CUR_TETRAMINO 90 degrees clock-wise. 267 | ; Output: 268 | ; CUR_TETRAMINO -> rotated tetramino. 269 | pusha 270 | push es 271 | 272 | ; Reset ES. 273 | push ds 274 | pop es 275 | 276 | mov si, CUR_TETRAMINO 277 | mov di, ROT_TETRAMINO + 3 278 | push si 279 | mov cl, 4 280 | 281 | .loop: 282 | mov ch, 4 283 | 284 | .tetramino_line: 285 | movsb 286 | scasw 287 | inc di 288 | dec ch 289 | jnz .tetramino_line 290 | 291 | sub di, 4*4+1 292 | loop .loop 293 | 294 | pop di 295 | mov cl, 4*4/2 ; CH would be zero, from above. 296 | rep movsw 297 | 298 | pop es 299 | popa 300 | 301 | loop .rotate_loop 302 | 303 | call bp 304 | ; To restore, just rotate 3 more times. 305 | mov cl, 3 306 | jc .rotate_loop 307 | 308 | .vertical_increment: 309 | mov cl, 1 310 | call upd_score 311 | 312 | ; Check if we can go below one byte, successfully. 313 | inc byte [si + 1] 314 | call bp 315 | .link_next_iter: 316 | jnc .next_iter 317 | 318 | ; If we can't, we need a new tetramino. 319 | dec byte [si + 1] 320 | je $ ; Game Over 321 | cwd 322 | 323 | ; Joins the current tetramino to the stack, and any complete lines together. 324 | ; si -> OFFSET. 325 | push es 326 | 327 | push ds 328 | pop es 329 | 330 | lea bx, [bp + merge - tetramino_collision_check] 331 | call tetramino_process 332 | 333 | mov si, STACK + 15 334 | std 335 | 336 | .loop_lines: 337 | push si 338 | mov cl, 16 339 | 340 | .line: 341 | lodsb 342 | test al, al 343 | loopnz .line ; If it was a blank, exit loop to indicate failure. 344 | 345 | jz .next_line 346 | 347 | lea cx, [si - (STACK - 1)] 348 | lea di, [si + 16] 349 | rep movsb 350 | mov cl, 64 351 | call upd_score 352 | 353 | .next_line: 354 | pop si 355 | add si, 16 356 | cmp si, STACK + 15 + 400 357 | jb .loop_lines 358 | 359 | cld 360 | pop es 361 | 362 | jmp .borders 363 | 364 | .next_iter: 365 | ; Display the stack. 366 | push si 367 | 368 | ; Add 24 characters padding in the front. 369 | mov ah, 0x0F 370 | mov di, 48 371 | mov si, STACK 372 | 373 | .loop_stack_lines: 374 | ; Copy 32 characters. 375 | mov cl, 16 376 | 377 | .stack_line: 378 | lodsb 379 | 380 | ; Store one character as two -- to make stack "squarish" on 80x25 display. 381 | stosw 382 | stosw 383 | 384 | loop .stack_line 385 | 386 | ; Handle remaining 24 characters in row, and starting 24 in next row. 387 | add di, 96 388 | cmp di, (25 * 160) ; If we go beyond the last row, we're over. 389 | jb .loop_stack_lines 390 | 391 | pop si 392 | 393 | ; Displays CUR_TETRAMINO at current OFFSET. 394 | ; si -> OFFSET. 395 | 396 | ; Calculate first index into screen. 397 | mov bx, [si] 398 | mov al, 40 399 | mul bh 400 | mov cl, 12 401 | add cl, bl 402 | add ax, cx 403 | 404 | ; One character takes 2 bytes in video memory. 405 | shl ax, 2 406 | xchg di, ax 407 | 408 | ; Loops for 16 input characters. 409 | mov cl, 0x10 410 | mov si, CUR_TETRAMINO 411 | 412 | mov ah, 0x0F 413 | 414 | .loop_tetramino: 415 | test cl, 0x13;0b1011 416 | jnz .load_tetramino 417 | 418 | ; Since each tetramino input is 4x4, we must go to next line 419 | ; at every multiple of 4. 420 | ; Since we output 2 characters for one input char, cover offset of 8. 421 | add di, (80 - 8) * 2 422 | 423 | .load_tetramino: 424 | lodsb 425 | test al, al 426 | 427 | ; Output two characters for "squarish" output. 428 | cmovz ax, [es:di] 429 | stosw 430 | stosw 431 | 432 | loop .loop_tetramino 433 | 434 | jmp .event_loop 435 | 436 | upd_score: 437 | mov bx, SCORE_DIGITS * 2 438 | 439 | .chk_score: 440 | dec bx 441 | dec bx 442 | js $ 443 | mov al, '0' 444 | xchg [fs:bx], al 445 | or al, 0x30 446 | cmp al, '9' 447 | je .chk_score 448 | inc ax 449 | mov [fs:bx], al 450 | 451 | loop upd_score 452 | ret 453 | 454 | ; IT'S A SECRET TO EVERYBODY. 455 | db "ShNoXgSo" 456 | 457 | ; Padding. 458 | times 510 - ($ - $$) db 0 459 | 460 | BIOS_signature: 461 | dw 0xAA55 462 | 463 | ; Pad to floppy disk. 464 | times (1440 * 1024) - ($ - $$) db 0 465 | --------------------------------------------------------------------------------