793 lines
28 KiB
C
793 lines
28 KiB
C
#include "../drivers/vga.h"
|
|
#include "../syscall.h"
|
|
|
|
#include "sprites.h"
|
|
#include <stdbool.h>
|
|
#include <stdint.h>
|
|
#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_VERY_SLOW 600
|
|
#define HATCHING_ANIM_TIME_VERY_SLOW 400
|
|
#define WALKING_ANIM_TIME_SLOW 450
|
|
#define HATCHING_ANIM_TIME_SLOW 150
|
|
#define WALKING_ANIM_TIME_FAST 200
|
|
#define HATCHING_ANIM_TIME_FAST 40
|
|
|
|
#define WEIRD_SOUND 400
|
|
|
|
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;
|
|
bool is_shift_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
|
|
int animation_speed_boost;
|
|
} snake = {1};
|
|
|
|
void init_snake() {
|
|
snake.is_space_pressed = false;
|
|
snake.is_shift_pressed = false;
|
|
snake.game_screen = game_screen_pause;
|
|
snake.have_game = false;
|
|
snake.selected_map_index = 6;
|
|
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;
|
|
snake.snake_head = (ivec2){SNAKE_START_HEAD_X, SNAKE_START_Y};
|
|
snake.snake_tail = (ivec2){SNAKE_START_PUPA_X, SNAKE_START_Y};
|
|
for (int app = 0; app < snake.gamemode_apples_count; app++) {
|
|
place_random_apple();
|
|
}
|
|
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.animation_speed_boost = 0;
|
|
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_WIDTH * 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_straight_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);
|
|
}
|
|
|
|
bool is_tile_pupa(tile_t tile) {
|
|
if (tile == tile_pupa_0_r || tile == tile_pupa_0_l ||
|
|
tile == tile_pupa_0_b || tile == tile_pupa_0_t) {
|
|
return true;
|
|
}
|
|
return 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() {
|
|
for (int tx = 0; tx < WORLD_WIDTH; tx++) {
|
|
for (int ty = 0; ty <= WORLD_HEIGHT; ty++) {
|
|
int iy = ty < WORLD_HEIGHT ? ty : 0;
|
|
tile_t tt = correct_game_tile(snake.world[tx][iy]);
|
|
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) {
|
|
if (snake.waiting_reason == waiting_reason_postmortum) {
|
|
StringBuilder_append_cstr(&hud, "YOU DIED ");
|
|
} else if (snake.is_dying) {
|
|
StringBuilder_append_cstr(&hud, "LOL ");
|
|
}
|
|
// StringBuilder_append_u32(&hud, snake.animation_speed_boost);
|
|
// StringBuilder_append_cstr(&hud, " ");
|
|
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);
|
|
if (snake.have_game) {
|
|
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_SHIFT) {
|
|
snake.is_shift_pressed = true;
|
|
} else if (keycode == (KEYCODE_SHIFT | 0x80)) {
|
|
snake.is_shift_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.animation_speed_boost == 1) {
|
|
if (snake.waiting_reason == waiting_reason_step) {
|
|
target_required_time = WALKING_ANIM_TIME_FAST;
|
|
} else {
|
|
target_required_time = HATCHING_ANIM_TIME_FAST;
|
|
}
|
|
} else if (snake.animation_speed_boost == 0) {
|
|
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_VERY_SLOW;
|
|
} else {
|
|
target_required_time = HATCHING_ANIM_TIME_VERY_SLOW;
|
|
}
|
|
}
|
|
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;
|
|
snake.score += 5;
|
|
} 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_straight_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_tile_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.snake_tail.x == snake.snake_head.x &&
|
|
snake.snake_tail.y == snake.snake_head.y
|
|
) {
|
|
snake.waiting_reason = waiting_reason_postmortum;
|
|
return;
|
|
}
|
|
if (snake.ghost_apples[penultimate_pos.x][penultimate_pos.y]) {
|
|
snake.ghost_apples[penultimate_pos.x][penultimate_pos.y] = 0;
|
|
|
|
syscall(SYS_set_beep, WEIRD_SOUND);
|
|
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, WEIRD_SOUND);
|
|
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_straight_snake_tile(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_zero_pupa;
|
|
} 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.animation_speed_boost = 0;
|
|
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.animation_speed_boost = 1;
|
|
} else if (snake.is_shift_pressed) {
|
|
snake.animation_speed_boost = -1;
|
|
}
|
|
/* 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;
|
|
}
|