Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -525,6 +525,7 @@ C versions of:
- `platformer`
- `raycaster`
- `wing_commander`
- `frogger`

See [examples/games/README.md](examples/games/README.md) for step-by-step
instructions to run each game embedded in firmware or as an uploadable
Expand Down
12 changes: 10 additions & 2 deletions components/prg32/include/prg32.h
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ extern "C" {

#define PRG32_CART_MAGIC "PRG2"
#define PRG32_CART_ABI_MAJOR 1
#define PRG32_CART_ABI_MINOR 0
#define PRG32_CART_ABI_MINOR 1
#define PRG32_CART_FLAG_AUDIO_BLOCK (1u << 0)
#define PRG32_CART_FLAG_MULTIPLAYER (1u << 1)
#define PRG32_CART_FLAG_ABI_TABLE (1u << 2)
Expand All @@ -103,7 +103,7 @@ extern "C" {
#define PRG32_CART_ARCH_ESP32C6 "esp32c6"
#define PRG32_CART_ARCH_QEMU "qemu"
#define PRG32_CART_LOAD_ADDR 0x40800000u
#define PRG32_CART_MAX_SIZE (32u * 1024u)
#define PRG32_CART_MAX_SIZE (64u * 1024u)
#ifndef CONFIG_PRG32_CART_RAM_KIB
#define CONFIG_PRG32_CART_RAM_KIB 32
#endif
Expand Down Expand Up @@ -274,7 +274,14 @@ const char *prg32_wifi_current_ssid(void);
int prg32_wifi_setup_requested(void);
int prg32_wifi_setup_run(void);
void prg32_scores_api_start(void);
int prg32_score_player_get(char *out_player, size_t max_len);
int prg32_score_player_set(const char *player);
int prg32_score_player_prompt(void);
int prg32_score_submit(const char *game, const char *player, uint32_t score);
int prg32_score_submit_current_player(const char *game, uint32_t score);
int prg32_score_count(const char *game);
int prg32_score_get(const char *game, int index, prg32_score_t *out_score);
int prg32_scoreboard_show(const char *game, const char *title);
int prg32_score_submit_remote(const char *base_url,
const char *game,
const char *player,
Expand Down Expand Up @@ -428,6 +435,7 @@ void prg32_platform_camera_follow(const prg32_platform_actor_t *actor,
int prg32_sprite_hitbox(int ax, int ay, int aw, int ah, int bx, int by, int bw, int bh);
void prg32_sprite_draw_8x8(int x, int y, const uint8_t *bits, uint16_t fg, uint16_t bg);
void prg32_sprite_draw_16x16(int x, int y, const uint16_t *rgb565);
void prg32_sprite_draw_24x24(int x, int y, const uint16_t *rgb565);
uint32_t prg32_sprite_anim_frame(uint32_t now_ms,
uint32_t frame_count,
uint32_t frame_ms);
Expand Down
4 changes: 2 additions & 2 deletions components/prg32/include/prg32_abi_hash.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@
/* Generated by tools/prg32_abi_gen.py; do not edit manually. */

#define PRG32_ABI_MAJOR 1u
#define PRG32_ABI_MINOR 0u
#define PRG32_ABI_HASH 0xb9cadd82u
#define PRG32_ABI_MINOR 2u
#define PRG32_ABI_HASH 0x23fced32u
10 changes: 9 additions & 1 deletion components/prg32/include/prg32_abi_index.h
Original file line number Diff line number Diff line change
Expand Up @@ -116,5 +116,13 @@ enum {
PRG32_ABI_FN_PRG32_SPRITE_ANIM_UPDATE = 110,
PRG32_ABI_FN_PRG32_SPRITE_ANIM_DRAW = 111,
PRG32_ABI_FN_PRG32_SCORE_SUBMIT = 112,
PRG32_ABI_FN_COUNT = 113
PRG32_ABI_FN_PRG32_SPRITE_DRAW_24X24 = 113,
PRG32_ABI_FN_PRG32_SCORE_PLAYER_GET = 114,
PRG32_ABI_FN_PRG32_SCORE_PLAYER_SET = 115,
PRG32_ABI_FN_PRG32_SCORE_PLAYER_PROMPT = 116,
PRG32_ABI_FN_PRG32_SCORE_SUBMIT_CURRENT_PLAYER = 117,
PRG32_ABI_FN_PRG32_SCORE_COUNT = 118,
PRG32_ABI_FN_PRG32_SCORE_GET = 119,
PRG32_ABI_FN_PRG32_SCOREBOARD_SHOW = 120,
PRG32_ABI_FN_COUNT = 121
};
8 changes: 8 additions & 0 deletions components/prg32/prg32_abi_exports.c
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,14 @@ static const prg32_any_fn_t g_prg32_cart_abi_exports[] = {
(prg32_any_fn_t)prg32_sprite_anim_update,
(prg32_any_fn_t)prg32_sprite_anim_draw,
(prg32_any_fn_t)prg32_score_submit,
(prg32_any_fn_t)prg32_sprite_draw_24x24,
(prg32_any_fn_t)prg32_score_player_get,
(prg32_any_fn_t)prg32_score_player_set,
(prg32_any_fn_t)prg32_score_player_prompt,
(prg32_any_fn_t)prg32_score_submit_current_player,
(prg32_any_fn_t)prg32_score_count,
(prg32_any_fn_t)prg32_score_get,
(prg32_any_fn_t)prg32_scoreboard_show,
};

void prg32_abi_exports_keep(void) {
Expand Down
8 changes: 8 additions & 0 deletions components/prg32/prg32_abi_table.c
Original file line number Diff line number Diff line change
Expand Up @@ -129,5 +129,13 @@ const prg32_abi_table_t prg32_abi_table = {
[PRG32_ABI_FN_PRG32_SPRITE_ANIM_UPDATE] = (const void *)prg32_sprite_anim_update,
[PRG32_ABI_FN_PRG32_SPRITE_ANIM_DRAW] = (const void *)prg32_sprite_anim_draw,
[PRG32_ABI_FN_PRG32_SCORE_SUBMIT] = (const void *)prg32_score_submit,
[PRG32_ABI_FN_PRG32_SPRITE_DRAW_24X24] = (const void *)prg32_sprite_draw_24x24,
[PRG32_ABI_FN_PRG32_SCORE_PLAYER_GET] = (const void *)prg32_score_player_get,
[PRG32_ABI_FN_PRG32_SCORE_PLAYER_SET] = (const void *)prg32_score_player_set,
[PRG32_ABI_FN_PRG32_SCORE_PLAYER_PROMPT] = (const void *)prg32_score_player_prompt,
[PRG32_ABI_FN_PRG32_SCORE_SUBMIT_CURRENT_PLAYER] = (const void *)prg32_score_submit_current_player,
[PRG32_ABI_FN_PRG32_SCORE_COUNT] = (const void *)prg32_score_count,
[PRG32_ABI_FN_PRG32_SCORE_GET] = (const void *)prg32_score_get,
[PRG32_ABI_FN_PRG32_SCOREBOARD_SHOW] = (const void *)prg32_scoreboard_show,
},
};
219 changes: 188 additions & 31 deletions components/prg32/prg32_http_scores.c
Original file line number Diff line number Diff line change
@@ -1,17 +1,36 @@
#include "prg32.h"
#include "prg32_config.h"

#if PRG32_WIFI_ENABLE
#include <stdio.h>
#include <string.h>

#if PRG32_WIFI_ENABLE
#include "cJSON.h"
#include "esp_err.h"
#include "esp_http_client.h"
#include "esp_http_server.h"
#include <stdio.h>
#include <string.h>
#endif

#if __has_include("esp_err.h")
#include "esp_err.h"
#else
#define ESP_OK 0
#define ESP_FAIL -1
#define ESP_ERR_INVALID_ARG -2
#endif

#if __has_include("freertos/FreeRTOS.h")
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#else
#define pdMS_TO_TICKS(ms) (ms)
static void vTaskDelay(int ticks) {
(void)ticks;
}
#endif

static prg32_score_t scores[PRG32_SCORE_MAX];
static int score_count;
static char current_player[sizeof(scores[0].player)] = "PLAYER";

static void copy_cstr(char *dst, size_t dst_size, const char *src) {
if (!dst || dst_size == 0) {
Expand All @@ -24,6 +43,168 @@ static void copy_cstr(char *dst, size_t dst_size, const char *src) {
dst[dst_size - 1] = '\0';
}

static int score_matches_game(const prg32_score_t *record, const char *game) {
return record && (!game || !game[0] || strcmp(record->game, game) == 0);
}

static int score_visible_index(const char *game, int visible_index) {
int seen = 0;
for (int i = 0; i < score_count; ++i) {
if (!score_matches_game(&scores[i], game)) {
continue;
}
if (seen == visible_index) {
return i;
}
seen++;
}
return -1;
}

int prg32_score_player_get(char *out_player, size_t max_len) {
if (!out_player || max_len == 0) {
return ESP_ERR_INVALID_ARG;
}
copy_cstr(out_player, max_len, current_player);
return ESP_OK;
}

int prg32_score_player_set(const char *player) {
if (!player || !player[0]) {
return ESP_ERR_INVALID_ARG;
}
copy_cstr(current_player, sizeof(current_player), player);
return ESP_OK;
}

int prg32_score_player_prompt(void) {
char player[sizeof(current_player)];
copy_cstr(player, sizeof(player), current_player);
int len = prg32_text_input(player, sizeof(player), "PLAYER NAME");
if (len < 0) {
return len;
}
if (player[0]) {
prg32_score_player_set(player);
}
return (int)strlen(current_player);
}

int prg32_score_submit(const char *game, const char *player, uint32_t score) {
if (!game || !game[0] || !player || !player[0]) {
return ESP_ERR_INVALID_ARG;
}
if (score_count >= PRG32_SCORE_MAX) {
score_count = PRG32_SCORE_MAX - 1;
}
memmove(&scores[1], &scores[0], sizeof(scores[0]) * score_count);
copy_cstr(scores[0].game, sizeof(scores[0].game), game);
copy_cstr(scores[0].player, sizeof(scores[0].player), player);
scores[0].score = score;
if (score_count < PRG32_SCORE_MAX) {
score_count++;
}
return ESP_OK;
}

int prg32_score_submit_current_player(const char *game, uint32_t score) {
return prg32_score_submit(game, current_player, score);
}

int prg32_score_count(const char *game) {
int count = 0;
for (int i = 0; i < score_count; ++i) {
if (score_matches_game(&scores[i], game)) {
count++;
}
}
return count;
}

int prg32_score_get(const char *game, int index, prg32_score_t *out_score) {
if (!out_score || index < 0) {
return ESP_ERR_INVALID_ARG;
}
int raw = score_visible_index(game, index);
if (raw < 0) {
return ESP_FAIL;
}
*out_score = scores[raw];
return ESP_OK;
}

static int top_score_index(const char *game, const int *used, int used_count) {
int best = -1;
for (int i = 0; i < score_count; ++i) {
if (!score_matches_game(&scores[i], game)) {
continue;
}
int already_used = 0;
for (int j = 0; j < used_count; ++j) {
if (used[j] == i) {
already_used = 1;
break;
}
}
if (already_used) {
continue;
}
if (best < 0 ||
scores[i].score > scores[best].score ||
(scores[i].score == scores[best].score && i < best)) {
best = i;
}
}
return best;
}

int prg32_scoreboard_show(const char *game, const char *title) {
int used[PRG32_SCORE_MAX];

prg32_input_wait_released(PRG32_BTN_A | PRG32_BTN_B | PRG32_BTN_SELECT);
while (1) {
uint32_t input = prg32_input_read_menu();
if (input & (PRG32_BTN_A | PRG32_BTN_B | PRG32_BTN_SELECT)) {
prg32_input_wait_released(PRG32_BTN_A | PRG32_BTN_B | PRG32_BTN_SELECT);
return 0;
}

prg32_gfx_clear(PRG32_COLOR_BLACK);
prg32_gfx_text8(8, 8, title ? title : "SCOREBOARD", PRG32_COLOR_WHITE, 0);
prg32_gfx_text8(8,
24,
game && game[0] ? game : "ALL GAMES",
PRG32_COLOR_CYAN,
0);

int used_count = 0;
for (int row = 0; row < 8; ++row) {
int index = top_score_index(game, used, used_count);
if (index < 0) {
if (row == 0) {
prg32_gfx_text8(8, 64, "NO SCORES YET", PRG32_COLOR_YELLOW, 0);
}
break;
}
used[used_count++] = index;
char line[48];
snprintf(line,
sizeof(line),
"%2d %-16s %lu",
row + 1,
scores[index].player,
(unsigned long)scores[index].score);
prg32_gfx_text8(8, 48 + row * 18, line, PRG32_COLOR_WHITE, 0);
}

prg32_gfx_text8(8, 224, "A / B / SELECT BACK", PRG32_COLOR_CYAN, 0);
prg32_gfx_present();
vTaskDelay(pdMS_TO_TICKS(80));
}
}

#if PRG32_WIFI_ENABLE

static int copy_json_string(char *dst, size_t dst_size, const char *src) {
if (!dst || dst_size == 0 || !src) {
return ESP_ERR_INVALID_ARG;
Expand Down Expand Up @@ -54,23 +235,6 @@ static int copy_json_string(char *dst, size_t dst_size, const char *src) {
return ESP_OK;
}

int prg32_score_submit(const char *game, const char *player, uint32_t score) {
if (!game || !player) {
return ESP_ERR_INVALID_ARG;
}
if (score_count >= PRG32_SCORE_MAX) {
score_count = PRG32_SCORE_MAX - 1;
}
memmove(&scores[1], &scores[0], sizeof(scores[0]) * score_count);
copy_cstr(scores[0].game, sizeof(scores[0].game), game);
copy_cstr(scores[0].player, sizeof(scores[0].player), player);
scores[0].score = score;
if (score_count < PRG32_SCORE_MAX) {
score_count++;
}
return ESP_OK;
}

int prg32_score_submit_remote(const char *base_url,
const char *game,
const char *player,
Expand Down Expand Up @@ -106,7 +270,7 @@ int prg32_score_submit_remote(const char *base_url,
esp_http_client_config_t cfg = {
.url = url,
.method = HTTP_METHOD_POST,
.timeout_ms = 3000
.timeout_ms = 3000,
};
esp_http_client_handle_t client = esp_http_client_init(&cfg);
if (!client) {
Expand Down Expand Up @@ -226,12 +390,12 @@ void prg32_http_register_score_handlers(httpd_handle_t server) {
httpd_uri_t gs = {
.uri = "/api/scores",
.method = HTTP_GET,
.handler = get_scores
.handler = get_scores,
};
httpd_uri_t ps = {
.uri = "/api/scores",
.method = HTTP_POST,
.handler = post_score
.handler = post_score,
};
ESP_ERROR_CHECK(httpd_register_uri_handler(server, &gs));
ESP_ERROR_CHECK(httpd_register_uri_handler(server, &ps));
Expand All @@ -242,13 +406,6 @@ void prg32_http_register_score_handlers(httpd_handle_t server) {

#else

int prg32_score_submit(const char *game, const char *player, uint32_t score) {
(void)game;
(void)player;
(void)score;
return 0;
}

int prg32_score_submit_remote(const char *base_url,
const char *game,
const char *player,
Expand Down
14 changes: 7 additions & 7 deletions components/prg32/prg32_sprite.c
Original file line number Diff line number Diff line change
Expand Up @@ -46,14 +46,14 @@ void prg32_sprite_draw_16x16(int x, int y, const uint16_t *rgb565) {
if (!rgb565) {
return;
}
for (int row = 0; row < 16; ++row) {
for (int col = 0; col < 16; ++col) {
uint16_t color = rgb565[row * 16 + col];
if (color != PRG32_COLOR_WHITE) {
prg32_gfx_pixel(x + col, y + row, color);
}
}
prg32_sprite_draw_frame(x, y, 16, 16, rgb565, 0, PRG32_COLOR_WHITE);
}

void prg32_sprite_draw_24x24(int x, int y, const uint16_t *rgb565) {
if (!rgb565) {
return;
}
prg32_sprite_draw_frame(x, y, 24, 24, rgb565, 0, PRG32_COLOR_WHITE);
}

uint32_t prg32_sprite_anim_frame(uint32_t now_ms,
Expand Down
Loading
Loading