Files
pavelkalas 313a7e4879 Přidal jsem testovací Tetris, co jde normálně hrát
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é.
2026-07-12 19:33:50 +02:00

497 lines
13 KiB
C

/*
* 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);
}