Files
ESP32C3-0.42OLED-OS/main/apps/app_game.c
T
2026-09-25 21:14:07 +01:00

154 lines
4.2 KiB
C

#include "apps.h"
#include "os_gfx.h"
#include "os_status.h"
#include <stdio.h>
#include <stdlib.h>
#define GROUND_Y 34
typedef enum {
GAME_STATE_READY,
GAME_STATE_PLAYING,
GAME_STATE_OVER
} game_state_t;
static game_state_t s_game_state = GAME_STATE_READY;
/* Fixed-point 8-bit physics for smooth 60 FPS jump arcs */
#define FP_SHIFT 8
#define TO_FP(val) ((int32_t)((val) << FP_SHIFT))
#define FROM_FP(val) ((int16_t)((val) >> FP_SHIFT))
static int32_t s_player_y_fp = TO_FP(GROUND_Y);
static int32_t s_player_vy_fp = 0;
static bool s_is_jumping = false;
static int32_t s_obs_x_fp = TO_FP(72);
static int16_t s_obs_h = 6;
static uint16_t s_score = 0;
static uint16_t s_high_score = 0;
static void game_reset(void)
{
s_player_y_fp = TO_FP(GROUND_Y);
s_player_vy_fp = 0;
s_is_jumping = false;
s_obs_x_fp = TO_FP(72);
s_obs_h = 5 + (rand() % 4);
s_score = 0;
}
static void game_init(void)
{
s_game_state = GAME_STATE_READY;
s_high_score = 0;
game_reset();
}
static void game_enter(void)
{
s_game_state = GAME_STATE_READY;
game_reset();
}
static void game_update(uint32_t delta_ms)
{
sys_gfx_draw_line(0, GROUND_Y + 1, OLED_WIDTH - 1, GROUND_Y + 1, COLOR_WHITE);
if (s_game_state == GAME_STATE_READY) {
sys_gfx_draw_str_5x7(10, 8, "TINY JUMP", COLOR_WHITE);
sys_gfx_draw_str_3x5(8, 20, "CLICK TO START", COLOR_WHITE);
sys_gfx_draw_action_bar("C:JUMP", "H:HOME");
sys_gfx_fill_rect(8, GROUND_Y - 5, 5, 5, COLOR_WHITE);
return;
}
if (s_game_state == GAME_STATE_PLAYING) {
// 60 FPS Jump Physics
if (s_is_jumping) {
s_player_y_fp += s_player_vy_fp;
s_player_vy_fp += 34; // Gravity per frame
if (s_player_y_fp >= TO_FP(GROUND_Y)) {
s_player_y_fp = TO_FP(GROUND_Y);
s_player_vy_fp = 0;
s_is_jumping = false;
}
}
// 60 FPS Obstacle Movement (~70 px/sec)
s_obs_x_fp -= 300; // ~1.17 pixels per 60fps frame
int16_t obs_x = FROM_FP(s_obs_x_fp);
if (obs_x < -4) {
s_obs_x_fp = TO_FP(72 + (rand() % 16));
obs_x = FROM_FP(s_obs_x_fp);
s_obs_h = 5 + (rand() % 5);
s_score++;
if (s_score > s_high_score) s_high_score = s_score;
}
int16_t player_y = FROM_FP(s_player_y_fp);
// Collision Check (Hitbox: player is 5x5 at x=8..12, obs is 3xH at obs_x..obs_x+2)
if (obs_x <= 13 && (obs_x + 3) >= 8) {
if ((player_y - 1) >= (GROUND_Y - s_obs_h + 1)) {
s_game_state = GAME_STATE_OVER;
sys_gfx_show_toast("GAME OVER", 800);
}
}
// Render Player and Obstacle
sys_gfx_fill_rect(8, player_y - 5, 5, 5, COLOR_WHITE);
sys_gfx_fill_rect(obs_x, GROUND_Y - s_obs_h + 1, 3, s_obs_h, COLOR_WHITE);
char score_str[12];
snprintf(score_str, sizeof(score_str), "SC:%u", s_score);
sys_gfx_draw_str_3x5(45, 1, score_str, COLOR_WHITE);
} else if (s_game_state == GAME_STATE_OVER) {
sys_gfx_draw_str_5x7(12, 6, "GAME OVER", COLOR_WHITE);
char buf[16];
snprintf(buf, sizeof(buf), "SCORE: %u", s_score);
sys_gfx_draw_str_3x5(16, 17, buf, COLOR_WHITE);
snprintf(buf, sizeof(buf), "BEST: %u", s_high_score);
sys_gfx_draw_str_3x5(16, 24, buf, COLOR_WHITE);
sys_gfx_draw_action_bar("C:RESTART", "H:HOME");
}
}
static void game_handle_input(input_action_t action)
{
if (action == INPUT_ACTION_CLICK) {
if (s_game_state == GAME_STATE_READY) {
s_game_state = GAME_STATE_PLAYING;
game_reset();
} else if (s_game_state == GAME_STATE_PLAYING) {
if (!s_is_jumping) {
s_is_jumping = true;
s_player_vy_fp = -740; // Instant jump impulse (-2.9 px/frame)
}
} else if (s_game_state == GAME_STATE_OVER) {
s_game_state = GAME_STATE_PLAYING;
game_reset();
}
}
}
static void game_exit(void)
{
}
const sys_app_t g_app_game = {
.id = "game",
.name = "Tiny Jump",
.init = game_init,
.enter = game_enter,
.update = game_update,
.handle_input = game_handle_input,
.exit = game_exit,
};