|
diff --git a/README.md b/README.md
|
| 1 |
**Bidi** is a lightweight framework and fantasy console designed for creating |
1 |
**Bidi** is a lightweight framework and fantasy console designed for creating |
| 2 |
tiny video games, perfect for learning and having fun. It takes heavy |
2 |
tiny video games, perfect for learning and having fun. It takes heavy |
| 3 |
inspiration from [LÖVE](https://www.love2d.org/), |
3 |
inspiration from [LÖVE](https://www.love2d.org/), |
| 4 |
[Pico8](https://www.lexaloffle.com/pico-8.php) and [Atari |
4 |
[Pico8](https://www.lexaloffle.com/pico-8.php), [Atari |
| 5 |
2600](https://en.wikipedia.org/wiki/Atari_2600). |
5 |
2600](https://en.wikipedia.org/wiki/Atari_2600) and [Windows |
|
|
6 |
3.1](https://en.wikipedia.org/wiki/Windows_3.1). |
| 6 |
|
7 |
|
| 7 |
You develop your games in Lua and you are only allowed to have one Lua file |
8 |
You develop your games in Lua and you are only allowed to have one Lua file |
| 8 |
where all your code lives. You can then load external assets like images and |
9 |
where all your code lives. You can then load external assets like images and |
| ... |
| 75 |
| `draw_circle` | `number center_x`, `number center_y`, `number radius`, `color color` | | |
76 |
| `draw_circle` | `number center_x`, `number center_y`, `number radius`, `color color` | | |
| 76 |
| `draw_ellipse` | `number center_x`, `number center_y`, `number radius_h`, `number radius_v`, `color color` | | |
77 |
| `draw_ellipse` | `number center_x`, `number center_y`, `number radius_h`, `number radius_v`, `color color` | | |
| 77 |
| `draw_triangle` | `number x1`, `number y1`, `number x2`, `number y2`, `number x3`, `number y3`, `color color` | | |
78 |
| `draw_triangle` | `number x1`, `number y1`, `number x2`, `number y2`, `number x3`, `number y3`, `color color` | | |
| 78 |
| `load_image` | `TODO` | | |
79 |
| `load_image` | `string filepath` | `string` | |
|
|
80 |
| `draw_image` | `string uid`, `number x`, `number y` | | |
| 79 |
| `load_audio` | `TODO` | | |
81 |
| `load_audio` | `TODO` | | |
| 80 |
| `button_down` | `button button` | `bool` | |
82 |
| `button_down` | `button button` | `bool` | |
| 81 |
| `button_pressed` | `button button` | `bool` | |
83 |
| `button_pressed` | `button button` | `bool` | |
| ... |
| 137 |
|
139 |
|
| 138 |
- https://hardwaretester.com/gamepad |
140 |
- https://hardwaretester.com/gamepad |
| 139 |
- https://www.raylib.com/examples/core/loader.html?name=core_input_gamepad |
141 |
- https://www.raylib.com/examples/core/loader.html?name=core_input_gamepad |
|
|
142 |
|
|
|
143 |
## Interesting games |
|
|
144 |
|
|
|
145 |
- https://www.old-games.com/download/3873/aethra-s-chronicles |
|
|
146 |
- https://www.old-games.com/download/10746/wintrek-1992 |
|
|
147 |
- https://www.old-games.com/download/3887/bard-s-tale-1 |
|
|
148 |
- https://www.old-games.com/download/3902/castle-of-the-winds |
|
|
149 |
- https://www.old-games.com/download/3923/decker |
|
|
150 |
- https://www.old-games.com/download/3920/darkspyre |
|
|
151 |
- https://www.old-games.com/download/6089/exile-3-ruined-world |
| 140 |
|
152 |
|
| 141 |
## Development references |
153 |
## Development references |
| 142 |
|
154 |
|
| ... |
|
diff --git a/main.c b/main.c
|
| ... |
| 3 |
#include <getopt.h> |
3 |
#include <getopt.h> |
| 4 |
#include <stdlib.h> |
4 |
#include <stdlib.h> |
| 5 |
#include <time.h> |
5 |
#include <time.h> |
|
|
6 |
#include <string.h> |
| 6 |
|
7 |
|
| 7 |
#include "raylib.h" |
8 |
#include "raylib.h" |
| 8 |
#include "lua.h" |
9 |
#include "lua.h" |
| ... |
| 24 |
#define XBOX_ALIAS_2 "x-box" |
25 |
#define XBOX_ALIAS_2 "x-box" |
| 25 |
#define PS_ALIAS "playstation" |
26 |
#define PS_ALIAS "playstation" |
| 26 |
|
27 |
|
| 27 |
typedef struct { |
28 |
typedef struct ExternalImage { |
| 28 |
char uid[UID_LENGTH + 1]; |
29 |
char uid[UID_LENGTH + 1]; |
| 29 |
const char* filepath; |
30 |
Texture2D texture; |
|
|
31 |
struct ExternalImage *next; |
| 30 |
} ExternalImage; |
32 |
} ExternalImage; |
| 31 |
|
33 |
|
| 32 |
typedef struct { |
34 |
typedef struct { |
| 33 |
} ExternalAudio; |
35 |
ExternalImage *head; |
|
|
36 |
ExternalImage *tail; |
|
|
37 |
int count; |
|
|
38 |
} ImageList; |
| 34 |
|
39 |
|
| 35 |
typedef struct { |
40 |
typedef struct { |
| 36 |
Font font; |
41 |
Font font; |
| 37 |
int font_size; |
42 |
int font_size; |
| 38 |
Camera2D camera; |
43 |
Camera2D camera; |
| 39 |
ExternalImage *images; |
44 |
ImageList images; |
| 40 |
ExternalAudio *audio; |
|
|
| 41 |
} Context; |
45 |
} Context; |
| 42 |
|
46 |
|
| 43 |
// Setting up global context. |
47 |
// Setting up global context. |
| 44 |
Context ctx = {0}; |
48 |
Context ctx = {0}; |
| 45 |
|
49 |
|
| 46 |
static void generate_uid(char *uid) { |
50 |
static void generate_uid(char *str, size_t length) { |
| 47 |
const char *chars = "0123456789abcdef"; |
51 |
static const char charset[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; |
| 48 |
for (size_t i = 0; i < UID_LENGTH; i++) { |
52 |
size_t charset_size = sizeof(charset) - 1; // exclude null terminator |
| 49 |
uid[i] = chars[rand() % 16]; |
53 |
|
|
|
54 |
for (size_t i = 0; i < length; i++) { |
|
|
55 |
int key = rand() % charset_size; |
|
|
56 |
str[i] = charset[key]; |
| 50 |
} |
57 |
} |
| 51 |
uid[UID_LENGTH + 1] = '\0'; |
58 |
str[length] = '\0'; |
|
|
59 |
} |
|
|
60 |
|
|
|
61 |
void init_image_list(ImageList *list) { |
|
|
62 |
list->head = NULL; |
|
|
63 |
list->tail = NULL; |
|
|
64 |
list->count = 0; |
| 52 |
} |
65 |
} |
| 53 |
|
66 |
|
| 54 |
static int lua_getfield_int(lua_State *L, int index, const char *key) { |
67 |
static int lua_getfield_int(lua_State *L, int index, const char *key) { |
| ... |
| 67 |
|
80 |
|
| 68 |
static int load_embedded_module(lua_State *L, const char *module_code, size_t code_len, const char *module_name) { |
81 |
static int load_embedded_module(lua_State *L, const char *module_code, size_t code_len, const char *module_name) { |
| 69 |
if (luaL_loadbuffer(L, module_code, code_len, module_name) || lua_pcall(L, 0, 1, 0)) { |
82 |
if (luaL_loadbuffer(L, module_code, code_len, module_name) || lua_pcall(L, 0, 1, 0)) { |
| 70 |
fprintf(stderr, "Error loading %s: %s\n", module_name, lua_tostring(L, -1)); |
83 |
TraceLog(LOG_FATAL, "Error loading %s: %s\n", module_name, lua_tostring(L, -1)); |
| 71 |
return 0; |
84 |
return 0; |
| 72 |
} |
85 |
} |
| 73 |
lua_setglobal(L, module_name); |
86 |
lua_setglobal(L, module_name); |
| ... |
| 380 |
} |
393 |
} |
| 381 |
|
394 |
|
| 382 |
static int l_load_image(lua_State *L) { |
395 |
static int l_load_image(lua_State *L) { |
|
|
396 |
const char *filepath = luaL_checkstring(L, 1); |
|
|
397 |
|
|
|
398 |
ExternalImage *img = malloc(sizeof(ExternalImage)); |
|
|
399 |
if (!img) { |
|
|
400 |
TraceLog(LOG_FATAL, "Out of memory!"); |
|
|
401 |
return 0; |
|
|
402 |
} |
|
|
403 |
|
|
|
404 |
Image image = LoadImage(filepath); |
|
|
405 |
if (!image.data) { |
|
|
406 |
TraceLog(LOG_FATAL, "Failed to load image: %s\n", filepath); |
|
|
407 |
free(img); |
|
|
408 |
return 0; |
|
|
409 |
} |
|
|
410 |
|
|
|
411 |
img->texture = LoadTextureFromImage(image); |
|
|
412 |
UnloadImage(image); |
|
|
413 |
|
|
|
414 |
generate_uid(img->uid, UID_LENGTH); |
|
|
415 |
img->next = NULL; |
|
|
416 |
|
|
|
417 |
if (ctx.images.tail) { |
|
|
418 |
ctx.images.tail->next = img; |
|
|
419 |
ctx.images.tail = img; |
|
|
420 |
} else { |
|
|
421 |
ctx.images.head = img; |
|
|
422 |
ctx.images.tail = img; |
|
|
423 |
} |
|
|
424 |
ctx.images.count++; |
|
|
425 |
|
|
|
426 |
TraceLog(LOG_WARNING, "[adding]\t %s (%d)", img->uid, strlen(img->uid)); |
|
|
427 |
|
|
|
428 |
lua_pushstring(L, img->uid); |
|
|
429 |
return 1; |
|
|
430 |
} |
|
|
431 |
|
|
|
432 |
static int l_load_audio(lua_State *L) { |
| 383 |
return 0; |
433 |
return 0; |
| 384 |
} |
434 |
} |
| 385 |
|
435 |
|
| 386 |
static int l_load_audio(lua_State *L) { |
436 |
static int l_draw_image(lua_State *L) { |
|
|
437 |
const char *uid = luaL_checkstring(L, 1); |
|
|
438 |
int x = luaL_checknumber(L, 2); |
|
|
439 |
int y = luaL_checknumber(L, 3); |
|
|
440 |
|
|
|
441 |
ExternalImage *current = ctx.images.head; |
|
|
442 |
while (current) { |
|
|
443 |
if (strncmp(current->uid, uid, UID_LENGTH) == 0) { |
|
|
444 |
/* TraceLog(LOG_WARNING, "[match]\t %s (%d)", uid, strlen(uid)); */ |
|
|
445 |
DrawTexture(current->texture, x, y, WHITE); |
|
|
446 |
break; |
|
|
447 |
} |
|
|
448 |
current = current->next; |
|
|
449 |
} |
|
|
450 |
|
| 387 |
return 0; |
451 |
return 0; |
| 388 |
} |
452 |
} |
| 389 |
|
453 |
|
| ... |
| 404 |
|
468 |
|
| 405 |
int main(int argc, char *argv[]) { |
469 |
int main(int argc, char *argv[]) { |
| 406 |
srand(time(NULL)); |
470 |
srand(time(NULL)); |
|
|
471 |
init_image_list(&ctx.images); |
| 407 |
|
472 |
|
| 408 |
TraceLogLevel debug_level = LOG_WARNING; |
473 |
TraceLogLevel debug_level = LOG_WARNING; |
| 409 |
const char *run_file = NULL; |
474 |
const char *run_file = NULL; |
| ... |
| 472 |
lua_register(L, "get_width", l_get_width); |
537 |
lua_register(L, "get_width", l_get_width); |
| 473 |
lua_register(L, "get_height", l_get_height); |
538 |
lua_register(L, "get_height", l_get_height); |
| 474 |
|
539 |
|
|
|
540 |
lua_register(L, "load_image", l_load_image); |
|
|
541 |
lua_register(L, "load_audio", l_load_audio); |
|
|
542 |
|
|
|
543 |
lua_register(L, "button_down", l_button_down); |
|
|
544 |
lua_register(L, "button_pressed", l_button_pressed); |
|
|
545 |
|
| 475 |
lua_register(L, "draw_info", l_draw_info); |
546 |
lua_register(L, "draw_info", l_draw_info); |
| 476 |
lua_register(L, "draw_rect", l_draw_rect); |
547 |
lua_register(L, "draw_rect", l_draw_rect); |
| 477 |
lua_register(L, "draw_text", l_draw_text); |
548 |
lua_register(L, "draw_text", l_draw_text); |
| ... |
| 480 |
lua_register(L, "draw_circle", l_draw_circle); |
551 |
lua_register(L, "draw_circle", l_draw_circle); |
| 481 |
lua_register(L, "draw_ellipse", l_draw_ellipse); |
552 |
lua_register(L, "draw_ellipse", l_draw_ellipse); |
| 482 |
lua_register(L, "draw_triangle", l_draw_triangle); |
553 |
lua_register(L, "draw_triangle", l_draw_triangle); |
| 483 |
|
554 |
lua_register(L, "draw_image", l_draw_image); |
| 484 |
lua_register(L, "load_image", l_load_image); |
|
|
| 485 |
lua_register(L, "load_audio", l_load_audio); |
|
|
| 486 |
|
|
|
| 487 |
lua_register(L, "button_down", l_button_down); |
|
|
| 488 |
lua_register(L, "button_pressed", l_button_pressed); |
|
|
| 489 |
|
555 |
|
| 490 |
// Interpreting and running input file Lua script. |
556 |
// Interpreting and running input file Lua script. |
| 491 |
if (luaL_loadfile(L, run_file) || lua_pcall(L, 0, 0, 0)) { |
557 |
if (luaL_loadfile(L, run_file) || lua_pcall(L, 0, 0, 0)) { |
| 492 |
fprintf(stderr, "Error: %s\n", lua_tostring(L, -1)); |
558 |
TraceLog(LOG_FATAL, "Error: %s\n", lua_tostring(L, -1)); |
| 493 |
return 1; |
559 |
return 1; |
| 494 |
} |
560 |
} |
| 495 |
|
561 |
|
| ... |
|
diff --git a/tests/test.lua b/tests/test.lua
|
| 1 |
math.randomseed(os.time()) |
1 |
math.randomseed(os.time()) |
| 2 |
|
2 |
|
|
|
3 |
open_window(800, 800, "Sample Window") |
|
|
4 |
set_fps(60) |
|
|
5 |
|
| 3 |
test_button_square = { x = 400, y = 400 } |
6 |
test_button_square = { x = 400, y = 400 } |
| 4 |
test_button_color = color.RED |
7 |
test_button_color = color.RED |
| 5 |
test_button_speed = 200 |
8 |
test_button_speed = 200 |
| 6 |
|
|
|
| 7 |
test_camera_position = { x = 0, y = 0 } |
9 |
test_camera_position = { x = 0, y = 0 } |
| 8 |
test_camera_speed = 200 |
10 |
test_camera_speed = 200 |
| 9 |
|
|
|
| 10 |
test_images_asset1 = load_image("tests/icons/icon_1.png") |
11 |
test_images_asset1 = load_image("tests/icons/icon_1.png") |
| 11 |
|
12 |
test_images_asset2 = load_image("tests/icons/icon_2.png") |
| 12 |
open_window(800, 800, "Sample Window") |
|
|
| 13 |
set_fps(60) |
|
|
| 14 |
|
13 |
|
| 15 |
function test_api() |
14 |
function test_api() |
| 16 |
draw_rect(100, 100, 300, 200, color.YELLOW) |
15 |
draw_rect(100, 100, 300, 200, color.YELLOW) |
| ... |
| 26 |
function get_random_color() |
25 |
function get_random_color() |
| 27 |
local keys = {} |
26 |
local keys = {} |
| 28 |
for k in pairs(color) do |
27 |
for k in pairs(color) do |
| 29 |
table.insert(keys, k) -- Collect all keys |
28 |
table.insert(keys, k) |
| 30 |
end |
29 |
end |
| 31 |
local randomKey = keys[math.random(1, #keys)] -- Select a random key |
30 |
local randomKey = keys[math.random(1, #keys)] |
| 32 |
return color[randomKey] -- Return the corresponding color |
31 |
return color[randomKey] |
| 33 |
end |
32 |
end |
| 34 |
|
33 |
|
| 35 |
function test_json() |
34 |
function test_json() |
| ... |
| 140 |
draw_text("This text doesn't move!", 10, 10, 20, color.VIOLET) |
139 |
draw_text("This text doesn't move!", 10, 10, 20, color.VIOLET) |
| 141 |
end |
140 |
end |
| 142 |
|
141 |
|
|
|
142 |
function test_images() |
|
|
143 |
draw_text(string.format("uid: %s", test_images_asset1), 50, 50, 20, color.VIOLET) |
|
|
144 |
draw_text(string.format("uid: %s", test_images_asset2), 50, 80, 20, color.VIOLET) |
|
|
145 |
|
|
|
146 |
draw_image(test_images_asset1, 150, 150) |
|
|
147 |
draw_image(test_images_asset2, 200, 200) |
|
|
148 |
end |
|
|
149 |
|
| 143 |
while window_running() do |
150 |
while window_running() do |
| 144 |
start_drawing() |
151 |
start_drawing() |
| 145 |
clear_window(color.BLACK) |
152 |
clear_window(color.BLACK) |
| ... |
| 148 |
-- test_api() |
155 |
-- test_api() |
| 149 |
-- test_buttons() |
156 |
-- test_buttons() |
| 150 |
-- test_camera() |
157 |
-- test_camera() |
|
|
158 |
-- test_images() |
| 151 |
|
159 |
|
| 152 |
draw_info() |
160 |
draw_info() |
| 153 |
stop_drawing() |
161 |
stop_drawing() |
| ... |