diff --git a/programs/games/arkanoid/Tupfile.lua b/programs/games/arkanoid/Tupfile.lua new file mode 100644 index 000000000..476d73d7a --- /dev/null +++ b/programs/games/arkanoid/Tupfile.lua @@ -0,0 +1,7 @@ +if tup.getconfig("NO_FASM") ~= "" then return end +HELPERDIR = (tup.getconfig("HELPERDIR") == "") and "../.." or tup.getconfig("HELPERDIR") +tup.include(HELPERDIR .. "/use_fasm.lua") +add_include(tup.getvariantdir()) + +tup.rule("echo lang fix " .. ((tup.getconfig("LANG") == "") and "en_US" or tup.getconfig("LANG")) .. " > %o", {"lang.inc"}) +tup.rule("arkanoid.asm", FASM .. " %f %o " .. tup.getconfig("KPACK_CMD"), "arkanoid") diff --git a/programs/games/arkanoid/arkanoid.asm b/programs/games/arkanoid/arkanoid.asm new file mode 100644 index 000000000..98b14a63a --- /dev/null +++ b/programs/games/arkanoid/arkanoid.asm @@ -0,0 +1,868 @@ +; ================== +; ARKANOID INFINITE +; ================== +; SPDX-License-Identifier: GPL-2.0-only +; Program - Lbreakout clone game +; Copyright (C) 2026 KolibriOS team +; Author: Max, + +include '../../macros.inc' +include '../../KOSfuncs.inc' + +format binary as 'run' +use32 +org 0x0 + +db 'MENUET01' +dd 0x01 +dd start +dd i_end +dd mem_end +dd stacktop +dd 0, 0 + +; --- CONSTANTS --- +WIN_W equ 550 +WIN_H equ 620 +CLR_WINDOW_BG equ 0xACE1AF +GAME_W equ 380 +GAME_H equ 500 +GAME_X equ 25 +GAME_Y equ 25 +BPP equ 3 +STRIDE equ GAME_W * BPP +BRICK_W equ 40 +BRICK_H equ 15 +MAP_X_OFF equ 30 +MAP_Y_OFF equ 80 +PADDLE_W equ 80 +PADDLE_Y equ 465 +BALL_SIZE equ 8 + + +start: + mov esp, stacktop + mcall SF_SET_EVENTS_MASK, EVM_REDRAW + EVM_BUTTON + EVM_KEY + call load_highscores + call generate_random_level + call draw_full_window + +event_loop: + mcall SF_WAIT_EVENT_TIMEOUT, 1 + test eax, eax + jz no_more_events + cmp eax, EV_REDRAW + je on_redraw + cmp eax, EV_BUTTON + je on_button + cmp eax, EV_KEY + je on_key + jmp event_loop + +on_key: + mcall SF_GET_KEY + shr eax, 8 + cmp dword [input_mode], 1 + je handle_name_input + cmp dword [lives], 0 + jg event_loop + cmp al, 'y' + je reset_game + cmp al, 'Y' + je reset_game + cmp al, 'n' + je close_app + cmp al, 'N' + je close_app + jmp event_loop +draw_input_area: + pushad + mov eax,13 + mov ebx, 420 shl 16 + 80 + mov ecx, 200 shl 16 + 15 + mov edx, CLR_WINDOW_BG + int 0x40 + mov eax, 4 + mov ebx, 420 shl 16 + 200 + mov ecx, 0xFF00FF + mov edx, new_name + mov esi, 8 + int 0x40 + popad + ret + +handle_name_input: + cmp al, 8 ; Backspace + je .backspace + cmp al, 13 ; Enter + je .save_hs + cmp al, ' ' + jl event_loop + cmp al, 'z' + jg event_loop + mov ecx, [name_ptr] + cmp ecx, 8 + jae event_loop + mov [new_name + ecx], al + inc dword [name_ptr] + call draw_input_area + jmp event_loop +.backspace: + cmp dword [name_ptr], 0 + je event_loop + dec dword [name_ptr] + mov ecx, [name_ptr] + mov byte [new_name + ecx], '-' + call draw_input_area + jmp event_loop +.save_hs: + call insert_highscore + mov dword [input_mode], 0 + call draw_full_window + jmp event_loop + +on_button: + mcall SF_GET_BUTTON + shr eax, 8 + cmp eax, 1 + jne event_loop +close_app: + mcall SF_TERMINATE_PROCESS + +on_redraw: + call draw_full_window + jmp event_loop + +reset_game: + mov dword [score], 0 + mov dword [lives], 4 + mov dword [current_level], 1 + mov edi, score_buffer + mov ecx, 8 + xor eax, eax + rep stosd + mov edi, level_buffer + mov ecx, 8 + rep stosd + mov dword [ball_active], 0 + mov dword [old_score], -1 + mov dword [old_lives], -1 + mov dword [old_level], -1 + call generate_random_level + call draw_full_window + jmp event_loop + +no_more_events: + cmp dword [lives], 0 + jle .render_only + cmp dword [input_mode], 1 + je .render_only + mcall 37, 1 + shr eax, 16 + movsx ebx, ax + sub ebx, GAME_X + (PADDLE_W / 2) + test ebx, ebx + jns .check_right + xor ebx, ebx +.check_right: + cmp ebx, GAME_W - PADDLE_W + jle .apply_p + mov ebx, GAME_W - PADDLE_W +.apply_p: + mov [paddle_x], ebx + cmp [ball_active], 0 + je .ball_on_paddle + call move_ball + call check_bricks + cmp dword [bricks_left], 0 + jne .render_only + call next_level + call draw_full_window + jmp .render_only +.ball_on_paddle: + mcall 37, 2 + test eax, 1 + jz .stay_still + mov [ball_active], 1 + mov dword [ball_dx], 2 + mov dword [ball_dy], -4 + jmp .render_only +.stay_still: + mov eax, [paddle_x] + add eax, (PADDLE_W / 2) - (BALL_SIZE / 2) + mov [ball_x], eax + mov [ball_y], PADDLE_Y - BALL_SIZE - 1 +.render_only: + call render_buffer + jmp event_loop + +; --- HIGH SCORE LOGIC --- + +load_highscores: + pushad + mov [file_info.op], 0 + mov [file_info.path], HS_FILE + mov [file_info.buf], hs_data + mov [file_info.size], 60 + mcall 70, file_info + test eax, eax + jnz .create_default + popad + ret +.create_default: + mov edi, hs_data + mov ecx, 60 / 4 + xor eax, eax + rep stosd + mov edi, hs_data + mov ecx, 5 +.init_loop: + push ecx + mov esi, default_name + mov ecx, 8 + rep movsb + xor eax, eax + stosd + pop ecx + loop .init_loop + + call save_highscores + popad + ret + +save_highscores: + pushad + mov [file_info.op], 2 + mov [file_info.path], HS_FILE + mov [file_info.buf], hs_data + mov [file_info.size], 60 + mcall 70, file_info + popad + ret + +check_if_highscore: + mov eax, [score] + test eax, eax + jz .no + mov esi, hs_data + 8 ; point value of the first record + mov ecx, 5 +.l: cmp eax, [esi] + jg .yes + add esi, 12 + loop .l +.no: ret +.yes: + mov dword [input_mode], 1 + mov dword [name_ptr], 0 + mov edi, new_name + mov al, '-' + mov ecx, 8 + rep stosb + ret + + +insert_highscore: + pushad + mov eax, [score] + mov edi, hs_data + mov ecx, 5 +.find_pos: + cmp eax, [edi+8] + jg .found + add edi, 12 + loop .find_pos + popad + ret +.found: + ; edi holds the address of the record being displaced + push edi ecx + ; Shift from the end of the list up to the edi position + mov esi, hs_data + 48 ; start of the last (5th) record + mov edx, hs_data + 60 ; end of buffer +.move_loop: + cmp esi, edi + je .done_move + ; Copy record [esi-12] to [esi] + push esi edi + mov edi, esi + sub esi, 12 + mov ecx, 12 + rep movsb + pop edi esi + sub esi, 12 + jmp .move_loop +.done_move: + pop ecx edi + ; Insert new record at edi location + mov esi, new_name + mov ecx, 8 + rep movsb + mov eax, [score] + stosd + call save_highscores + popad + ret + + +; --- BALL MECHANICS --- +move_ball: + mov eax, [ball_x] + add eax, [ball_dx] + mov [ball_x], eax + cmp eax, 0 + jg .check_right_wall + mov dword [ball_x], 0 + neg dword [ball_dx] + jmp .move_y +.check_right_wall: + cmp eax, GAME_W - BALL_SIZE + jl .move_y + mov dword [ball_x], GAME_W - BALL_SIZE + neg dword [ball_dx] +.move_y: + mov eax, [ball_y] + add eax, [ball_dy] + mov [ball_y], eax + cmp eax, 0 + jg .check_paddle + mov dword [ball_y], 0 + neg dword [ball_dy] + ret +.check_paddle: + cmp eax, PADDLE_Y - BALL_SIZE + jl .check_out + cmp eax, PADDLE_Y + 5 + jg .check_out + mov edx, [ball_x] + add edx, BALL_SIZE / 2 + mov ebx, [paddle_x] + mov ecx, edx + sub ecx, ebx + js .check_out + cmp ecx, 80 + jg .check_out + sub ecx, 40 + mov eax, ecx + cdq + mov esi, 8 + idiv esi + add eax, [ball_dx] + sar eax, 1 + test eax, eax + jnz .apply_dx + mov eax, 1 +.apply_dx: + mov [ball_dx], eax + neg dword [ball_dy] + mov dword [ball_y], PADDLE_Y - BALL_SIZE - 1 + ret +.check_out: + cmp eax, GAME_H + jg .die + ret +.die: + mov dword [ball_active], 0 + dec dword [lives] + jnz .not_over + call check_if_highscore +.not_over: + call update_dynamic_stats + ret + +check_bricks: + pushad + mov esi, active_map + xor edi, edi +.loop_y: + xor ebx, ebx +.loop_x: + mov al, [esi] + test al, al + jz .next + mov ecx, ebx + imul ecx, BRICK_W + add ecx, MAP_X_OFF + mov edx, edi + imul edx, BRICK_H + add edx, MAP_Y_OFF + mov eax, [ball_x] + add eax, BALL_SIZE + cmp eax, ecx + jl .next + sub eax, BALL_SIZE + mov ebp, ecx + add ebp, BRICK_W + cmp eax, ebp + jg .next + mov eax, [ball_y] + add eax, BALL_SIZE + cmp eax, edx + jl .next + sub eax, BALL_SIZE + mov ebp, edx + add ebp, BRICK_H + cmp eax, ebp + jg .next + dec byte [esi] + jnz .brick_reflect + dec dword [bricks_left] +.brick_reflect: + neg dword [ball_dy] + mov eax, [ball_dy] + test eax, eax + js .snap_up + mov [ball_y], ebp + jmp .done_collision +.snap_up: + mov [ball_y], edx + sub dword [ball_y], BALL_SIZE +.done_collision: + add dword [score], 10 + call update_dynamic_stats + popad + ret +.next: + inc esi + inc ebx + cmp ebx, 8 + jl .loop_x + inc edi + cmp edi, 4 + jl .loop_y + popad + ret + +; --- RENDERING --- +draw_full_window: + mcall SF_REDRAW, SSF_BEGIN_DRAW + mcall SF_CREATE_WINDOW, <100, WIN_W>, <100, WIN_H>, 0x33000000 + CLR_WINDOW_BG, 0, title + mcall SF_DRAW_TEXT, <420, 30>, 0x0000FF, score_txt, 5 + mcall SF_DRAW_TEXT, <420, 75>, 0x000000, lives_txt, 5 + mcall SF_DRAW_TEXT, <420, 125>, 0x555555, level_txt, 5 + mcall SF_DRAW_TEXT, <420, 280>, 0x8B0000, hs_title, 11 +.hs_draw: + mov esi, hs_data + mov dword [temp_y], 300 + mov ecx, 5 +.hs_loop: + push ecx + push esi + mov eax, 4 + mov ebx, 420 + shl ebx, 16 + add ebx, [temp_y] + mov ecx, 0x000000 + mov edx, esi + mov esi, 8 + int 0x40 + pop esi + push esi + mov eax, [esi+8] + mov edi, hs_temp_buf + + call int_to_string + mov edx, hs_temp_buf + mov esi, eax + mov eax, 4 + mov ebx, 500 + shl ebx, 16 + add ebx, [temp_y] + mov ecx, 0x000000 + int 0x40 + pop esi + pop ecx + add esi, 12 + add dword [temp_y], 20 + dec ecx + jnz .hs_loop + jmp .hs_finish_up +.hs_finish_up: + + + mov dword [old_score], -1 + mov dword [old_lives], -1 + mov dword [old_level], -1 + call update_dynamic_stats + call render_buffer + mcall SF_REDRAW, SSF_END_DRAW + ret + +update_dynamic_stats: + pushad + ; SCORE + mov eax, [score] + cmp eax, [old_score] + je .skip_score + mov edi, score_buffer + push eax + mov eax, [old_score] + cmp eax, -1 + je .no_old_s + call int_to_string + mcall SF_DRAW_TEXT, <420, 50>, CLR_WINDOW_BG, score_buffer, edx +.no_old_s: + pop eax + mov [old_score], eax + mov edi, score_buffer + call int_to_string + mcall SF_DRAW_TEXT, <420, 50>, 0x0000FF, score_buffer, edx +.skip_score: + + ; LIVES + mov eax, [lives] + cmp eax, [old_lives] + je .skip_lives + mov edi, lives_buffer + push eax + mov eax, [old_lives] + cmp eax, -1 + je .no_old_l + call int_to_string + mcall SF_DRAW_TEXT, <420, 95>, CLR_WINDOW_BG, lives_buffer, edx +.no_old_l: + pop eax + mov [old_lives], eax + cmp eax, 0 + jle .over + mov edi, lives_buffer + call int_to_string + mcall SF_DRAW_TEXT, <420, 95>, 0x000000, lives_buffer, edx +.skip_lives: + + ; LEVEL + mov eax, [current_level] + cmp eax, [old_level] + je .skip_level + mov edi, level_buffer + push eax + mov eax, [old_level] + cmp eax, -1 + je .no_old_v + call int_to_string + mcall SF_DRAW_TEXT, <420, 145>, CLR_WINDOW_BG, level_buffer, edx +.no_old_v: + pop eax + mov [old_level], eax + mov edi, level_buffer + call int_to_string + mcall SF_DRAW_TEXT, <420, 145>, 0x555555, level_buffer, edx +.skip_level: + popad + ret + +.over: + cmp dword [input_mode], 1 + je .input_ui + mcall SF_DRAW_TEXT, <420, 180>, 0x000000, again_txt, 11 + mcall SF_DRAW_TEXT, <420, 200>, 0x000000, y_n_txt, 3 + mcall SF_DRAW_TEXT, <420, 95>, 0xFF0000, over_txt, 9 + popad + ret +.input_ui: + mcall SF_DRAW_TEXT, <420, 180>, 0x0000FF, new_hs_txt, 13 + mcall SF_DRAW_TEXT, <420, 200>, 0xFF00FF, new_name, 8 + mcall SF_DRAW_TEXT, <420, 220>, 0x000000, enter_txt, 13 + popad + ret + +; --- HELPERS --- +int_to_string: + push ebx + push ecx + push edi + + mov ebx, edi ; ebx = buffer + xor ecx, ecx ; digit counter + + ; If number is 0 + test eax, eax + jnz .conv_loop + mov byte [ebx], '0' + mov byte [ebx+1], 0 + mov eax, 1 ; Return length 1 + jmp .done_int + +.conv_loop: + xor edx, edx + mov esi, 10 + div esi + add dl, '0' + push dx ; Save digit on stack + inc ecx + test eax, eax + jnz .conv_loop + + mov eax, ecx ; Return length in EAX + mov edi, ebx +.copy_loop: + pop dx + mov [edi], dl + inc edi + loop .copy_loop + mov byte [edi], 0 + +.done_int: + pop edi + pop ecx + pop ebx + ret + +generate_random_level: + pushad + mov edi, active_map + mov ecx, 32 + mov dword [bricks_left], 0 + mcall 26 + add [random_seed], eax +.gen_loop: + mov eax, [random_seed] + imul eax, 1103515245 + add eax, 12345 + mov [random_seed], eax + shr eax, 16 + xor edx, edx + mov ebx, 3 + div ebx + mov [edi], dl + test dl, dl + jz .skip + inc dword [bricks_left] +.skip: + inc edi + loop .gen_loop + cmp dword [bricks_left], 0 + jne .done + mov byte [active_map], 1 + mov dword [bricks_left], 1 +.done: + popad + ret + +next_level: + inc dword [current_level] + mov dword [ball_active], 0 + call generate_random_level + ret + +render_buffer: + pushad + mov edi, screen_buffer + mov ecx, (GAME_W * GAME_H * 3) / 4 + xor eax, eax + rep stosd + mov esi, active_map + xor ebp, ebp +.r_y: xor edi, edi +.r_x: lodsb + test al, al + jz .r_next + push edi ebp eax + imul ebx, edi, BRICK_W + add ebx, MAP_X_OFF + imul ecx, ebp, BRICK_H + add ecx, MAP_Y_OFF + cmp al, 2 + je .c_s + mov edx, 0xFF0000 + cmp ebp, 1 + je .c_o + cmp ebp, 2 + je .c_y + cmp ebp, 3 + je .c_g + jmp .dr +.c_o: mov edx, 0xFF8C00 + jmp .dr +.c_y: mov edx, 0xFFFF00 + jmp .dr +.c_g: mov edx, 0x00FF00 + jmp .dr +.c_s: mov edx, 0xC0C0C0 +.dr: + mov edi, BRICK_W - 2 + mov ebp, BRICK_H - 2 + call fill_rect + pop eax ebp edi +.r_next: + inc edi + cmp edi, 8 + jl .r_x + inc ebp + cmp ebp, 4 + jl .r_y + mov ebx, [paddle_x] + mov ecx, PADDLE_Y + call draw_paddle + call draw_ball + mcall SF_PUT_IMAGE, screen_buffer, , + popad + ret + +fill_rect: + pushad + imul eax, ecx, STRIDE + imul ecx, ebx, 3 + add eax, ecx + add eax, screen_buffer +.yl: push eax + mov ecx, edi +.xl: mov [eax], dl + mov [eax+1], dh + push edx + shr edx, 16 + mov [eax+2], dl + pop edx + add eax, 3 + loop .xl + pop eax + add eax, STRIDE + dec ebp + jnz .yl + popad + ret + +draw_paddle: + pushad + imul eax, ecx, STRIDE + imul ecx, ebx, 3 + add eax, ecx + add eax, screen_buffer + xor ebp, ebp +.yl: push eax + mov esi, 20 + mov ecx, ebp + shl ecx, 1 + sub esi, ecx + js .sk + imul ecx, esi, 3 + add eax, ecx + mov edi, 80 + sub edi, esi + sub edi, esi + jle .sk +.xl: mov byte [eax+1], 0xFF + add eax, 3 + dec edi + jnz .xl +.sk: pop eax + add eax, STRIDE + inc ebp + cmp ebp, 10 + jl .yl + popad + ret + +draw_ball: + pushad + mov edx, [ball_y] + test edx, edx + js .done + mov ebx, edx + add ebx, 8 + cmp ebx, GAME_H + jg .done + mov ebx, [ball_x] + imul eax, edx, STRIDE + imul ecx, ebx, 3 + add eax, ecx + add eax, screen_buffer + xor edx, edx +.yl: push eax + xor ecx, ecx +.xl: cmp edx, 0 + je .bc + cmp edx, 7 + je .bc + jmp .bp +.bc: cmp ecx, 0 + je .bs + cmp ecx, 7 + je .bs +.bp: mov byte [eax], 0xFF + mov byte [eax+1], 0xFF + mov byte [eax+2], 0xFF +.bs: add eax, 3 + inc ecx + cmp ecx, 8 + jl .xl + pop eax + add eax, STRIDE + inc edx + cmp edx, 8 + jl .yl +.done: + popad + ret + + +; --- DATA --- +DATA + title db 'Arkanoid Infinite', 0 + score_txt db 'SCORE', 0 + lives_txt db 'LIVES', 0 + level_txt db 'LEVEL', 0 + over_txt db 'GAME OVER', 0 + again_txt db 'PLAY AGAIN?', 0 + y_n_txt db 'Y/N', 0 + hs_title db 'HIGH SCORES', 0 + new_hs_txt db 'NEW HIGHSCORE!', 0 + enter_txt db 'PRESS ENTER...', 0 + default_name db 'PLAYER--' + align 4 + paddle_x dd 150 + ball_active dd 0 + ball_x dd 0 + ball_y dd 0 + ball_dx dd 3 + ball_dy dd -4 + score dd 0 + old_score dd -1 + lives dd 4 + old_lives dd -1 + current_level dd 1 + old_level dd -1 + bricks_left dd 0 + random_seed dd 0x42069 + temp_y dd 0 + input_mode dd 0 + name_ptr dd 0 + + + +align 4 +hs_data: + db 'PLAYER--', 0, 0, 0, 0 + db 'PLAYER--', 0, 0, 0, 0 + db 'PLAYER--', 0, 0, 0, 0 + db 'PLAYER--', 0, 0, 0, 0 + db 'PLAYER--', 0, 0, 0, 0 + +align 4 + file_info: + .op dd 0 + .pos dd 0 + .pos_hi dd 0 + .size dd 0 + .buf dd 0 + .rsvd db 0 + .path dd 0 + +align 4 +HS_FILE db '/rd/1/GAMES/arkanoid.hs',0 +align 4 +hs_temp_buf: db 32 dup (0) +score_buffer: db 32 dup (0) +lives_buffer: db 32 dup (0) +level_buffer: db 32 dup (0) +new_name db '--------' + +i_end: + +align 64 +active_map: rb 128 +screen_buffer: rb GAME_W * GAME_H * 3 + 8192 + +align 16 +rb 4096 +stacktop: +mem_end: