From 313a7e4879ea1c9bd72727538b31faa624453ff4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pavel=20Kala=C5=A1=20=28Floxen=29?= Date: Sun, 12 Jul 2026 19:33:50 +0200 Subject: [PATCH] =?UTF-8?q?P=C5=99idal=20jsem=20testovac=C3=AD=20Tetris,?= =?UTF-8?q?=20co=20jde=20norm=C3=A1ln=C4=9B=20hr=C3=A1t?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Je tam výběr obtížnosti, velikosti pole, slow mode na P a víc tvarů.\n\nNení to ještě finální AAA hra, ale jako test frameworku je to už prakticky použitelné. --- apps/tetris_app.c | 496 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 496 insertions(+) create mode 100644 apps/tetris_app.c diff --git a/apps/tetris_app.c b/apps/tetris_app.c new file mode 100644 index 0000000..2daa838 --- /dev/null +++ b/apps/tetris_app.c @@ -0,0 +1,496 @@ +/* + * AsterOS Kernel + * Autor: Pavel Kalas + * Rok: 2026 + * + */ + +/* + * Testovaci hra Tetris napsana nad Aster App lifecycle API. + * Ukazuje jednoduchy update/draw loop, render helpery a klavesnicovy vstup. + */ + +#include "aster_api.h" + +#define TETRIS_BOARD_MAX_H 24 +#define TETRIS_BOARD_MAX_W 26 + +#define TETRIS_SHAPES 7 + +typedef struct { + int board_size; + int board_w; + int board_h; + int offset_x; + int offset_y; + u8 board[TETRIS_BOARD_MAX_H][TETRIS_BOARD_MAX_W]; + int piece_id; + u64 piece_counter; + int piece_x; + int piece_y; + int score; + int difficulty; + int slow_mode; + int base_drop_ticks; + int base_drop_frames; + u64 last_drop_tick; + u64 last_drop_frame; + int drop_every_ticks; + int drop_every_frames; + int game_over; +} tetris_state_t; + +static const u8 g_tetris_shapes[TETRIS_SHAPES][4][4] = { + { + {0, 0, 0, 0}, + {1, 1, 1, 1}, + {0, 0, 0, 0}, + {0, 0, 0, 0} + }, + { + {0, 1, 1, 0}, + {0, 1, 1, 0}, + {0, 0, 0, 0}, + {0, 0, 0, 0} + }, + { + {0, 1, 0, 0}, + {1, 1, 1, 0}, + {0, 0, 0, 0}, + {0, 0, 0, 0} + }, + { + {1, 0, 0, 0}, + {1, 1, 1, 0}, + {0, 0, 0, 0}, + {0, 0, 0, 0} + }, + { + {0, 0, 1, 0}, + {1, 1, 1, 0}, + {0, 0, 0, 0}, + {0, 0, 0, 0} + }, + { + {0, 1, 1, 0}, + {1, 1, 0, 0}, + {0, 0, 0, 0}, + {0, 0, 0, 0} + }, + { + {1, 1, 0, 0}, + {0, 1, 1, 0}, + {0, 0, 0, 0}, + {0, 0, 0, 0} + } +}; + +static const char g_tetris_shape_char[TETRIS_SHAPES] = {'I', 'O', 'T', 'L', 'J', 'S', 'Z'}; +static const u8 g_tetris_shape_color[TETRIS_SHAPES] = {0x0B, 0x0E, 0x0D, 0x06, 0x09, 0x0A, 0x0C}; + +static void tetris_apply_speed_profile(tetris_state_t *s) { + if (!s) { + return; + } + + s->drop_every_ticks = s->base_drop_ticks; + s->drop_every_frames = s->base_drop_frames; + + if (s->slow_mode) { + s->drop_every_ticks *= 3; + s->drop_every_frames *= 3; + } +} + +static void tetris_set_difficulty(tetris_state_t *s, int difficulty) { + if (!s) { + return; + } + + s->difficulty = difficulty; + + if (difficulty == 1) { + s->base_drop_ticks = 24; + s->base_drop_frames = 18; + } else if (difficulty == 2) { + s->base_drop_ticks = 14; + s->base_drop_frames = 11; + } else if (difficulty == 3) { + s->base_drop_ticks = 6; + s->base_drop_frames = 5; + } else { + s->base_drop_ticks = 3; + s->base_drop_frames = 2; + } + + tetris_apply_speed_profile(s); +} + +static int tetris_prompt_difficulty(void) { + for (;;) { + int k; + (void)aster_api_clear(); + (void)aster_api_print_line("TETRIS - vyber obtiznost:"); + (void)aster_api_print_line("1 = lehka"); + (void)aster_api_print_line("2 = stredni"); + (void)aster_api_print_line("3 = tezka"); + (void)aster_api_print_line("4 = impossible"); + (void)aster_api_print_line(""); + (void)aster_api_print_line("Stiskni 1-4:"); + + k = aster_api_read_key(); + if (k == '1' || k == '2' || k == '3' || k == '4') { + return k - '0'; + } + } +} + +static int tetris_prompt_board_size(void) { + for (;;) { + int k; + (void)aster_api_clear(); + (void)aster_api_print_line("TETRIS - vyber velikost pole:"); + (void)aster_api_print_line("1 = male (12x14)"); + (void)aster_api_print_line("2 = stredni (16x18)"); + (void)aster_api_print_line("3 = velke (20x22)"); + (void)aster_api_print_line(""); + (void)aster_api_print_line("Stiskni 1-3:"); + + k = aster_api_read_key(); + if (k == '1' || k == '2' || k == '3') { + return k - '0'; + } + } +} + +static void tetris_draw_num(int x, int y, int value, u8 fg, u8 bg) { + char out[16]; + char tmp[16]; + int i = 0; + int j = 0; + + if (value == 0) { + out[0] = '0'; + out[1] = '\0'; + (void)aster_api_render_text(x, y, out, fg, bg); + return; + } + + while (value > 0 && i < 15) { + tmp[i++] = (char)('0' + (value % 10)); + value /= 10; + } + + while (i > 0) { + out[j++] = tmp[--i]; + } + out[j] = '\0'; + (void)aster_api_render_text(x, y, out, fg, bg); +} + +static int tetris_piece_cell_at(const tetris_state_t *s, int px, int py) { + if (!s) { + return 0; + } + + if (s->piece_id < 0 || s->piece_id >= TETRIS_SHAPES) { + return 0; + } + + if (px < 0 || px >= 4 || py < 0 || py >= 4) { + return 0; + } + + return g_tetris_shapes[s->piece_id][py][px] ? 1 : 0; +} + +static int tetris_collides(const tetris_state_t *s, int base_x, int base_y) { + int y; + int x; + + for (y = 0; y < 4; ++y) { + for (x = 0; x < 4; ++x) { + int bx; + int by; + if (!tetris_piece_cell_at(s, x, y)) { + continue; + } + + bx = base_x + x; + by = base_y + y; + + if (bx < 0 || bx >= s->board_w || by >= s->board_h) { + return 1; + } + + if (by >= 0 && s->board[by][bx]) { + return 1; + } + } + } + + return 0; +} + +static void tetris_lock_piece(tetris_state_t *s) { + int y; + int x; + + for (y = 0; y < 4; ++y) { + for (x = 0; x < 4; ++x) { + int bx; + int by; + if (!tetris_piece_cell_at(s, x, y)) { + continue; + } + + bx = s->piece_x + x; + by = s->piece_y + y; + if (bx >= 0 && bx < s->board_w && by >= 0 && by < s->board_h) { + s->board[by][bx] = (u8)(s->piece_id + 1); + } + } + } +} + +static void tetris_clear_lines(tetris_state_t *s) { + int y; + + for (y = s->board_h - 1; y >= 0; --y) { + int x; + int full = 1; + + for (x = 0; x < s->board_w; ++x) { + if (!s->board[y][x]) { + full = 0; + break; + } + } + + if (full) { + int yy; + for (yy = y; yy > 0; --yy) { + for (x = 0; x < s->board_w; ++x) { + s->board[yy][x] = s->board[yy - 1][x]; + } + } + for (x = 0; x < s->board_w; ++x) { + s->board[0][x] = 0; + } + s->score += 100; + ++y; + } + } +} + +static void tetris_spawn_piece(tetris_state_t *s) { + s->piece_id = (int)((s->piece_counter++) % TETRIS_SHAPES); + s->piece_x = (s->board_w / 2) - 2; + s->piece_y = 0; + if (tetris_collides(s, s->piece_x, s->piece_y)) { + s->game_over = 1; + } +} + +static int tetris_initial(aster_api_app_state_t *state, void *user) { + tetris_state_t *s = (tetris_state_t *)user; + int y; + int x; + + (void)state; + + if (s->board_size == 1) { + s->board_w = 12; + s->board_h = 14; + } else if (s->board_size == 2) { + s->board_w = 16; + s->board_h = 18; + } else { + s->board_w = 20; + s->board_h = 22; + } + s->offset_x = 2; + s->offset_y = 2; + s->score = 0; + s->slow_mode = 0; + tetris_apply_speed_profile(s); + s->game_over = 0; + + if (s->board_w > TETRIS_BOARD_MAX_W || s->board_h > TETRIS_BOARD_MAX_H) { + return ASTER_API_APP_ERROR; + } + + (void)aster_api_clear(); + + for (y = 0; y < s->board_h; ++y) { + for (x = 0; x < s->board_w; ++x) { + s->board[y][x] = 0; + } + } + + tetris_spawn_piece(s); + s->last_drop_tick = aster_api_ticks_now(); + s->last_drop_frame = state ? state->frame : 0; + + return ASTER_API_APP_CONTINUE; +} + +static int tetris_update(aster_api_app_state_t *state, void *user) { + tetris_state_t *s = (tetris_state_t *)user; + int key = aster_api_try_read_key(); + u64 now = aster_api_ticks_now(); + int should_drop = 0; + + if (key == 'q' || key == 'Q') { + return ASTER_API_APP_CLOSE; + } + + if (key == 'p' || key == 'P') { + s->slow_mode = s->slow_mode ? 0 : 1; + tetris_apply_speed_profile(s); + } + + if (s->game_over) { + if (key == 'r' || key == 'R') { + (void)tetris_initial(state, user); + } + return ASTER_API_APP_CONTINUE; + } + + if (key == ASTER_API_KEY_LEFT || key == 'a' || key == 'A') { + if (!tetris_collides(s, s->piece_x - 1, s->piece_y)) { + s->piece_x -= 1; + } + } else if (key == ASTER_API_KEY_RIGHT || key == 'd' || key == 'D') { + if (!tetris_collides(s, s->piece_x + 1, s->piece_y)) { + s->piece_x += 1; + } + } else if (key == ASTER_API_KEY_DOWN || key == 's' || key == 'S') { + if (!tetris_collides(s, s->piece_x, s->piece_y + 1)) { + s->piece_y += 1; + } + } + + if ((now - s->last_drop_tick) > 0) { + if ((now - s->last_drop_tick) >= (u64)s->drop_every_ticks) { + should_drop = 1; + s->last_drop_tick = now; + s->last_drop_frame = state->frame; + } + } else if ((state->frame - s->last_drop_frame) >= (u64)s->drop_every_frames) { + should_drop = 1; + s->last_drop_frame = state->frame; + } + + if (should_drop) { + s->last_drop_tick = now; + + if (!tetris_collides(s, s->piece_x, s->piece_y + 1)) { + s->piece_y += 1; + } else { + tetris_lock_piece(s); + tetris_clear_lines(s); + tetris_spawn_piece(s); + } + } + + return ASTER_API_APP_CONTINUE; +} + +static int tetris_draw(aster_api_app_state_t *state, void *user) { + tetris_state_t *s = (tetris_state_t *)user; + int hud_x = s->offset_x + s->board_w + 4; + int y; + int x; + + (void)state; + + (void)aster_api_render_border(s->offset_x - 1, s->offset_y - 1, s->board_w + 2, s->board_h + 2, 0x0F, 0x00); + + for (y = 0; y < s->board_h; ++y) { + for (x = 0; x < s->board_w; ++x) { + if (s->board[y][x]) { + int id = (int)(s->board[y][x] - 1); + if (id < 0 || id >= TETRIS_SHAPES) { + id = 0; + } + (void)aster_api_render_char(s->offset_x + x, s->offset_y + y, g_tetris_shape_char[id], g_tetris_shape_color[id], 0x00); + } else { + (void)aster_api_render_char(s->offset_x + x, s->offset_y + y, '.', 0x01, 0x00); + } + } + } + + for (y = 0; y < 4; ++y) { + for (x = 0; x < 4; ++x) { + if (!tetris_piece_cell_at(s, x, y)) { + continue; + } + (void)aster_api_render_char(s->offset_x + s->piece_x + x, s->offset_y + s->piece_y + y, + g_tetris_shape_char[s->piece_id], + g_tetris_shape_color[s->piece_id], + 0x00); + } + } + + (void)aster_api_render_text(hud_x, 2, "TETRIS demo", 0x0E, 0x00); + (void)aster_api_render_text(hud_x, 4, "A/D nebo sipky: pohyb", 0x07, 0x00); + (void)aster_api_render_text(hud_x, 5, "S nebo dolu: rychleji", 0x07, 0x00); + (void)aster_api_render_text(hud_x, 6, "Q: konec", 0x07, 0x00); + (void)aster_api_render_text(hud_x, 7, "P: slow mode", 0x07, 0x00); + (void)aster_api_render_text(hud_x, 8, "Skore:", 0x0F, 0x00); + tetris_draw_num(hud_x + 7, 8, s->score, 0x0F, 0x00); + + (void)aster_api_render_text(hud_x, 9, "Obtiznost:", 0x07, 0x00); + if (s->difficulty == 1) { + (void)aster_api_render_text(hud_x + 11, 9, "lehka", 0x0A, 0x00); + } else if (s->difficulty == 2) { + (void)aster_api_render_text(hud_x + 11, 9, "stredni", 0x0E, 0x00); + } else if (s->difficulty == 3) { + (void)aster_api_render_text(hud_x + 11, 9, "tezka", 0x0C, 0x00); + } else { + (void)aster_api_render_text(hud_x + 11, 9, "impossible", 0x0D, 0x00); + } + + (void)aster_api_render_text(hud_x, 10, "Slow:", 0x07, 0x00); + if (s->slow_mode) { + (void)aster_api_render_text(hud_x + 6, 10, "ON", 0x0A, 0x00); + } else { + (void)aster_api_render_text(hud_x + 6, 10, "OFF", 0x08, 0x00); + } + + if (s->game_over) { + (void)aster_api_render_text(hud_x, 12, "GAME OVER", 0x0C, 0x00); + (void)aster_api_render_text(hud_x, 13, "R: restart", 0x0F, 0x00); + } + + return ASTER_API_APP_CONTINUE; +} + +static int tetris_closing(aster_api_app_state_t *state, void *user) { + (void)state; + (void)user; + + (void)aster_api_clear(); + (void)aster_api_print_line("[tetris] v1.0 by Pavel Kalas 2026 <3"); + (void)aster_api_print_line("[tetris] ukonceno"); + return ASTER_API_APP_CONTINUE; +} + +void app_tetris_main(void) { + tetris_state_t state; + aster_api_app_callbacks_t callbacks; + + state.difficulty = tetris_prompt_difficulty(); + state.board_size = tetris_prompt_board_size(); + state.slow_mode = 0; + state.piece_counter = 0; + tetris_set_difficulty(&state, state.difficulty); + + callbacks.initial = tetris_initial; + callbacks.update = tetris_update; + callbacks.draw = tetris_draw; + callbacks.closing = tetris_closing; + + (void)aster_api_app_run(&callbacks, &state, 30); +}