#include "../drivers/vga.h" #include "../syscall.h" #include "sprites.h" #include #include #include "misc_utils.h" #include "pause_effect.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, } GameScreen; #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, } SnakeDirection; #define WALKING_ANIM_TIME_SLOW 500 #define HATCHING_ANIM_TIME_SLOW 200 #define WALKING_ANIM_TIME_FAST 200 #define HATCHING_ANIM_TIME_FAST 80 struct Snake { GameScreen 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; tile_t world[WORLD_WIDTH][WORLD_HEIGHT]; uint8_t ghost_apples[WORLD_WIDTH][WORLD_HEIGHT]; ivec2 snake_head; ivec2 snake_pupa; bool is_time_sped_up; SnakeDirection cur_snake_direction; SnakeDirection 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 is_dead state, where * nothing moves. * If we are about to hit ourselves, we don't even move, * we immediately draw one small splash and enter is_dead state. * There is no winning state, you just raise your score until you are dead. */ bool is_dying; bool is_dead; int score; } snake = {1}; void init_snake() { snake.game_screen = game_screen_pause; snake.have_game = false; snake.selected_map_index = 0; snake.gamemode_apples_count = SNAKE_GAMEMODE_DEFAULT_APPLES; /* snake_head and snake_pupa and snake.world * and other gameplay-related variables will be initialized during * map loading */ } 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; snake.new_snake_direction = snake.cur_snake_direction = snake_direction_left; snake.is_dying = false; snake.is_dead = false; snake.score = 0; } uint32_t time_ms(void) { return (uint32_t)syscall(SYS_time_ms, 0); } 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; } } 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 = 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_frame() { clear_frame(0); draw_game_world(); 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"); } StringBuilder_append_cstr(&hud, "PRESS 1 FOR NEW GAME"); if (snake.have_game) { StringBuilder_append_cstr(&hud, "PRESS 2 TO CONTINUE"); } StringBuilder_append_cstr(&hud, "PRESS Q TO QUIT"); } 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 ON MAP\nCURRENTLY SELECTED:\n"); StringBuilder_append_u32(&hud, snake.gamemode_apples_count); } draw_hud(StringBuilder_get_cstr(&hud)); } void wait_until(uint32_t deadline_ms) { while ((int32_t)(time_ms() - deadline_ms) < 0) { syscall(SYS_halt, 0); } } int main(void) { init_snake(); syscall(SYS_switch_to_graphics, 0); snake.selected_map_index = 3; start_snake_game(); uint32_t next_frame_ms = time_ms(); while (1) { // while (true) { // int ch = syscall(SYS_getc, 0); // if (ch < 0) // break; // uint8_t keycode = (uint8_t)ch; // // } draw_frame(); syscall(SYS_swap_frame, (uintptr_t)frame); next_frame_ms += 500; wait_until(next_frame_ms); } }