263 lines
7.7 KiB
C

#include "../drivers/vga.h"
#include "../syscall.h"
#include "sprites.h"
#include <stdbool.h>
#include <stdint.h>
#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);
}
}