#include "../drivers/vga.h" #include "../syscall.h" #include "sprites.h" #include #include #include "misc_utils.h" #include "keymapping.h" #include "pause_effect.h" #include "random.h" #include "sprites.h" #include "sprite_data.h" #include "maps.h" #include "map_data.h" #define FRAME_SIZE (VGA_GRAPHICS_WIDTH * VGA_GRAPHICS_HEIGHT) #define HUD_OFFSET 3 #define GLYPH_SPACING 3 #define FONT_SPACE_INDEX 0 #define FONT_DIGIT_BASE 1 #define FONT_LETTER_BASE (FONT_DIGIT_BASE + 10) uint8_t frame[FRAME_SIZE] = { 1 }; #define SNAKE_GAMEMODE_MAX_APPLES 10 #define SNAKE_GAMEMODE_DEFAULT_APPLES 5 typedef enum { game_screen_gaming, game_screen_pause, game_screen_select_map, game_screen_select_apple_count, } game_screen_t; #define SNAKE_START_Y 2 #define SNAKE_START_HEAD_X 4 #define SNAKE_START_PUPA_X 6 typedef enum { snake_direction_left = 0, snake_direction_right = 1, snake_direction_top = 2, snake_direction_bottom = 3, } snake_direction_t; #define WALKING_ANIM_TIME_SLOW 500 #define HATCHING_ANIM_TIME_SLOW 200 #define WALKING_ANIM_TIME_FAST 200 #define HATCHING_ANIM_TIME_FAST 80 typedef enum { waiting_reason_step, waiting_reason_p1, waiting_reason_p2, waiting_reason_cross, /* we go from cross pupa state to m1 pupa state through 'step', * not through a separate animation target */ waiting_reason_m1, waiting_reason_zero_pupa, waiting_reason_postmortum, } waiting_reason_t; struct Snake { bool is_space_pressed; game_screen_t game_screen; bool have_game; /* from 0 to PLAYABLE_MAPS_COUNT - 1 */ int selected_map_index; /* from 1 to SNAKE_GAMEMODE_MAX_APPLES, default is SNAKE_GAMEMODE_DEFAULT_APPLES */ int gamemode_apples_count; RandomEngine r_eng; tile_t world[WORLD_WIDTH][WORLD_HEIGHT]; uint8_t ghost_apples[WORLD_WIDTH][WORLD_HEIGHT]; ivec2 snake_head; ivec2 snake_tail; snake_direction_t cur_snake_direction; snake_direction_t new_snake_direction; /* If we are about to make a step into a wall, we start dying, * we enter into dying mode, where we keep making steps, but the head * just disappears after walking, moving back. * When the pupa disappears this way, we enter 'awaiting postmortum' state, where * nothing moves. * If we are about to hit ourselves, we don't even move, * we immediately draw one small splash and enter 'awaiting postmortum' state. * There is no winning state, you just raise your score until you are dead. */ bool is_dying; int score; /* from -1 to PUDDLE_SPRITES - 1 (where -1 means there is no puddle) */ int puddle_sprite; ivec2 puddle_center; /* This stuff regulates game flow and animation progress */ waiting_reason_t waiting_reason; uint32_t waiting_time; // Measures how much time we already accumulated bool is_time_sped_up; } snake = {1}; void init_snake() { snake.is_space_pressed = false; snake.game_screen = game_screen_pause; snake.have_game = false; snake.selected_map_index = 0; snake.gamemode_apples_count = SNAKE_GAMEMODE_DEFAULT_APPLES; snake.r_eng = (RandomEngine){.state = time_ms()}; /* snake_head and snake_pupa and snake.world * and other gameplay-related variables will be initialized during * map loading */ } bool place_random_apple() { for (int t = 0; t < 50; t++) { uint32_t i = RandomEngine_rnd_interval(&snake.r_eng, 0, WORLD_WIDTH * WORLD_HEIGHT); if ((&snake.world[0][0])[i] == tile_empty) { (&snake.world[0][0])[i] = tile_apple; return true; } } /* I had enough */ for (uint32_t i = 0; i < WORLD_WIDTH * WORLD_HEIGHT; i++) { if ((&snake.world[0][0])[i] == tile_empty) { (&snake.world[0][0])[i] = tile_apple; return true; } } return false; } void start_snake_game() { snake.have_game = true; snake.game_screen = game_screen_gaming; const MapConfig* map_config = &map_list.maps[snake.selected_map_index]; memcpy(&snake.world[0][0], &map_config->map[0][0], sizeof(tile_t) * WORLD_WIDTH * WORLD_HEIGHT); memset(&snake.ghost_apples, 0, sizeof(snake.ghost_apples)); for (int tx = SNAKE_START_HEAD_X; tx < SNAKE_START_PUPA_X; tx++) { snake.world[tx][SNAKE_START_Y] = tile_snake_l; } snake.world[SNAKE_START_PUPA_X][SNAKE_START_Y] = tile_pupa_0_l; for (int app = 0; app < snake.gamemode_apples_count; app++) { } snake.new_snake_direction = snake.cur_snake_direction = snake_direction_left; snake.is_dying = false; snake.score = 0; snake.waiting_reason = waiting_reason_step; snake.waiting_time = 0; snake.is_time_sped_up = false; snake.puddle_sprite = -1; } void set_tile(ivec2 pos, tile_t val) { check(0 <= pos.x && pos.x < WORLD_WIDTH && 0 <= pos.y && pos.y < WORLD_HEIGHT); snake.world[pos.x][pos.y] = val; } tile_t get_tile(ivec2 pos) { check(0 <= pos.x && pos.x < WORLD_WIDTH && 0 <= pos.y && pos.y < WORLD_HEIGHT); return snake.world[pos.x][pos.y]; } snake_direction_t get_opposite_direction(snake_direction_t x) { if (x == snake_direction_left) { return snake_direction_right; } else if (x == snake_direction_right) { return snake_direction_left; } else if (x == snake_direction_top) { return snake_direction_bottom; } else { return snake_direction_top; } } ivec2 world_walk_direction(ivec2 v, snake_direction_t dir) { if (dir == snake_direction_left) { return (ivec2){v.x == 0 ? WORLD_WIDTH - 1 : v.x - 1, v.y}; } else if (dir == snake_direction_right) { return (ivec2){v.x + 1 == WORLD_WIDTH ? 0 : v.x + 1, v.y}; } else if (dir == snake_direction_top) { return (ivec2){v.x, v.y == 0 ? WORLD_HEIGHT - 1 : v.y - 1}; } else { return (ivec2){v.x, v.y + 1 == WORLD_HEIGHT ? 0 : v.y + 1}; } } ivec2 get_puddle_center_for_contact(ivec2 head, snake_direction_t dir) { ivec2 tile_pos = (ivec2){TILE_WIDTH * head.x, TILE_HEIGHT * head.y}; if (dir == snake_direction_left || dir == snake_direction_right) { tile_pos.y += TILE_WIDTH / 2; } else if (dir == snake_direction_bottom) { tile_pos.y += TILE_WIDTH; } if (dir == snake_direction_top || dir == snake_direction_bottom) { tile_pos.x += TILE_WIDTH / 2; } else if (dir == snake_direction_right) { tile_pos.x += TILE_WIDTH; } return tile_pos; } /* takes snake tile, returns cardinal direction of snake origin */ snake_direction_t get_snake_origin_dir(tile_t tile) { switch (tile) { case tile_snake_l: return snake_direction_right; case tile_snake_r: return snake_direction_left; case tile_snake_t: return snake_direction_bottom; case tile_snake_b: return snake_direction_top; case tile_snake_bl: return snake_direction_bottom; case tile_snake_br: return snake_direction_bottom; case tile_snake_lb: return snake_direction_left; case tile_snake_rb: return snake_direction_right; case tile_snake_tl: return snake_direction_top; case tile_snake_tr: return snake_direction_top; case tile_snake_lt: return snake_direction_left; case tile_snake_rt: return snake_direction_right; default: check(false); } } tile_t construct_snake_tile(snake_direction_t from, snake_direction_t to) { check(from != to); if (from == snake_direction_left) { if (to == snake_direction_right) return tile_snake_r; if (to == snake_direction_top) return tile_snake_lt; /* to bottom */ return tile_snake_lb; } if (from == snake_direction_right) { if (to == snake_direction_left) return tile_snake_l; if (to == snake_direction_top) return tile_snake_rt; /* to bottom */ return tile_snake_rb; } if (from == snake_direction_top) { if (to == snake_direction_left) return tile_snake_tl; if (to == snake_direction_right) return tile_snake_tr; /* to bottom */ return tile_snake_b; } if (from == snake_direction_bottom) { if (to == snake_direction_left) return tile_snake_bl; if (to == snake_direction_right) return tile_snake_br; /* to top */ return tile_snake_t; } check(false); } tile_t construct_head_snake_tile(snake_direction_t to) { return construct_snake_tile(get_opposite_direction(to), to); } tile_t construct_zero_pupa(snake_direction_t to) { if (to == snake_direction_left) return tile_pupa_0_l; if (to == snake_direction_right) return tile_pupa_0_r; if (to == snake_direction_top) return tile_pupa_0_t; if (to == snake_direction_bottom) return tile_pupa_0_b; check(false); } /* takes snake tile, returns it's target direction*/ snake_direction_t get_snake_destination(tile_t tile) { switch (tile) { case tile_snake_l: return snake_direction_left; case tile_snake_r: return snake_direction_right; case tile_snake_t: return snake_direction_top; case tile_snake_b: return snake_direction_bottom; case tile_snake_bl: return snake_direction_left; case tile_snake_br: return snake_direction_right; case tile_snake_lb: return snake_direction_bottom; case tile_snake_rb: return snake_direction_bottom; case tile_snake_tl: return snake_direction_left; case tile_snake_tr: return snake_direction_right; case tile_snake_lt: return snake_direction_top; case tile_snake_rt: return snake_direction_top; default: check(false); } } snake_direction_t get_zero_pupa_destination(tile_t tile) { if (tile == tile_pupa_0_r) return snake_direction_right; if (tile == tile_pupa_0_l) return snake_direction_left; if (tile == tile_pupa_0_t) return snake_direction_top; if (tile == tile_pupa_0_b) return snake_direction_bottom; check(false); } /* takes a tile that can possibly become a tail. Either straight snake or pupa */ bool is_tail_pupa(tile_t tile) { if (18 <= tile && tile <= 21) return false; if (tile == tile_pupa_0_r || tile == tile_pupa_0_l || tile == tile_pupa_0_b || tile == tile_pupa_0_t) { return true; } check(false); } void clear_frame(uint8_t color) { for (uint32_t i = 0; i < FRAME_SIZE; i++) { frame[i] = color; } } void put_pixel(int x, int y, uint8_t color) { if (x < 0 || x >= VGA_GRAPHICS_WIDTH || y < 0 || y >= VGA_GRAPHICS_HEIGHT) { return; } frame[y * VGA_GRAPHICS_WIDTH + x] = color; } void draw_sprite( int dst_x, int dst_y, int sprite_width, int sprite_height, const uint8_t* sprite) { for (int x = 0; x < sprite_width; x++) { for (int y = 0; y < sprite_height; y++) { uint8_t color = sprite[x * sprite_height + y]; if (color != TRANSPARENCY_COLOR) { put_pixel(dst_x + x, dst_y + y, color); } } } } bool font_index_for_char(char ch, int *out_index) { if (ch == ' ') { *out_index = FONT_SPACE_INDEX; return true; } if (ch >= '0' && ch <= '9') { *out_index = FONT_DIGIT_BASE + (ch - '0'); return true; } if (ch >= 'A' && ch <= 'Z') { *out_index = FONT_LETTER_BASE + (ch - 'A'); return true; } return false; } void draw_hud(const char *text) { // int lines_count = 1; int y0 = HUD_OFFSET; for (const char* line_start = text; *line_start;) { int glyph_count = 0; const char* p = line_start; for (; *p != '\n' && *p != 0; p++) { glyph_count++; } if (glyph_count > 0) { const int text_width = glyph_count * FONT_WIDTH + (glyph_count - 1) * GLYPH_SPACING; int x0 = VGA_GRAPHICS_WIDTH - HUD_OFFSET - text_width; for (const char* q = line_start; q != p; q++) { int glyph_index; check(font_index_for_char(*q, &glyph_index)); draw_sprite(x0, y0, FONT_WIDTH, FONT_HEIGHT, &sprite_data.font[glyph_index].tex[0][0]); x0 += FONT_WIDTH + GLYPH_SPACING; } } if (*p == 0) { break; } line_start = p + 1; y0 += FONT_HEIGHT + GLYPH_SPACING; } } /* You see, we never store pupa hatching stage in world array. * Instead, we store pupa 0 in world matrix and deduce the correct tile * in draw_game_world using snake.waiting_reason */ tile_t correct_game_tile(tile_t x) { /* waiting for p2 => we are in p1 */ if (snake.waiting_reason == waiting_reason_p2) { if (x == tile_pupa_0_l) return tile_pupa_p1_l; if (x == tile_pupa_0_r) return tile_pupa_p1_r; if (x == tile_pupa_0_t) return tile_pupa_p1_t; if (x == tile_pupa_0_b) return tile_pupa_p1_b; } /* waiting for cross => we are in p2 */ if (snake.waiting_reason == waiting_reason_cross) { if (x == tile_pupa_0_l) return tile_pupa_p2_l; if (x == tile_pupa_0_r) return tile_pupa_p2_r; if (x == tile_pupa_0_t) return tile_pupa_p2_t; if (x == tile_pupa_0_b) return tile_pupa_p2_b; } /* waiting for m1 => we are in m2 */ if (snake.waiting_reason == waiting_reason_m1) { if (x == tile_pupa_0_l) return tile_pupa_m2_l; if (x == tile_pupa_0_r) return tile_pupa_m2_r; if (x == tile_pupa_0_t) return tile_pupa_m2_t; if (x == tile_pupa_0_b) return tile_pupa_m2_b; } /* waiting for zero_pupa => we are in m1 */ if (snake.waiting_reason == waiting_reason_zero_pupa) { if (x == tile_pupa_0_l) return tile_pupa_m1_l; if (x == tile_pupa_0_r) return tile_pupa_m1_r; if (x == tile_pupa_0_t) return tile_pupa_m1_t; if (x == tile_pupa_0_b) return tile_pupa_m1_b; } /* When waiting postmortum, step or p1, pupa looks exactly the same * (default is zero pupa) */ return x; } void draw_game_world() { if (!snake.have_game) return; for (int tx = 0; tx < WORLD_WIDTH; tx++) { for (int ty = 0; ty < WORLD_HEIGHT; ty++) { tile_t tt = correct_game_tile(snake.world[tx][ty]); if (tt == tile_empty) continue; check((uint8_t)tt < TILE_SPRITES); draw_sprite(TILE_WIDTH * tx, TILE_WIDTH * ty - TILE_HEIGHT_OFFSET, TILE_WIDTH, TILE_HEIGHT, &sprite_data.tile[tt].tex[0][0]); } } if (snake.game_screen != game_screen_gaming) { for (uint32_t pix = 0; pix < FRAME_SIZE; pix++) { frame[pix] = vga_to_gray(frame[pix]); } } } void draw_game_text() { StringBuilder hud = { 0 }; if (snake.game_screen == game_screen_gaming) { StringBuilder_append_u32(&hud, snake.score); } else if (snake.game_screen == game_screen_pause) { if (snake.have_game) { StringBuilder_append_cstr(&hud, "PAUSED\n"); } StringBuilder_append_cstr(&hud, "PRESS 1 FOR NEW GAME\n"); if (snake.have_game) { StringBuilder_append_cstr(&hud, "PRESS ESC TO CONTINUE\n"); } StringBuilder_append_cstr(&hud, "PRESS Q TO QUIT\n"); } else if (snake.game_screen == game_screen_select_map) { StringBuilder_append_cstr(&hud, "SELECT LEVEL\nCURRENTLY SELECTED\n"); check(snake.selected_map_index < PLAYABLE_MAPS_COUNT); StringBuilder_append_cstr(&hud, map_list.maps[snake.selected_map_index].name); } else if (snake.game_screen == game_screen_select_apple_count) { StringBuilder_append_cstr(&hud, "SELECT DIFFICULTY\n" "HOW MANY APPLES\nCURRENTLY SELECTED\n"); StringBuilder_append_u32(&hud, snake.gamemode_apples_count); } draw_hud(StringBuilder_get_cstr(&hud)); } void draw_game_puddles() { if (snake.puddle_sprite > 0) { const PuddleSprite* sprite = &sprite_data.puddle[snake.puddle_sprite]; draw_sprite( snake.puddle_center.x - PUDDLE_WIDTH / 2, snake.puddle_center.y - PUDDLE_WIDTH / 2, PUDDLE_WIDTH, PUDDLE_WIDTH, (&sprite->tex[0][0])); } } void draw_frame() { clear_frame(226); draw_game_puddles(); draw_game_world(); draw_game_text(); } /* Called when game_screen is game_screen_gaming */ void handle_gaming_keycode(uint8_t keycode) { if (keycode == KEYCODE_ESCAPE) { snake.game_screen = game_screen_pause; } if (!snake.is_dying) { if (is_keycode_for_press_left(keycode) && snake.cur_snake_direction != snake_direction_right) { snake.new_snake_direction = snake_direction_left; } else if (is_keycode_for_press_right(keycode) && snake.cur_snake_direction != snake_direction_left) { snake.new_snake_direction = snake_direction_right; } else if (is_keycode_for_press_up(keycode) && snake.cur_snake_direction != snake_direction_bottom) { snake.new_snake_direction = snake_direction_top; } else if (is_keycode_for_press_down(keycode) && snake.cur_snake_direction != snake_direction_top) { snake.new_snake_direction = snake_direction_bottom; } } } /* return 1 if we are quitting */ int handle_incoming_keycode_after_halt(uint8_t keycode) { if (keycode == KEYCODE_SPACE) { snake.is_space_pressed = true; } else if (keycode == (KEYCODE_SPACE | 0x80)) { snake.is_space_pressed = false; } if (keycode == KEYCODE_Q && snake.game_screen != game_screen_gaming) { return 1; } if (snake.game_screen == game_screen_gaming) { handle_gaming_keycode(keycode); } else if (snake.game_screen == game_screen_pause) { if (keycode == KEYCODE_1 || (keycode == KEYCODE_ENTER && !snake.have_game)) { snake.game_screen = game_screen_select_map; } else if (keycode == KEYCODE_ESCAPE && snake.have_game) { snake.game_screen = game_screen_gaming; } } else if (snake.game_screen == game_screen_select_map) { if (keycode == KEYCODE_ESCAPE) { snake.game_screen = game_screen_pause; } else if (is_keycode_for_press_left(keycode)) { snake.selected_map_index--; if (snake.selected_map_index < 0) snake.selected_map_index = PLAYABLE_MAPS_COUNT - 1; } else if (is_keycode_for_press_right(keycode)) { snake.selected_map_index++; if (snake.selected_map_index >= PLAYABLE_MAPS_COUNT) snake.selected_map_index = 0; } else if (keycode == KEYCODE_ENTER) { snake.game_screen = game_screen_select_apple_count; } } else if (snake.game_screen == game_screen_select_apple_count) { if (keycode == KEYCODE_ESCAPE) { snake.game_screen = game_screen_select_map; } else if (is_keycode_for_press_left(keycode)) { if (snake.gamemode_apples_count > 1) snake.gamemode_apples_count--; } else if (is_keycode_for_press_right(keycode)) { if (snake.gamemode_apples_count < SNAKE_GAMEMODE_MAX_APPLES) snake.gamemode_apples_count++; } else if (keycode == KEYCODE_ENTER) { start_snake_game(); } } return 0; } /* Time flows only in gaming mode. This function can call frame drawing */ void handle_time_difference(uint32_t time_diff) { if (snake.game_screen != game_screen_gaming) return; if (snake.waiting_reason == waiting_reason_postmortum) return; snake.waiting_time += time_diff; uint32_t target_required_time; if (snake.is_time_sped_up) { if (snake.waiting_reason == waiting_reason_step) { target_required_time = WALKING_ANIM_TIME_SLOW; } else { target_required_time = HATCHING_ANIM_TIME_SLOW; } } else { if (snake.waiting_reason == waiting_reason_step) { target_required_time = WALKING_ANIM_TIME_FAST; } else { target_required_time = HATCHING_ANIM_TIME_FAST; } } if (snake.waiting_time > target_required_time) { snake.waiting_time -= target_required_time; if (snake.waiting_reason == waiting_reason_step) { ivec2 tile_pos_ahead = world_walk_direction( snake.snake_head, snake.new_snake_direction); tile_t tile_ahead = get_tile(tile_pos_ahead); /* Processing collisions */ if (tile_ahead == tile_apple) { snake.ghost_apples[tile_pos_ahead.x][tile_pos_ahead.y] = 1; } else if (18 <= tile_ahead && tile_ahead <= 49) { snake.waiting_reason = waiting_reason_postmortum; snake.puddle_center = get_puddle_center_for_contact( snake.snake_head, snake.new_snake_direction); snake.puddle_sprite = 0; return; } else if (2 <= tile_ahead && tile_ahead <= 17) { snake.is_dying = true; snake.puddle_center = get_puddle_center_for_contact( snake.snake_head, snake.new_snake_direction); if (snake.puddle_sprite + 1 < PUDDLE_SPRITES) { snake.puddle_sprite++; } } /* normal stuff */ /* Step 1 */ ivec2 old_head_pos = snake.snake_head; if (tile_ahead == tile_empty || tile_ahead == tile_apple) { set_tile(tile_pos_ahead, construct_head_snake_tile(snake.new_snake_direction)); snake.snake_head = tile_pos_ahead; } /* Step 2 */ set_tile(old_head_pos, construct_snake_tile( get_opposite_direction(snake.cur_snake_direction), snake.new_snake_direction)); snake.cur_snake_direction = snake.new_snake_direction; /* Steps 3... */ if (is_tail_pupa(get_tile(snake.snake_tail))) { /* Step 3a */ snake_direction_t pupa_dir = get_zero_pupa_destination(get_tile(snake.snake_tail)); ivec2 penultimate_pos = world_walk_direction(snake.snake_tail, pupa_dir); set_tile(snake.snake_tail, tile_empty); /* Step 4a */ snake_direction_t penultimate_part_dir = get_snake_destination(get_tile(penultimate_pos)); set_tile(penultimate_pos, construct_zero_pupa(penultimate_part_dir)); snake.snake_tail = penultimate_pos; /* WARNING: we updated snake_tail by moving pupa forward, we * WARNING: should check if we moved onto a ghost apple */ // check, this is important. This is the only place where we move pupa to a new pos // we check for ghost apples. Clear ghost apples and enter // p1-waiting state (instead of remaining in step awaiting state) if (snake.ghost_apples[penultimate_pos.x][penultimate_pos.y]) { snake.ghost_apples[penultimate_pos.x][penultimate_pos.y] = 0; syscall(SYS_set_beep, 5000); snake.waiting_reason = waiting_reason_p1; /* Nothing changed on screen because we need to wait even * for the first stage of hatching. * We could go to p1 immediately, though */ } } else { snake_direction_t tail_dir = get_snake_destination(get_tile(snake.snake_tail)); set_tile(snake.snake_tail, construct_zero_pupa(tail_dir)); syscall(SYS_set_beep, 3000); snake.waiting_reason = waiting_reason_m1; } /* If we went with route 3a, pupa has moved and we definitely can * place an apple. But if it had not and we went through route 3b, * well, okay, we just won't have a free space for apple */ if (tile_ahead == tile_apple) { place_random_apple(); } } /* But there are more animation targets that can finish, other than 'step' */ else if (snake.waiting_reason == waiting_reason_p1) { snake.waiting_reason = waiting_reason_p2; } else if (snake.waiting_reason == waiting_reason_p2) { snake.waiting_reason = waiting_reason_cross; } else if (snake.waiting_reason == waiting_reason_cross) { /* Finished pupa hatching */ snake_direction_t tail_dir = get_zero_pupa_destination(get_tile(snake.snake_tail)); set_tile(snake.snake_tail, construct_zero_pupa(tail_dir)); syscall(SYS_set_beep, 0); snake.waiting_reason = waiting_reason_step; } else if (snake.waiting_reason == waiting_reason_m1) { snake.waiting_reason = waiting_reason_cross; } else if (snake.waiting_reason == waiting_reason_zero_pupa) { /* Finished pupa regrowing */ syscall(SYS_set_beep, 0); snake.waiting_reason = waiting_reason_step; } } } void after_awake_action(uint32_t time_diff) { handle_time_difference(time_diff); snake.is_time_sped_up = false; draw_frame(); syscall(SYS_swap_frame, (uintptr_t)frame); } int main(void) { init_snake(); syscall(SYS_switch_to_graphics, 0); uint32_t prev_time = time_ms(); while (1) { /* Returned from halt, we can enjoy keyboard input and animation progress*/ for (int ch;(ch = syscall(SYS_getc, 0)) >= 0;) { int ret = handle_incoming_keycode_after_halt((uint8_t)ch); if (ret == 1) goto quit; } /* As an additional benefit, we can check if is_space_pressed here */ if (snake.is_space_pressed) { snake.is_time_sped_up = true; } /* snake.is_time_sped_up will be relevant when checking animation end */ uint32_t current_time = time_ms(); after_awake_action(current_time - prev_time); prev_time = current_time; syscall(SYS_halt, 0); } quit: syscall(SYS_switch_to_text, 0); syscall(SYS_puts, (uintptr_t)"Quit from game\n"); return 0; }