diff --git a/2048.ASM b/2048.ASM new file mode 100644 index 0000000..3fdfefb --- /dev/null +++ b/2048.ASM @@ -0,0 +1,112 @@ +; Part of 2048 in assembler +; (C) AHOHNMYC, 2017 +; Licensed under WTFPL v2 +; version 0.0a +; https://github.com/AHOHNMYC/asm2048 + +.model tiny +.386 +.code +org 100h +start: + call delay_selector + call restart ; middle of key_catcher proc in main.inc +exit: mov ax, 3 + int 10h ; reinit 80x25 + int 20h ; exit game; DOS Interrupt ! have to be replaced ! + ; used here because we may want to exit from + ; subroutines with various enclosure level + +;ret ; hardcode^H^H^H^Hcore exit for COM application in *DOS + +delay_selector proc + mov ax, 3 + int 10h ; set text color mode 80x25 +; 1st string + mov bl, 0Ah ; green color + mov ah, 13h ; print string function + mov cx, ds_text_l ; length + mov dx, 10*100h+40-ds_text_l/2 ; position in center + mov bp, offset ds_text + int 10h ; print 1st line +; 2nd string + mov cl, ds_text2_l ; length + mov dx, 12*100h+40-ds_text2_l/2 ; position in center + mov bp, offset ds_text2 + int 10h ; print 2nd line +; 3rd string + mov cl, ds_text3_l ; length +; mov dx, 13*100h+40-ds_text2_l/2 ; column as 2nd line, so : + inc dh ; increase line pointer + mov bp, offset ds_text3 + int 10h ; print 3rd line + +delay_loop: + mov ah, 10h ; get pressed key + int 16h ; Keyboard API + cmp al, '1' + je d_box + cmp al, '2' + je d_qemu + cmp al, '0' ; invisible \ + je d_zero ; useful for debugging + cmp al, ESC_key + je exit + jmp delay_loop + +d_zero: mov delay, 0 + ret +d_box: mov delay, 1 + ret +d_qemu: mov delay, 40h + ret +delay_selector endp + +include main.inc ; main cycle, plates moving, random generation + ; 2048 finding, key press +include graphics.inc ; text print effect, rainbow effect, displaying + ; of header, table, numbers and copyright + +;.data ; places all data with alignment. May eat some bytes +krosavchek db 'You got 2048. Cool, broo! (C)ontinue/(R)estart' +krosavchek_l equ $ - krosavchek +copy_text db '(C) AHOHNMYC, 2017' +copy_text_l equ $ - copy_text +score_text db 'Score:' +score_text_l equ $ - score_text +wasted db 'WASTED' +wasted_l equ $ - wasted +header_text db 'TWENTY FORTYEIGHT' +header_text_l equ $ - header_text + +; store this megastring more effective than generate values dynamicaly +; try to compile POWERS.ASM in "Side products" for comparsion +powers db ' ',' 2 ',' 4 ',' 8 ',' 16 ',' 32 ',' 64 ',' 128' + db ' 256',' 512','1024','2048','4096','8192','2^14','2^15','2^16' + +ds_text db 'Select your platform to set delays in game :3' +ds_text_l equ $ - ds_text +ds_text2 db '1 - DOSBox, DOS on real PC' +ds_text2_l equ $ - ds_text2 +ds_text3 db '2 - QEMU, NTVDM' +ds_text3_l equ $ - ds_text3 + +regstore_s struc + _al db ? + _bl db ? + _cl db ? + _dl db ? +regstore_s ends +regstore regstore_s <> ; temporary storage for registers + +keycode db ? +succ_moving db ? +do_print_spaces db ? +ignr_2048_plate db ? +delay db ? +score dw ? +scorebuf db 5 dup(?) +scorebuf_l equ $-scorebuf +matrix db 16 dup(?) + +end start \ No newline at end of file diff --git a/GRAPHICS.INC b/GRAPHICS.INC new file mode 100644 index 0000000..f6295f2 --- /dev/null +++ b/GRAPHICS.INC @@ -0,0 +1,308 @@ +; Part of 2048 in assembler +; (C) AHOHNMYC, 2017 +; Licensed under WTFPL v2 +; version 0.0a +; https://github.com/AHOHNMYC/asm2048 + +print_text macro text, length, position + mov ax, 1300h + mov cx, length + mov dx, position + mov bp, offset text + int 10h +endm + +print_main proc + mov ax, 0003h + int 10h ; go to color 80x25 mode + xor dx, dx + mov bx, 02h ; colour, green + call print_header + mov bx, 08h ; colour, gray + call print_inner + +; double with teh end of print_border because i moved print_header upper + mov ah, 02h + mov dx, 26*100h ; move cursor to 26th line + int 10h ; out of teh screen + + call rainbow ; SUCH RAINBOW! MUCH GRAFONIUM! SO PERFECT!!!111 + + mov bx, 08h ; colour, gray + print_text score_text, score_text_l, 6*100h+40-score_text_l + print_text copy_text, copy_text_l, 23*100h+78-copy_text_l + ret +print_main endp + +print_header proc + push offset header_text + push header_text_l + call text_effect + ret +print_header endp + +show_loserscreen proc + mov bx, 000Ch + push offset wasted + push wasted_l + call text_effect ; <- call effect and then + mov ax, 10h ; <- wait for keypress + int 16h + cmp al, ESC_key + je exit + ret +show_loserscreen endp + +show_winnerscreen proc + mov bx, 000Ah + push offset krosavchek + push krosavchek_l + call text_effect + +choice_loop: + mov ah, 10h + int 16h + cmp al, 'r' + je choice_done + cmp al, 'c' + je choice_done + cmp al, ESC_key + je exit + jmp choice_loop +choice_done: + + push ax ; here we store pressed key + call print_header ; now we have nice green header :3 + pop ax ; which will be rewritten when new game starts + ret +show_winnerscreen endp + +text_effect proc + mov ah, 02h + mov dx, 3*100h+1 + int 10h ; set cursor + mov ax, 0A20h ; and + mov cx, 78 ; then + int 10h ; rewrite header line with 78 spaces (in AL) + mov ax, [esp+2] ; \ + shr ax, 1 ; | + mov dx, 3*100h+40 ; | dx = 3*100h+40 - [esp+2]/2 + sub dx, ax ; / + + mov ah, 02h + int 10h ; set cursor in beginning to write string + mov cx, [esp+2] ; cx = string length +text_effect_loop: + mov ax, [esp+4] ; \ + add ax, [esp+2] ; | + sub ax, cx ; | al = symbol to write + mov al, [eax] ; / + mov ah, 09h + push cx ; push it there to not to \ + mov cl, 1 ; break [esp+2,4] addressing + int 10h + + inc dx + + push dx ; store position 'cause we r in loop. we need it + mov ah, 02h + int 10h ; cursor to next position + mov ah, 86h + mov cl, delay + xor dx, dx + int 15h ; delay + pop dx + + pop cx + loop text_effect_loop + + pop ax ; temporary store returning address + add sp, 4 ; remove two prameters from stack + push ax ; restore returning address + + mov ah, 02h + mov dx, 26*100h ; move cursor to 26th line + int 10h ; out of teh screen + + ret ; return using restored address +text_effect endp + +; џзҐ©ЄЁ ў 業вॠнЄа ­  +print_inner proc + mov ah, 02h + mov dh, 9 + int 10h + mov cx, 3 +print_inner_loop: + push ' і' + call print_inner_sub + push 'ДЕ' + call print_inner_sub + loop print_inner_loop + push ' і' + call print_inner_sub + ret +print_inner endp + + +print_inner_sub proc + push cx + mov ah, 02h + mov dl, 30 + int 10h + mov cx, 3 +print_inner_sub_loop: + push cx + mov al, [esp+7] + mov cx, 4 + call print_symb ; 1st sumbol x 4 times + mov al, [esp+6] + mov cx, 1 + call print_symb ; 2nd sumbol x 1 time + + pop cx + loop print_inner_sub_loop + + mov al, [esp+5] + mov cx, 4 + call print_symb ; 1st sumbol x 4 times + + inc dh + pop cx + + pop ax ; temporary store returning address + add sp, 2 ; remove one prameter from stack + push ax ; restore returning address + + ret ; return using restored address +print_inner_sub endp + + +print_symb proc + mov ah, 02h ; set cursor position + int 10h ; Video BIOS services + mov ah, 09h ; write charecter + int 10h ; Video BIOS services + add dl, cl ; add 1 to position index + ret +print_symb endp + + +update_screen proc + call display_numbers + call display_score + ret +update_screen endp + + +display_numbers proc + mov bp, offset matrix+15 ; changing, have to be stored + mov dh, 15 ; position, line + mov cx, 4 ; 4 lines +display_numbers_loop1: + push cx + mov dl, 45 ; 4th number, position of 1st symbol + mov cl, 4 ; 4 columns +display_numbers_loop2: + push cx + push bp + + movzx bp, ds:byte ptr [bp] ; BP usually addreses as SS:BP +; movzx bp, cs:byte ptr [bp] ; but we DO NOT WANT + + mov ax, bp ; color generation. Range: + mov bl, 6 ; 0Ah..0Fh which means + div bl ; black BG colour + mov bx, 0Ah ; and bright FG colour in 3 low bits + add bl, ah ; BL = 0Ah + remainder + + shl bp, 2 ; bp = bp*4 \ + add bp, offset powers ; because our substrings occupy 4 bytes\ + mov ax, 1300h ; each + mov cl, 4 ; 4 symbols to grab from [offset power] + int 10h ; so let's print them + + sub dl, 5h ; next (in fact previous) position + ; as our string - 4 and border - 1 + pop bp + dec bp + pop cx + loop display_numbers_loop2 + sub dh, 2 ; move up two lines + pop cx + loop display_numbers_loop1 + ret +display_numbers endp + + +display_score proc + mov ax, score + mov bx, 10 + mov do_print_spaces, bh ; BH=0 + mov cx, scorebuf_l + mov di, offset scorebuf + add di, cx +display_score_loop: + xor dx, dx + div bx + + cmp do_print_spaces, 1 + jge another_space + add dl, '0' +another_space: + + dec di + mov [di], dl + + test ax, ax + jnz dddont + inc do_print_spaces ; we may move "1", but it costs 1 byte. So, no. + ; And as we may maximum increase it 5 times, + ; there is no owerflow in future +dddont: + + loop display_score_loop + + dec bx ; 'l be 9 - 1001 - bright green + print_text scorebuf, scorebuf_l, 6*100h+41 + ret +display_score endp + + +rainbow proc + call fill_zero + + mov ax, offset matrix+16 + xor bl, bl +rainbow_loop_with_counter_init: + mov cx, 16 +rainbow_loop: + pusha ; in fact we have to store AX, BX and CX + + sub ax, cx + test bl, bl + jz nnumber_fill +;zzero_fill + mov byte ptr [eax], 0 + jmp ffffill +nnumber_fill: + mov byte ptr [eax], 17 + sub byte ptr [eax], cl +ffffill: + call display_numbers + + mov ah, 86h ; delay + movzx cx, delay + shl cx, 1 ; delay will be 2x + xor dx, dx ; delay located in CX:DX + int 15h ; System BIOS services + + popa + loop rainbow_loop + + inc bx ; here we set our flag + cmp bl, 1 + je rainbow_loop_with_counter_init ;if flag wasn't already set, jmp + + ret +rainbow endp \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..07b7a81 --- /dev/null +++ b/LICENSE @@ -0,0 +1,13 @@ + DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE + Version 2, December 2004 + +Copyright (C) 2004 Sam Hocevar + +Everyone is permitted to copy and distribute verbatim or modified +copies of this license document, and changing it is allowed as long +as the name is changed. + + DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. You just DO WHAT THE FUCK YOU WANT TO. \ No newline at end of file diff --git a/MAIN.INC b/MAIN.INC new file mode 100644 index 0000000..5de3f55 --- /dev/null +++ b/MAIN.INC @@ -0,0 +1,361 @@ +; Part of 2048 in assembler +; (C) AHOHNMYC, 2017 +; Licensed under WTFPL v2 +; version 0.0a +; https://github.com/AHOHNMYC/asm2048 + +UP equ 48h +LEFT equ 4Bh +RGHT equ 4Dh +DOWN equ 50h +ESC_key equ 1Bh + +key_catcher proc + mov ah, 10h + int 16h + + cmp al, ESC_key ; ESC code located in AL, meanwhile others in AH + je exit + + cmp ah, UP + je gotcha + cmp ah, LEFT + je gotcha + cmp ah, RGHT + je gotcha + cmp ah, DOWN + jne key_catcher +gotcha: + mov keycode, ah + call move_numbers + cmp succ_moving, 1 + jne dont_put_number + +put_und_update: + call put_number +dont_put_number: + call update_screen + + cmp ignr_2048_plate, 1 + je do_ignore_2048_plate + call check_2048_plate + test al, al + jz do_ignore_2048_plate + call show_winnerscreen + + cmp al, 'r' + je restart + mov ignr_2048_plate, 1 ; if chosen (C)ontinue + +do_ignore_2048_plate: + + call can_move + test al, al + jz loshara_ty_proigral + jmp key_catcher ; if loshara still not proigral we + ; goto Teh Endless TimeHole Loop +loshara_ty_proigral: + call show_loserscreen + +restart: + call print_main ; blyadskiy borders and copyright + + xor ax, ax ; register cleaning + mov score, ax ; and moving it to memory + mov ignr_2048_plate, al ; saves 3 our bytes + jmp put_und_update + + ret ; decorative role. it is infinite-loop-function +key_catcher endp ; with exit designed as jmp to 2048.asm module + + +fill_zero proc + mov ax, offset matrix-1 ; 'cause last pass with cx == 1 + mov cx, 16 ;while we have to fill matrix[0] + xor bx, bx +fill_zero_loop: + mov [eax+ecx], bl ; BL=0 + loop fill_zero_loop + + mov score, bx;moving register instead of const "0" saves 2 our bytes + ret +fill_zero endp + + +; proc from Zubkov's book adopted to 16-bit random number. AX - random number +get_random proc + push dx + mov ax, seed ; считать последнее случайное число + test ax, ax ; проверить его, если это -1, + js fetch_seed ; функция еще ни разу не вызывалась + ; и надо создать начальное значение +randomize: + mul word ptr rand_a ; умножить на число а, + div word ptr rand_m ; взять остаток от деления на 231-1 + mov ax, dx + mov seed, ax ; сохранить для следующих вызовов + pop dx + ret + +fetch_seed: + push ds + push 0040h + pop ds + mov ax, ds:006Ch ; считать двойное слово из области + pop ds ; данных BIOS по адресу 0040:0060 - + jmp short randomize ; текущее число тактов таймера + +rand_a dw 271 +rand_m dw 7FFFh +seed dw -1 +get_random endp + +put_number proc + call count_zeros + test ax, ax + jz put_number_exit + push ax + call get_random ; AH - for 2-or-4 value + ; AL - for choosing free plate + mov cl, 1 + cmp ah, 22 ; 23/256 == 9% probability + ; zero - 23rd number + jg dont_put_four_in_cell + inc cl ; CL == 2 that'll shown as 4 on screen +dont_put_four_in_cell: + + pop bx ; restore count of cells with zeroes + xor ah, ah ; clear AH used in 2-or-4 choising + div bl ; AL division with remainder in AH + shr ax, 8 ; movzx ax, ah + inc ax ; now AL - position to place number + mov ah, cl ; AH - number to put in cell + call put_number_sub +put_number_exit: + ret +put_number endp + + +count_zeros proc + xor ax, ax + mov bx, offset matrix-1 ; -1 because of ecx = [1..16] + mov cx, 16 ; we need [0..15] +count_zeros_loop1: + mov dl, [ebx+ecx] + test dl, dl + jnz count_zeros_continue + inc ax +count_zeros_continue: + loop count_zeros_loop1 + ret +count_zeros endp + + +put_number_sub proc + mov bx, offset matrix-1 + mov cx, 16 +put_number_sub_loop: + cmp byte ptr [ebx+ecx], 0 + jne put_number_sub_continue + dec al ; position to put number + jnz put_number_sub_continue ; we wait it to be zero + mov byte ptr [ebx+ecx], ah ; AH = 1..2 (for 2 or 4 putting) + jmp put_number_sub_exit +put_number_sub_continue: + loop put_number_sub_loop +put_number_sub_exit: + ret +put_number_sub endp + +; below - some moving routine +compress_ax_bx proc ; if (al == 0 and bl !=0) + test al, al ; then al = bl; + jnz c_ax_bx_end ; bl = 0; + test bl, bl + jz c_ax_bx_end + xchg ax, bx + mov succ_moving, 1 ; plates had been moved today! +c_ax_bx_end: + ret +compress_ax_bx endp + +merge_ax_bx proc ; if (al != 0 and al == bl) + test al, al ; then al++; + jz m_ax_bx_end ; bl = 0; + cmp al, bl ; score += ax; + jne m_ax_bx_end + inc ax + xor bl, bl + add score, ax + mov succ_moving, 1 ; plates had been moved today! +m_ax_bx_end: + ret +merge_ax_bx endp + +compress_registers proc + mov si, 3 +c_registers_loop: + call compress_ax_bx + xchg ax, cx + xchg ax, bx + call compress_ax_bx + xchg ax, dx + xchg ax, bx + call compress_ax_bx + xchg ax, cx + xchg bx, dx + + dec si ; 'cause in CX we have + jnz c_registers_loop ; 2nd plate value + ret +compress_registers endp + +merge_registers proc + call merge_ax_bx + xchg ax, bx + xchg bx, cx + call merge_ax_bx + xchg ax, bx + xchg bx, dx + call merge_ax_bx + xchg ax, cx + xchg bx, dx + ret +merge_registers endp + +prepare_registers_to_loading proc + mov ax, 3 ; move it with hope of DOWN/RGHT in keykode + mov bx, 2 + mov cx, 1 ; olso it clears high registers, that gives us possi- + xor dx, dx ; bility to save bytes in [preverted] addressing below + + cmp keycode, DOWN + je move_load_end + cmp keycode, RGHT + je move_load_end +;move_up_left_load + xor al, al ; 0 ; its all + dec bx ; 1 ; just + inc cx ; 2 ; byte + mov dl, 3 ; 3 ; fucking +move_load_end: + + cmp keycode, LEFT + je not_vertical + cmp keycode, RGHT + je not_vertical + shl al, 2 + shl bl, 2 + shl cl, 2 + shl dl, 2 +not_vertical: + ret +prepare_registers_to_loading endp + + +move_numbers proc + mov di, offset matrix + mov cx, 4 + mov succ_moving, 0 +loop1: + push cx + + call prepare_registers_to_loading + mov al, [edi+eax] + mov bl, [edi+ebx] + mov cl, [edi+ecx] + mov dl, [edi+edx] + + call compress_registers + call merge_registers + call compress_registers + + mov regstore._al, al + mov regstore._bl, bl + mov regstore._cl, cl + mov regstore._dl, dl + + call prepare_registers_to_loading + call store_registers + + cmp keycode, UP + je goto_next_cell + cmp keycode, DOWN + je goto_next_cell + add di, 3 +goto_next_cell: + inc di + pop cx + loop loop1 + ret +move_numbers endp + + +store_registers proc + push bx + mov bl, regstore._al + mov [edi+eax], bl + pop bx + mov al, regstore._bl + mov [edi+ebx], al + mov al, regstore._cl + mov [edi+ecx], al + mov al, regstore._dl + mov [edi+edx], al + ret +store_registers endp + + +can_move proc + mov ax, 1 ; returning value + mov dx, offset matrix ; pointer to array + mov cx, 4 +can_move_loop_glob: + mov bh, cl + mov cl, 4 + +can_move_loop: + mov bl, [edx] + cmp bl, ah ; comparing with AH equal 0, saves 1 byte + jz we_can_move + + cmp cl, al ; AL=1 ; +1 neighbour + je can_move_loop_next ; doesn't exist on matrix[3,7,11,15] + cmp bl, [edx+1] + je we_can_move + +can_move_loop_next: + cmp bh, al ; AL=1 ; +4 neighbour + jle can_move_loop_continue ; doesn't exist on matrix[12..] + cmp bl, [edx+4] + je we_can_move + +can_move_loop_continue: + inc dx + + loop can_move_loop + mov cl, bh + loop can_move_loop_glob + +;we_canot_move: + xor ax, ax +we_can_move: + ret +can_move endp + + +check_2048_plate proc + xor ax, ax + mov bx, offset matrix-1 + mov cx, 16 +check_2048_plate_loop: + cmp byte ptr [ebx+ecx], 11 + je plate_2048_exists + loop check_2048_plate_loop + jmp check_2048_plate_end + +plate_2048_exists: + inc ax +check_2048_plate_end: + ret +check_2048_plate endp diff --git a/ReadMe.md b/ReadMe.md new file mode 100644 index 0000000..595ca83 --- /dev/null +++ b/ReadMe.md @@ -0,0 +1,64 @@ +# 2048.COM + +*BIOS*-interrupts based, 16-bit assembler written, power of _2_ formed logical game. + +Works under: + +* DOSBox +* FreeDOS, MS-DOS (tested in QEMU with SeaBIOS) + +Doesn't work on: + +* NTVDM +* real PC (hey, don't laugh at me, you) + +*Based on 2048 originally written by Gabriele Cirulli.* +*Licensed as WTFPL software.* +*(C) AHOHNMYC, 2017* + +## Compiling: + +Tested with TASM 4.1 and TLINK 7.1.30.1 + +``` +:: m - multiple passes +TASM /m 2048.ASM +:: t - COM compilation +:: x - 'cause we do not want MAP file +TLINK /t /x 2048.OBJ +``` + +## Bugs: + +One, but fat and complex. + +* Sometimes it doesn't work. + +Game doesn't want to start correctly in XP's NTVDM and on my PC with real (and strange) BIOS. + +I don't know what's wrong with NTVDM and how it interacts with physical BIOS (and exists those interactions at all or not). + +In case of BIOS all is simple: some INTs returns changed registers. This behavior not described in my literature and available references (HelpPC 2.10 [1991], TECH Help! [1993]). + +I see single simple solution: wrapper for all calls that'll pusha/popa every time. (I know, it's ANON.FM's level, but it have to work correctly) + +Something like that: + +```assembly +int_wrapper proc + pusha + ; addressing crap + ; interruption call + + ; maybe sort of self-alterating code for minimum complexity + ;'cause int call - 2 bytes: INT 20h == "CD 20" in hex codes + ; addressing, segment horror - DO NOT WANT. + popa + ret +int_wrapper endp +``` +## Todo: + +* More variability (maybe different phrases dependent on score) +* Try to compress code further (but I at present time done some scary things about reusing high registers and somewhere ignoring high-halfs at all) +* Some easter eggs \ No newline at end of file