2025-08-13 15:04:49 -05:00
|
|
|
#define PXL8_AUTHORS "asrael <asrael@pxl8.org>"
|
|
|
|
|
#define PXL8_COPYRIGHT "Copyright (c) 2024-2025 pxl8.org"
|
|
|
|
|
#define PXL8_VERSION "0.1.0"
|
|
|
|
|
|
2025-11-01 12:39:59 -05:00
|
|
|
#include <pthread.h>
|
2025-09-29 11:26:52 -05:00
|
|
|
#include <stdlib.h>
|
2025-10-17 17:54:33 -05:00
|
|
|
#include <string.h>
|
2025-08-13 15:04:49 -05:00
|
|
|
#include <sys/stat.h>
|
|
|
|
|
#include <unistd.h>
|
|
|
|
|
|
2025-09-27 11:03:36 -05:00
|
|
|
#include "pxl8_cart.h"
|
2025-10-17 17:54:33 -05:00
|
|
|
#include "pxl8_game.h"
|
|
|
|
|
#include "pxl8_hal.h"
|
2025-08-13 15:04:49 -05:00
|
|
|
#include "pxl8_macros.h"
|
2025-10-04 04:13:48 -05:00
|
|
|
#include "pxl8_script.h"
|
2025-08-13 15:04:49 -05:00
|
|
|
#include "pxl8_types.h"
|
2025-10-04 11:55:04 -05:00
|
|
|
#include "pxl8_ui.h"
|
2025-08-13 15:04:49 -05:00
|
|
|
|
2025-10-17 17:54:33 -05:00
|
|
|
pxl8_game_result pxl8_init(pxl8_game* game, i32 argc, char* argv[]) {
|
|
|
|
|
if (!game) {
|
|
|
|
|
return PXL8_GAME_FAILURE;
|
2025-08-13 15:04:49 -05:00
|
|
|
}
|
|
|
|
|
|
2025-10-17 17:54:33 -05:00
|
|
|
if (!game->hal) {
|
|
|
|
|
pxl8_error("HAL must be set before calling pxl8_init");
|
|
|
|
|
return PXL8_GAME_FAILURE;
|
2025-08-13 15:04:49 -05:00
|
|
|
}
|
|
|
|
|
|
2025-10-17 17:54:33 -05:00
|
|
|
game->color_mode = PXL8_COLOR_MODE_MEGA;
|
|
|
|
|
game->resolution = PXL8_RESOLUTION_640x360;
|
2025-09-27 11:03:36 -05:00
|
|
|
|
2025-08-13 15:04:49 -05:00
|
|
|
const char* script_arg = NULL;
|
2025-09-27 11:03:36 -05:00
|
|
|
bool pack_mode = false;
|
|
|
|
|
const char* pack_input = NULL;
|
|
|
|
|
const char* pack_output = NULL;
|
|
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
for (i32 i = 1; i < argc; i++) {
|
2025-08-13 15:04:49 -05:00
|
|
|
if (strcmp(argv[i], "--repl") == 0) {
|
2025-10-17 17:54:33 -05:00
|
|
|
game->repl_mode = true;
|
2025-09-27 11:03:36 -05:00
|
|
|
} else if (strcmp(argv[i], "--pack") == 0) {
|
|
|
|
|
pack_mode = true;
|
|
|
|
|
if (i + 2 < argc) {
|
|
|
|
|
pack_input = argv[++i];
|
|
|
|
|
pack_output = argv[++i];
|
|
|
|
|
} else {
|
|
|
|
|
pxl8_error("--pack requires <folder> <output.pxc>");
|
2025-10-17 17:54:33 -05:00
|
|
|
return PXL8_GAME_FAILURE;
|
2025-09-27 11:03:36 -05:00
|
|
|
}
|
2025-08-13 15:04:49 -05:00
|
|
|
} else if (!script_arg) {
|
|
|
|
|
script_arg = argv[i];
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-09-27 11:03:36 -05:00
|
|
|
|
|
|
|
|
if (pack_mode) {
|
|
|
|
|
pxl8_result result = pxl8_cart_pack(pack_input, pack_output);
|
2025-10-17 17:54:33 -05:00
|
|
|
return (result == PXL8_OK) ? PXL8_GAME_SUCCESS : PXL8_GAME_FAILURE;
|
2025-08-13 15:04:49 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2025-10-17 17:54:33 -05:00
|
|
|
if (game->repl_mode) {
|
|
|
|
|
fprintf(stderr, "\033[38;2;184;187;38m[pxl8]\033[0m Starting in REPL mode with script: %s\n", game->script_path);
|
2025-08-13 15:04:49 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pxl8_info("Starting up");
|
2025-10-04 04:13:48 -05:00
|
|
|
|
2025-10-17 17:54:33 -05:00
|
|
|
game->gfx = pxl8_gfx_create(game->hal, game->color_mode, game->resolution, "pxl8", 1280, 720);
|
|
|
|
|
if (!game->gfx) {
|
2025-10-04 04:13:48 -05:00
|
|
|
pxl8_error("Failed to create graphics context");
|
2025-10-17 17:54:33 -05:00
|
|
|
return PXL8_GAME_FAILURE;
|
2025-08-13 15:04:49 -05:00
|
|
|
}
|
2025-10-04 04:13:48 -05:00
|
|
|
|
2025-10-17 17:54:33 -05:00
|
|
|
if (pxl8_gfx_load_font_atlas(game->gfx) != PXL8_OK) {
|
2025-08-13 15:04:49 -05:00
|
|
|
pxl8_error("Failed to load font atlas");
|
2025-10-17 17:54:33 -05:00
|
|
|
return PXL8_GAME_FAILURE;
|
2025-08-13 15:04:49 -05:00
|
|
|
}
|
2025-10-04 04:13:48 -05:00
|
|
|
|
2025-10-17 17:54:33 -05:00
|
|
|
game->ui = pxl8_ui_create(game->gfx);
|
|
|
|
|
if (!game->ui) {
|
2025-10-04 11:55:04 -05:00
|
|
|
pxl8_error("Failed to create UI");
|
2025-10-17 17:54:33 -05:00
|
|
|
pxl8_gfx_destroy(game->gfx);
|
|
|
|
|
return PXL8_GAME_FAILURE;
|
2025-10-04 11:55:04 -05:00
|
|
|
}
|
|
|
|
|
|
2025-10-17 17:54:33 -05:00
|
|
|
game->script = pxl8_script_create();
|
|
|
|
|
if (!game->script) {
|
|
|
|
|
pxl8_error("Failed to initialize scripting: %s", pxl8_script_get_last_error(game->script));
|
|
|
|
|
pxl8_gfx_destroy(game->gfx);
|
|
|
|
|
return PXL8_GAME_FAILURE;
|
2025-08-13 15:04:49 -05:00
|
|
|
}
|
2025-09-27 11:03:36 -05:00
|
|
|
|
2025-09-28 13:10:29 -05:00
|
|
|
const char* cart_path = script_arg ? script_arg : "demo";
|
|
|
|
|
|
|
|
|
|
struct stat st;
|
|
|
|
|
bool is_cart = (stat(cart_path, &st) == 0 && S_ISDIR(st.st_mode)) ||
|
|
|
|
|
(cart_path && strstr(cart_path, ".pxc"));
|
|
|
|
|
|
|
|
|
|
if (is_cart) {
|
|
|
|
|
char* original_cwd = getcwd(NULL, 0);
|
2025-10-17 17:54:33 -05:00
|
|
|
game->cart = pxl8_cart_create();
|
|
|
|
|
if (!game->cart) {
|
2025-10-04 04:13:48 -05:00
|
|
|
pxl8_error("Failed to create cart");
|
2025-10-17 17:54:33 -05:00
|
|
|
return PXL8_GAME_FAILURE;
|
2025-09-28 13:10:29 -05:00
|
|
|
}
|
2025-10-17 17:54:33 -05:00
|
|
|
if (pxl8_cart_load(game->cart, cart_path) == PXL8_OK) {
|
|
|
|
|
pxl8_script_set_cart_path(game->script, pxl8_cart_get_base_path(game->cart), original_cwd);
|
|
|
|
|
pxl8_cart_mount(game->cart);
|
|
|
|
|
strcpy(game->script_path, "main.fnl");
|
|
|
|
|
pxl8_info("Loaded cart: %s", pxl8_cart_get_name(game->cart));
|
2025-09-27 11:03:36 -05:00
|
|
|
} else {
|
2025-09-28 13:10:29 -05:00
|
|
|
pxl8_error("Failed to load cart: %s", cart_path);
|
2025-10-17 17:54:33 -05:00
|
|
|
return PXL8_GAME_FAILURE;
|
2025-09-27 11:03:36 -05:00
|
|
|
}
|
2025-09-28 13:10:29 -05:00
|
|
|
free(original_cwd);
|
|
|
|
|
} else if (script_arg) {
|
2025-10-17 17:54:33 -05:00
|
|
|
strncpy(game->script_path, script_arg, sizeof(game->script_path) - 1);
|
|
|
|
|
game->script_path[sizeof(game->script_path) - 1] = '\0';
|
2025-09-27 11:03:36 -05:00
|
|
|
}
|
2025-09-28 13:10:29 -05:00
|
|
|
|
2025-10-17 17:54:33 -05:00
|
|
|
pxl8_script_set_gfx(game->script, game->gfx);
|
|
|
|
|
pxl8_script_set_input(game->script, &game->input);
|
|
|
|
|
pxl8_script_set_ui(game->script, game->ui);
|
2025-09-28 13:10:29 -05:00
|
|
|
|
2025-10-17 17:54:33 -05:00
|
|
|
if (game->script_path[0] != '\0') {
|
|
|
|
|
pxl8_result result = pxl8_script_load_main(game->script, game->script_path);
|
|
|
|
|
game->script_loaded = (result == PXL8_OK);
|
|
|
|
|
|
2025-11-01 12:39:59 -05:00
|
|
|
if (game->script_loaded && !game->repl_mode) {
|
|
|
|
|
pxl8_script_call_function(game->script, "init");
|
2025-08-13 15:04:49 -05:00
|
|
|
}
|
|
|
|
|
}
|
2025-11-01 12:39:59 -05:00
|
|
|
|
2025-10-17 17:54:33 -05:00
|
|
|
game->last_time = game->hal->get_ticks();
|
|
|
|
|
game->running = true;
|
|
|
|
|
|
2025-08-13 15:04:49 -05:00
|
|
|
|
2025-10-17 17:54:33 -05:00
|
|
|
return PXL8_GAME_CONTINUE;
|
2025-08-13 15:04:49 -05:00
|
|
|
}
|
|
|
|
|
|
2025-10-17 17:54:33 -05:00
|
|
|
pxl8_game_result pxl8_update(pxl8_game* game) {
|
|
|
|
|
if (!game) {
|
|
|
|
|
return PXL8_GAME_FAILURE;
|
|
|
|
|
}
|
2025-08-13 15:04:49 -05:00
|
|
|
|
2025-10-17 17:54:33 -05:00
|
|
|
u64 current_time = game->hal->get_ticks();
|
|
|
|
|
f32 dt = (f32)(current_time - game->last_time) / 1000000000.0f;
|
2025-08-13 15:04:49 -05:00
|
|
|
|
2025-10-17 17:54:33 -05:00
|
|
|
game->last_time = current_time;
|
|
|
|
|
game->time += dt;
|
|
|
|
|
|
|
|
|
|
pxl8_script_check_reload(game->script);
|
|
|
|
|
|
2025-11-01 12:39:59 -05:00
|
|
|
if (game->repl_mode && !game->repl_started) {
|
|
|
|
|
if (game->script_loaded) {
|
|
|
|
|
pxl8_script_call_function(game->script, "init");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
game->repl = pxl8_script_repl_create();
|
|
|
|
|
if (game->repl) {
|
|
|
|
|
fprintf(stderr, "\033[38;2;184;187;38m[pxl8 REPL]\033[0m Fennel %s - Tab for completions, Ctrl-D to exit\n", "1.5.1");
|
|
|
|
|
|
|
|
|
|
if (pxl8_script_load_module(game->script, "pxl8") != PXL8_OK) {
|
|
|
|
|
fprintf(stderr, "Warning: Failed to setup pxl8 global: %s\n", pxl8_script_get_last_error(game->script));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pxl8_script_repl_init(game->repl);
|
|
|
|
|
game->repl_started = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-17 17:54:33 -05:00
|
|
|
if (game->repl_mode && game->repl) {
|
2025-11-01 12:39:59 -05:00
|
|
|
if (pxl8_script_repl_should_quit(game->repl)) {
|
|
|
|
|
game->running = false;
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-17 17:54:33 -05:00
|
|
|
pxl8_script_repl_command* cmd = pxl8_script_repl_pop_command(game->repl);
|
2025-08-13 15:04:49 -05:00
|
|
|
if (cmd) {
|
2025-11-01 12:39:59 -05:00
|
|
|
pxl8_result result = pxl8_script_eval_repl(game->script, pxl8_script_repl_command_buffer(cmd));
|
2025-10-04 04:13:48 -05:00
|
|
|
if (result != PXL8_OK) {
|
2025-11-01 12:39:59 -05:00
|
|
|
if (pxl8_script_is_incomplete_input(game->script)) {
|
|
|
|
|
} else {
|
|
|
|
|
pxl8_error("%s", pxl8_script_get_last_error(game->script));
|
|
|
|
|
pxl8_script_repl_clear_accumulator(game->repl);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
pxl8_script_repl_clear_accumulator(game->repl);
|
2025-08-13 15:04:49 -05:00
|
|
|
}
|
2025-11-01 12:39:59 -05:00
|
|
|
pxl8_script_repl_eval_complete(game->repl);
|
2025-08-13 15:04:49 -05:00
|
|
|
}
|
|
|
|
|
}
|
2025-10-04 04:13:48 -05:00
|
|
|
|
2025-10-17 17:54:33 -05:00
|
|
|
if (game->ui) {
|
|
|
|
|
i32 render_width, render_height;
|
|
|
|
|
pxl8_gfx_get_resolution_dimensions(game->resolution, &render_width, &render_height);
|
2025-10-06 18:14:07 -05:00
|
|
|
|
2025-10-17 17:54:33 -05:00
|
|
|
pxl8_ui_input_mousemove(game->ui, game->input.mouse_x, game->input.mouse_y);
|
|
|
|
|
|
|
|
|
|
if (game->input.mouse_wheel_x != 0 || game->input.mouse_wheel_y != 0) {
|
|
|
|
|
pxl8_ui_input_scroll(game->ui, game->input.mouse_wheel_x * 10, -game->input.mouse_wheel_y * 10);
|
|
|
|
|
}
|
2025-10-04 11:55:04 -05:00
|
|
|
|
|
|
|
|
for (i32 i = 0; i < 3; i++) {
|
2025-10-17 17:54:33 -05:00
|
|
|
if (game->input.mouse_buttons_pressed[i]) {
|
|
|
|
|
pxl8_ui_input_mousedown(game->ui, game->input.mouse_x, game->input.mouse_y, i + 1);
|
2025-10-04 11:55:04 -05:00
|
|
|
}
|
2025-10-17 17:54:33 -05:00
|
|
|
if (game->input.mouse_buttons_released[i]) {
|
|
|
|
|
pxl8_ui_input_mouseup(game->ui, game->input.mouse_x, game->input.mouse_y, i + 1);
|
2025-10-04 11:55:04 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (i32 key = 0; key < 256; key++) {
|
2025-10-17 17:54:33 -05:00
|
|
|
if (game->input.keys_pressed[key]) {
|
|
|
|
|
pxl8_ui_input_keydown(game->ui, key);
|
2025-10-04 11:55:04 -05:00
|
|
|
}
|
2025-10-17 17:54:33 -05:00
|
|
|
if (!game->input.keys_down[key] && game->input.keys_pressed[key]) {
|
|
|
|
|
pxl8_ui_input_keyup(game->ui, key);
|
2025-10-04 11:55:04 -05:00
|
|
|
}
|
|
|
|
|
}
|
2025-10-17 17:54:33 -05:00
|
|
|
|
|
|
|
|
pxl8_ui_frame_begin(game->ui);
|
2025-10-04 11:55:04 -05:00
|
|
|
}
|
|
|
|
|
|
2025-10-17 17:54:33 -05:00
|
|
|
if (game->script_loaded) {
|
|
|
|
|
pxl8_script_call_function_f32(game->script, "update", dt);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return PXL8_GAME_CONTINUE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pxl8_game_result pxl8_frame(pxl8_game* game) {
|
|
|
|
|
if (!game) {
|
|
|
|
|
return PXL8_GAME_FAILURE;
|
|
|
|
|
}
|
2025-10-04 04:13:48 -05:00
|
|
|
|
2025-10-17 17:54:33 -05:00
|
|
|
pxl8_bounds bounds = pxl8_gfx_get_bounds(game->gfx);
|
|
|
|
|
|
|
|
|
|
if (game->script_loaded) {
|
|
|
|
|
pxl8_result frame_result = pxl8_script_call_function(game->script, "frame");
|
2025-10-04 04:13:48 -05:00
|
|
|
if (frame_result == PXL8_ERROR_SCRIPT_ERROR) {
|
2025-10-17 17:54:33 -05:00
|
|
|
pxl8_error("Error calling frame: %s", pxl8_script_get_last_error(game->script));
|
2025-08-13 15:04:49 -05:00
|
|
|
}
|
|
|
|
|
} else {
|
2025-10-17 17:54:33 -05:00
|
|
|
pxl8_clr(game->gfx, 32);
|
2025-10-04 11:55:04 -05:00
|
|
|
|
2025-08-13 15:04:49 -05:00
|
|
|
i32 render_width, render_height;
|
2025-10-17 17:54:33 -05:00
|
|
|
pxl8_gfx_get_resolution_dimensions(game->resolution, &render_width, &render_height);
|
2025-10-04 11:55:04 -05:00
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
for (i32 y = 0; y < render_height; y += 24) {
|
|
|
|
|
for (i32 x = 0; x < render_width; x += 32) {
|
2025-10-17 17:54:33 -05:00
|
|
|
u32 color = ((x / 32) + (y / 24) + (i32)(game->time * 2)) % 8;
|
|
|
|
|
pxl8_rect_fill(game->gfx, x, y, 31, 23, color);
|
2025-08-13 15:04:49 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-10-04 11:55:04 -05:00
|
|
|
|
2025-08-13 15:04:49 -05:00
|
|
|
i32 render_width, render_height;
|
2025-10-17 17:54:33 -05:00
|
|
|
pxl8_gfx_get_resolution_dimensions(game->resolution, &render_width, &render_height);
|
2025-10-04 04:13:48 -05:00
|
|
|
|
2025-10-17 17:54:33 -05:00
|
|
|
if (game->ui) {
|
|
|
|
|
pxl8_ui_frame_end(game->ui);
|
2025-10-04 11:55:04 -05:00
|
|
|
}
|
|
|
|
|
|
2025-10-17 17:54:33 -05:00
|
|
|
pxl8_gfx_set_viewport(game->gfx, pxl8_gfx_viewport(bounds, render_width, render_height));
|
|
|
|
|
pxl8_gfx_upload_framebuffer(game->gfx);
|
|
|
|
|
pxl8_gfx_upload_atlas(game->gfx);
|
|
|
|
|
pxl8_gfx_present(game->gfx);
|
2025-10-04 11:55:04 -05:00
|
|
|
|
2025-10-17 17:54:33 -05:00
|
|
|
memset(game->input.keys_pressed, 0, sizeof(game->input.keys_pressed));
|
|
|
|
|
memset(game->input.keys_released, 0, sizeof(game->input.keys_released));
|
|
|
|
|
memset(game->input.mouse_buttons_pressed, 0, sizeof(game->input.mouse_buttons_pressed));
|
|
|
|
|
memset(game->input.mouse_buttons_released, 0, sizeof(game->input.mouse_buttons_released));
|
|
|
|
|
game->input.mouse_wheel_x = 0;
|
|
|
|
|
game->input.mouse_wheel_y = 0;
|
2025-08-13 15:04:49 -05:00
|
|
|
|
2025-10-17 17:54:33 -05:00
|
|
|
return game->running ? PXL8_GAME_CONTINUE : PXL8_GAME_SUCCESS;
|
2025-08-13 15:04:49 -05:00
|
|
|
}
|
|
|
|
|
|
2025-10-17 17:54:33 -05:00
|
|
|
void pxl8_quit(pxl8_game* game) {
|
|
|
|
|
if (!game) return;
|
2025-10-04 11:55:04 -05:00
|
|
|
|
2025-10-17 17:54:33 -05:00
|
|
|
if (game->repl_mode && game->repl) {
|
2025-11-01 12:39:59 -05:00
|
|
|
fprintf(stderr, "\r\033[K");
|
|
|
|
|
fflush(stderr);
|
2025-10-17 17:54:33 -05:00
|
|
|
pxl8_script_repl_shutdown(game->repl);
|
|
|
|
|
pxl8_script_repl_destroy(game->repl);
|
2025-08-13 15:04:49 -05:00
|
|
|
}
|
2025-10-04 11:55:04 -05:00
|
|
|
|
2025-11-01 12:39:59 -05:00
|
|
|
pxl8_info("Shutting down");
|
|
|
|
|
|
2025-10-17 17:54:33 -05:00
|
|
|
if (game->cart) {
|
|
|
|
|
pxl8_cart_unload(game->cart);
|
|
|
|
|
free(game->cart);
|
|
|
|
|
game->cart = NULL;
|
2025-08-13 15:04:49 -05:00
|
|
|
}
|
2025-10-04 04:13:48 -05:00
|
|
|
|
2025-10-17 17:54:33 -05:00
|
|
|
pxl8_gfx_destroy(game->gfx);
|
|
|
|
|
pxl8_script_destroy(game->script);
|
|
|
|
|
if (game->ui) pxl8_ui_destroy(game->ui);
|
2025-08-13 15:04:49 -05:00
|
|
|
}
|