diff --git a/.gitignore b/.gitignore index 07d4a8d..4c59a6c 100644 --- a/.gitignore +++ b/.gitignore @@ -60,3 +60,4 @@ src/ginpee.toml src/tetris.log src/high_score.txt src/build/high_score.txt +code-samples/frogger/project.md diff --git a/src/Makefile b/src/Makefile index 146d89b..d0899a4 100644 --- a/src/Makefile +++ b/src/Makefile @@ -2,7 +2,7 @@ # Автоопределение компилятора и флагов CC ?= gcc -CFLAGS ?= -Wall -Wextra -std=c11 -g +CFLAGS ?= -Wall -Wextra -std=c11 -g -D_POSIX_C_SOURCE=199309L CHECK_CFLAGS ?= -I/usr/include/check # Проверяем наличие библиотек diff --git a/src/brick_game/tetris/01_automato.h b/src/brick_game/tetris/01_automato.h index 64f1ccd..f771752 100644 --- a/src/brick_game/tetris/01_automato.h +++ b/src/brick_game/tetris/01_automato.h @@ -1,12 +1,12 @@ #ifndef AUTOMATO_H #define AUTOMATO_H -#define _POSIX_C_SOURCE 199309L // Добавляем здесь для POSIX +#define _POSIX_C_SOURCE 199309L #include "00_tetris.h" #include #include -#include // Для clock_gettime +#include typedef enum { Init, @@ -50,8 +50,11 @@ typedef struct { Moving_t moving_type; int field[FIELD_HEIGHT][FIELD_WIDTH]; GameInfo_t* info; - long long last_move_time; // Время последнего движения (мс) - long long pause_start_time; // Время начала паузы (мс) + long long last_move_time; + long long pause_start_time; + long long attach_start_time; // Время начала attach + int attach_completed; // Флаг завершения attach (фигура размещена) + int down_key_was_released; } GameState_t; GameState_t* get_game_state(void); @@ -61,9 +64,8 @@ void do_init(void); int load_high_score(); void save_high_score(int score); void generate_next_figure(void); -void terminate_and_free(void); // Добавляем прототип здесь +void terminate_and_free(void); -// Вспомогательная функция для времени long long get_current_time_ms(void); // spawn diff --git a/src/brick_game/tetris/02_tetris.c b/src/brick_game/tetris/02_tetris.c index 70eb61b..ffed52b 100644 --- a/src/brick_game/tetris/02_tetris.c +++ b/src/brick_game/tetris/02_tetris.c @@ -4,63 +4,87 @@ void userInput(UserAction_t action, bool hold) { (void)hold; GameState_t* state = get_game_state(); - if (state->info->pause && - (action == Left || action == Right || action == Down || action == Up || - action == Action || action == Start)) { - return; + int should_process = 1; + + // Проверка паузы + if (state->info->pause) { + if (action == Left || action == Right || action == Down || + action == Up || action == Action || action == Start) { + should_process = 0; + } + } + + // Блокируем движения во время Attaching (до завершения задержки) + if (state->state == Attaching && !state->attach_completed) { + if (action == Left || action == Right || action == Down || + action == Up || action == Action) { + should_process = 0; + } } - switch (action) { - case Start: - if (state->info->score >= state->info->high_score) { - state->info->high_score = state->info->score; - save_high_score(state->info->high_score); - } - state->info->high_score = load_high_score(); - state->state = Init; - break; - case Terminate: - if (state->info->score > state->info->high_score) { - state->info->high_score = state->info->score; - save_high_score(state->info->high_score); - } - terminate_and_free(); // Освобождаем память здесь - единственное место - state->state = GameOver; - break; - case Left: - state->state = Moving; - state->moving_type = LeftDown; - break; - case Right: - state->state = Moving; - state->moving_type = RightDown; - break; - case Action: - state->state = Moving; - state->moving_type = Rotate; - break; - case Down: - state->state = Moving; - state->moving_type = ToDown; - break; - case Pause: - if (!state->info->pause) { - state->pause_start_time = get_current_time_ms(); - } else { - long long pause_duration = get_current_time_ms() - state->pause_start_time; - state->last_move_time += pause_duration; - } - state->info->pause = !state->info->pause; - break; - default: - break; + if (should_process) { + switch (action) { + case Start: + if (state->info->score >= state->info->high_score) { + state->info->high_score = state->info->score; + save_high_score(state->info->high_score); + } + state->info->high_score = load_high_score(); + state->state = Init; + state->down_key_was_released = 1; + break; + case Terminate: + if (state->info->score > state->info->high_score) { + state->info->high_score = state->info->score; + save_high_score(state->info->high_score); + } + terminate_and_free(); + state->state = GameOver; + break; + case Left: + state->state = Moving; + state->moving_type = LeftDown; + break; + case Right: + state->state = Moving; + state->moving_type = RightDown; + break; + case Action: + state->state = Moving; + state->moving_type = Rotate; + break; + case Down: + if (state->down_key_was_released) { + state->state = Moving; + state->moving_type = ToDown; + state->down_key_was_released = 0; + } + break; + case Up: + state->down_key_was_released = 1; + break; + case Pause: + if (!state->info->pause) { + state->pause_start_time = get_current_time_ms(); + } else { + long long pause_duration = get_current_time_ms() - state->pause_start_time; + state->last_move_time += pause_duration; + // Корректируем attach_start_time если мы в Attaching + state->attach_start_time += (state->state == Attaching) * pause_duration; + } + state->info->pause = !state->info->pause; + break; + default: + break; + } } } GameInfo_t updateCurrentState() { GameState_t* state = get_game_state(); - if (!state->info->pause || state->state == GameOver) { + int should_update = (!state->info->pause || state->state == GameOver); + if (should_update) { switch (state->state) { case Init: do_init(); diff --git a/src/brick_game/tetris/03_automato.c b/src/brick_game/tetris/03_automato.c index a70039b..3352e3e 100644 --- a/src/brick_game/tetris/03_automato.c +++ b/src/brick_game/tetris/03_automato.c @@ -47,6 +47,9 @@ GameState_t* get_game_state(void) { state.info->pause = 0; state.last_move_time = get_current_time_ms(); state.pause_start_time = 0; + state.attach_start_time = 0; + state.attach_completed = 0; + state.down_key_was_released = 1; state.info->high_score = load_high_score(); state.state = GameOver; diff --git a/src/brick_game/tetris/05_spawn.c b/src/brick_game/tetris/05_spawn.c index bd4b74f..941586a 100644 --- a/src/brick_game/tetris/05_spawn.c +++ b/src/brick_game/tetris/05_spawn.c @@ -13,19 +13,25 @@ void generate_next_figure(void) { state->next.sprite = rand() % FIGURE_COUNT; state->next.rotation = 0; const int (*shape)[4] = get_figure_shape(state->next.sprite, 0); - for (int i = 0; i < 4; ++i) - for (int j = 0; j < 4; ++j) + for (int i = 0; i < 4; ++i) { + for (int j = 0; j < 4; ++j) { state->next.mtrx[i][j] = shape[i][j]; + } + } } void do_spawn(void) { + GameState_t* state = get_game_state(); + set_current_figure_from_next(); generate_next_figure(); - if (check_collision()) { - get_game_state()->state = GameOver; - return; // TODO + int has_collision = check_collision(); + + if (has_collision) { + state->state = GameOver; + } else { + state->state = Move; + state->down_key_was_released = 1; } - - get_game_state()->state = Move; -} \ No newline at end of file +} diff --git a/src/brick_game/tetris/06_move.c b/src/brick_game/tetris/06_move.c index 8ba3880..97461ee 100644 --- a/src/brick_game/tetris/06_move.c +++ b/src/brick_game/tetris/06_move.c @@ -2,14 +2,16 @@ int get_milliseconds_to_wait(void) { GameState_t* state = get_game_state(); + int result = 0; if (state->moving_type == ToDown) { - return 30; + result = 30; + } else { + int base_delay = 1100 - (state->info->speed * 100); + result = (base_delay > 100) ? base_delay : 100; } - // Скорость от 1 до 10: 1000ms -> 100ms - int base_delay = 1100 - (state->info->speed * 100); - return base_delay > 100 ? base_delay : 100; + return result; } void do_move(void) { @@ -18,15 +20,17 @@ void do_move(void) { long long current_time = get_current_time_ms(); int ms_to_wait = get_milliseconds_to_wait(); - if (current_time - state->last_move_time < ms_to_wait) { - return; - } + int should_move = (current_time - state->last_move_time >= ms_to_wait); - state->last_move_time = current_time; - - state->curr.y++; - if (check_collision()) { - state->curr.y--; - state->state = Attaching; + if (should_move) { + state->last_move_time = current_time; + + state->curr.y++; + int has_collision = check_collision(); + + if (has_collision) { + state->curr.y--; + state->state = Attaching; + } } } diff --git a/src/brick_game/tetris/08_attaching.c b/src/brick_game/tetris/08_attaching.c index 086b02d..5843883 100644 --- a/src/brick_game/tetris/08_attaching.c +++ b/src/brick_game/tetris/08_attaching.c @@ -2,35 +2,60 @@ void do_attaching(void) { GameState_t* state = get_game_state(); - place_figure(); - clear_lines(); - if (is_game_over()) { - state->state = GameOver; - } else { - state->state = Spawn; + long long current_time = get_current_time_ms(); + + // Если только что вошли в Attaching - размещаем фигуру и запускаем таймер + if (!state->attach_completed) { + // Первый вход в Attaching + if (state->attach_start_time == 0) { + place_figure(); + clear_lines(); + state->attach_start_time = current_time; + state->attach_completed = 0; + } + + // Проверяем, прошло ли 350мс + if (current_time - state->attach_start_time >= 350) { + state->attach_completed = 1; + state->attach_start_time = 0; // Сбрасываем таймер + + // Проверяем game over и переходим + int game_over = is_game_over(); + + if (game_over) { + state->state = GameOver; + } else { + state->state = Spawn; + } + + state->attach_completed = 0; // Сбрасываем флаг для следующего attach + } + // Иначе остаёмся в Attaching и ждём } } int check_collision() { GameState_t* state = get_game_state(); Figure_t* fig = &state->curr; + int collision_detected = 0; - for (int i = 0; i < 4; ++i) { - for (int j = 0; j < 4; ++j) { + for (int i = 0; i < 4 && !collision_detected; ++i) { + for (int j = 0; j < 4 && !collision_detected; ++j) { if (fig->mtrx[i][j]) { int x = fig->x + j; int y = fig->y + i; - if (x < 0 || x >= FIELD_WIDTH || y >= FIELD_HEIGHT) { - return 1; // TODO - } - if (y >= 0 && state->field[y][x]) { - return 1; // TODO + int out_of_bounds = (x < 0 || x >= FIELD_WIDTH || y >= FIELD_HEIGHT); + int hits_placed_block = (y >= 0 && state->field[y][x]); + + if (out_of_bounds || hits_placed_block) { + collision_detected = 1; } } } } - return 0; + + return collision_detected; } void place_figure() { @@ -43,7 +68,7 @@ void place_figure() { int x = fig->x + j; int y = fig->y + i; if (y >= 0 && y < FIELD_HEIGHT && x >= 0 && x < FIELD_WIDTH) { - state->field[y][x] = 2; // закреплённая фигура + state->field[y][x] = 2; } } } @@ -68,31 +93,36 @@ void clear_lines() { for (int i = FIELD_HEIGHT - 1; i >= 0; --i) { int full = 1; + for (int j = 0; j < FIELD_WIDTH; ++j) { if (state->field[i][j] != 2) { full = 0; - break; // TODO } } + if (full) { shift_lines_down(i); lines_cleared++; - i++; // Check the same row again after shifting + i++; } } if (lines_cleared > 0) { int points[] = {0, 100, 300, 700, 1500}; state->info->score += points[lines_cleared]; + if (state->info->score > state->info->high_score) { state->info->high_score = state->info->score; } + int new_level = (state->info->score / 600) + 1; - if (new_level > 10) new_level = 10; + if (new_level > 10) { + new_level = 10; + } if (new_level > state->info->level) { state->info->level = new_level; state->info->speed = new_level * 3; } } -} \ No newline at end of file +} diff --git a/src/brick_game/tetris/09_gameover.c b/src/brick_game/tetris/09_gameover.c index ba34b03..0122634 100644 --- a/src/brick_game/tetris/09_gameover.c +++ b/src/brick_game/tetris/09_gameover.c @@ -9,19 +9,22 @@ void do_gameover(void) { } const int (*shape)[4] = empty_fig(); - for (int i = 0; i < 4; ++i) - for (int j = 0; j < 4; ++j) + for (int i = 0; i < 4; ++i) { + for (int j = 0; j < 4; ++j) { state->next.mtrx[i][j] = shape[i][j]; - + } + } } int is_game_over() { GameState_t* state = get_game_state(); + int game_over = 0; + for (int j = 0; j < FIELD_WIDTH; ++j) { if (state->field[0][j] || state->field[1][j]) { - return 1; // TODO + game_over = 1; } } - return 0; -} \ No newline at end of file + return game_over; +} diff --git a/src/gui/cli/main.c b/src/gui/cli/main.c index bfa3d8b..5afab19 100644 --- a/src/gui/cli/main.c +++ b/src/gui/cli/main.c @@ -2,7 +2,7 @@ #include #include #include -#include "../../brick_game/tetris/00_tetris.h" // Только этот хедер! +#include "../../brick_game/tetris/00_tetris.h" void display_game(GameInfo_t game_state); @@ -20,11 +20,12 @@ int main() { refresh(); int ch = 0; - while (1) { + int started = 0; + while (!started) { ch = getch(); if (ch == 'f' || ch == 'F') { userInput(Start, false); - break; + started = 1; } } @@ -39,39 +40,30 @@ int main() { ch = getch(); action_valid = false; - switch (ch) { - case 'q': - userInput(Terminate, false); // Это освободит память через бэкенд - running = false; - break; - case 'r': case ' ': - current_action = Action; - action_valid = true; - break; - case KEY_LEFT: - current_action = Left; - action_valid = true; - break; - case KEY_RIGHT: - current_action = Right; - action_valid = true; - break; - case KEY_DOWN: - current_action = Down; - action_valid = true; - break; - case KEY_UP: - current_action = Up; - action_valid = true; - break; - case 's': case 'S': - current_action = Start; - action_valid = true; - break; - case 'p': case 'P': - current_action = Pause; - action_valid = true; - break; + if (ch == 'q') { + userInput(Terminate, false); + running = false; + } else if (ch == 'r' || ch == ' ') { + current_action = Action; + action_valid = true; + } else if (ch == KEY_LEFT) { + current_action = Left; + action_valid = true; + } else if (ch == KEY_RIGHT) { + current_action = Right; + action_valid = true; + } else if (ch == KEY_DOWN) { + current_action = Down; + action_valid = true; + } else if (ch == KEY_UP) { + current_action = Up; + action_valid = true; + } else if (ch == 's' || ch == 'S') { + current_action = Start; + action_valid = true; + } else if (ch == 'p' || ch == 'P') { + current_action = Pause; + action_valid = true; } if (action_valid) {