diff options
Diffstat (limited to 'config')
| -rw-r--r-- | config/ecexrc.c | 55 | ||||
| -rw-r--r-- | config/filebrowser_plugin.c | 20 | ||||
| -rw-r--r-- | config/markdown_plugin.c | 126 | ||||
| -rw-r--r-- | config/render_demo.c | 302 | ||||
| -rw-r--r-- | config/tetris.c | 682 |
5 files changed, 1163 insertions, 22 deletions
diff --git a/config/ecexrc.c b/config/ecexrc.c index db4b6b9..1d13f18 100644 --- a/config/ecexrc.c +++ b/config/ecexrc.c @@ -1,8 +1,13 @@ #include "ecex.h" #include "buffers.h" -#define C(x) ((float)(x) / 255.0f) -#define RGB(r, g, b) C(r), C(g), C(b) +#define ECEX_NO_STANDALONE_CONFIG +#include "render_demo.c" +#include "tetris.c" +#include "markdown_plugin.c" +#undef ECEX_NO_STANDALONE_CONFIG + +#define RGB(r, g, b) ECEX_RGB8(r, g, b) /* Gruvbox dark palette */ #define GB_DARK0_HARD RGB(0x1d, 0x20, 0x21) @@ -93,7 +98,23 @@ static int demo_interactive_command(ecex_t *ed) { return 0; } -int ecex_config_init(ecex_t *ed) { +static const ecex_config_command_t config_commands[] = { + {"hello", hello_command}, + {"make-notes", make_notes_command}, + {"demo-interactive", demo_interactive_command}, +}; + +static const ecex_config_keybind_t config_binds[] = { + {"C-h", "hello"}, + {"C-m", "make-notes"}, + {"F2", "list-commands"}, + {"F3", "list-buffers"}, + {"F4", "demo-interactive"}, + {"C-q", "quit"}, + {"C-x h", "beginning-of-buffer"}, +}; + +ECEX_CONFIG_BEGIN ecex_set_font_size(ed, 20.0f); ecex_set_bg_color(ed, GB_DARK0_HARD); @@ -115,28 +136,18 @@ int ecex_config_init(ecex_t *ed) { ecex_set_cursor_color(ed, GB_AQUA); ecex_set_region_bg_color(ed, GB_DARK2); - ecex_register_command(ed, "hello", hello_command); - ecex_register_command(ed, "make-notes", make_notes_command); - ecex_register_command(ed, "demo-interactive", demo_interactive_command); - - ecex_bind_key(ed, "C-h", "hello"); - ecex_bind_key(ed, "C-m", "make-notes"); - ecex_bind_key(ed, "F2", "list-commands"); - ecex_bind_key(ed, "F3", "list-buffers"); - ecex_bind_key(ed, "F4", "demo-interactive"); - ecex_bind_key(ed, "C-q", "quit"); - ecex_bind_key(ed, "C-x h", "beginning-of-buffer"); - - ecex_bind_key(ed, "C-x b n", "next-buffer"); - ecex_bind_key(ed, "C-x b p", "previous-buffer"); + ECEX_CONFIG_COMMANDS(config_commands); + ECEX_CONFIG_BINDS(config_binds); + ECEX_CONFIG_INCLUDE(ecex_render_demo_plugin); + ECEX_CONFIG_INCLUDE(ecex_tetris_plugin); + ECEX_CONFIG_INCLUDE(ecex_markdown_plugin); buffer_t *scratch = ecex_current_buffer(ed); - buffer_insert(scratch, "ecex config loaded with Gruvbox colors!\n"); - buffer_insert(scratch, "Try F1 M-x, C-x C-f, C-x C-s, F2 commands, F3 buffers, F4 demo menu, C-x b n/p.\n"); - buffer_insert(scratch, "Eval live C snippets with C-x C-e (line), C-x e r (marked region), C-x e b (buffer), C-x e f (file).\n"); + buffer_insert(scratch, "ecex config loaded !\n"); + buffer_insert(scratch, "Try F1, C-x C-f, C-x C-s, C-x d file browser, F2 commands, F3 buffers, F4 demo menu, C-x b.\n"); + buffer_insert(scratch, "Run M-x render-demo for a custom renderer demo, or M-x tetris for Tetris.\n"); buffer_insert(scratch, "Example line to edit then C-x C-e: ecex_set_font_size(ed, 28.0f);\n"); buffer_insert(scratch, "Example relative change: ecex_adjust_font_size(ed, 2.0f);\n\n"); - return 0; -} +ECEX_CONFIG_END diff --git a/config/filebrowser_plugin.c b/config/filebrowser_plugin.c new file mode 100644 index 0000000..fb92cef --- /dev/null +++ b/config/filebrowser_plugin.c @@ -0,0 +1,20 @@ +/* Compatibility shim. + + The file browser is now built into the editor core and registered during + startup as these commands: + - file-browser + - file-browser-here + - file-browser-refresh + - file-browser-open + - file-browser-preview + - file-browser-toggle-preview + + Old configs that still include this file can keep doing so safely. */ +#ifndef ECEX_FILEBROWSER_PLUGIN_C +#define ECEX_FILEBROWSER_PLUGIN_C +#include "ecex.h" +static int ecex_filebrowser_plugin_init(ecex_t *ed) { + (void)ed; + return 0; +} +#endif diff --git a/config/markdown_plugin.c b/config/markdown_plugin.c new file mode 100644 index 0000000..ded0e1c --- /dev/null +++ b/config/markdown_plugin.c @@ -0,0 +1,126 @@ +#include "ecex.h" + +#define MD_TEXT_TITLE 1 + +typedef struct md_state { + ecex_t *ed; +} md_state_t; + +static int md_render(ecex_t *ed, buffer_t *buffer, ecex_draw_context_t *ctx, void *userdata) { + md_state_t *state = (md_state_t *)userdata; + int draw_count; + int line_count; + int scroll; + int y; + int h; + int line_h; + int in_code = 0; + int i; + + if (!ed || !buffer || !ctx || !state) return 0; + + draw_count = ecex_var_i32(ed, state, "draw_count", 0) + 1; + ecex_var_i32_set_scalar(ed, state, "draw_count", draw_count); + if (draw_count <= 2 || (draw_count % 120) == 0) { + ecex_log_int("markdown_plugin_draw: count=", draw_count); + } + + line_count = ecex_buffer_line_count_i(buffer); + ecex_log_int("markdown_plugin_draw: line_count=", line_count); + if (line_count < 0) line_count = 0; + + ecex_text_set_buffer_title(ed, state, MD_TEXT_TITLE, buffer); + ecex_draw_markdown_canvas_auto_i(ctx, state, MD_TEXT_TITLE); + + y = ecex_markdown_body_y_i(ctx); + h = ecex_draw_context_height_i(ctx); + line_h = ecex_draw_context_line_height_i(ctx); + scroll = ecex_buffer_scroll_line(buffer); + if (scroll < 0) scroll = 0; + if (scroll > line_count) scroll = line_count; + + for (i = scroll; i < line_count && y < h - line_h; ++i) { + int packed; + int advance; + if (i < scroll + 4) ecex_log_int("markdown_plugin_draw: host line=", i); + packed = ecex_markdown_draw_line_from_buffer_i(ctx, state, buffer, i, y, in_code); + in_code = (packed & 0x10000) ? 1 : 0; + advance = packed & 0xffff; + if (advance <= 0) advance = line_h; + y += advance; + } + + return 0; +} + +static void md_free_state(void *userdata) { + md_state_t *state = (md_state_t *)userdata; + ecex_t *ed; + if (!state) return; + ed = state->ed; + ecex_text_free_owner(ed, state); + ecex_var_free_owner(ed, state); + ecex_object_free(ed, state); +} + +static int md_view_buffer(ecex_t *ed, buffer_t *buffer) { + md_state_t *state; + if (!ed || !buffer) return -1; + if (ecex_buffer_has_renderer(buffer)) ecex_buffer_clear_renderer(buffer); + + state = (md_state_t *)ecex_object_calloc(ed, 1, sizeof(*state)); + if (!state) return -1; + state->ed = ed; + + if (ecex_buffer_set_renderer(buffer, md_render, state, md_free_state, ECEX_RENDER_REPLACE_CONTENT) != 0) { + ecex_object_free(ed, state); + return -1; + } + ecex_buffer_set_major_mode_by_name(ed, buffer, "markdown-mode"); + return 0; +} + +static int md_file_handler(ecex_t *ed, buffer_t *buffer) { + if (!buffer) return -1; + if (ecex_buffer_has_renderer(buffer)) return 0; + return md_view_buffer(ed, buffer); +} + +static int cmd_markdown_view(ecex_t *ed) { + return md_view_buffer(ed, ecex_current_buffer(ed)); +} + +static int cmd_markdown_source(ecex_t *ed) { + buffer_t *buffer = ecex_current_buffer(ed); + if (!buffer) return -1; + if (ecex_buffer_has_renderer(buffer)) ecex_buffer_clear_renderer(buffer); + ecex_buffer_set_major_mode_by_name(ed, buffer, "markdown-mode"); + return 0; +} + +static int cmd_markdown_toggle(ecex_t *ed) { + buffer_t *buffer = ecex_current_buffer(ed); + if (!buffer) return -1; + if (ecex_buffer_has_renderer(buffer)) return cmd_markdown_source(ed); + return md_view_buffer(ed, buffer); +} + +int ecex_markdown_plugin(ecex_t *ed) { + ECEX_CONFIG_MODE("markdown-mode"); + ECEX_CONFIG_COMMAND("markdown-view", cmd_markdown_view); + ECEX_CONFIG_COMMAND("markdown-source", cmd_markdown_source); + ECEX_CONFIG_COMMAND("markdown-toggle", cmd_markdown_toggle); + ECEX_CONFIG_MODE_BIND("markdown-mode", "C-c C-c", "markdown-toggle"); + ECEX_CONFIG_MODE_BIND("markdown-mode", "C-c C-s", "markdown-source"); + ecex_register_file_handler(ed, ".md", md_file_handler); + ecex_register_file_handler(ed, ".markdown", md_file_handler); + ecex_register_file_handler(ed, ".mdown", md_file_handler); + ecex_register_file_handler(ed, ".mkd", md_file_handler); + return 0; +} + +#ifndef ECEX_NO_STANDALONE_CONFIG +ECEX_CONFIG_BEGIN + ECEX_CONFIG_INCLUDE(ecex_markdown_plugin); +ECEX_CONFIG_END +#endif diff --git a/config/render_demo.c b/config/render_demo.c new file mode 100644 index 0000000..761b552 --- /dev/null +++ b/config/render_demo.c @@ -0,0 +1,302 @@ +#include "ecex.h" + +#define RENDER_DEMO_BUF "*render-demo*" +#define RENDER_DEMO_VAR_X "x_milli" +#define RENDER_DEMO_VAR_Y "y_milli" +#define RENDER_DEMO_VAR_TARGET_X "target_x_milli" +#define RENDER_DEMO_VAR_TARGET_Y "target_y_milli" +#define RENDER_DEMO_VAR_MOVING "moving" +#define RENDER_DEMO_VAR_BOX_X "box_x" +#define RENDER_DEMO_VAR_BOX_Y "box_y" +#define RENDER_DEMO_VAR_BOX_SIZE "box_size" +#define RENDER_DEMO_VAR_TRAVEL_X "travel_x" +#define RENDER_DEMO_VAR_TRAVEL_Y "travel_y" +#define RENDER_DEMO_VAR_AREA_X "area_x" +#define RENDER_DEMO_VAR_AREA_Y "area_y" + +#define RENDER_DEMO_LABEL_TITLE 20 +#define RENDER_DEMO_LABEL_SUBTITLE 21 +#define RENDER_DEMO_LABEL_SAFE_DRAW 22 +#define RENDER_DEMO_LABEL_ANIM 23 +#define RENDER_DEMO_LABEL_POSITION 24 +#define RENDER_DEMO_LABEL_CLICK 25 + +typedef struct render_demo_state { + ecex_t *ed; + int log_draw_count; +} render_demo_state_t; + +static int render_demo_get(ecex_t *ed, render_demo_state_t *s, const char *name, int fallback) { + if (!ed || !s || !name) return fallback; + return ecex_var_i32(ed, s, name, fallback); +} + +static void render_demo_set(ecex_t *ed, render_demo_state_t *s, const char *name, int value) { + if (!ed || !s || !name) return; + ecex_var_i32_set_scalar(ed, s, name, value); +} + +static void render_demo_reset(ecex_t *ed, render_demo_state_t *s) { + ecex_log_ptr("render_demo_reset: state=", s); + if (!ed || !s) return; + + /* Store mutable demo state in the host variable registry. This mirrors the + * Tetris plugin flow and avoids relying on CCDJIT struct-field writes for + * values that must survive renderer, animation, and mouse callbacks. + * Values are fixed-point integers: 0..1000 represents the available travel + * range inside the demo frame. */ + render_demo_set(ed, s, RENDER_DEMO_VAR_X, 0); + render_demo_set(ed, s, RENDER_DEMO_VAR_Y, 500); + render_demo_set(ed, s, RENDER_DEMO_VAR_TARGET_X, 0); + render_demo_set(ed, s, RENDER_DEMO_VAR_TARGET_Y, 500); + render_demo_set(ed, s, RENDER_DEMO_VAR_MOVING, 0); + s->log_draw_count = 0; +} + +static int render_demo_draw(ecex_t *ed, buffer_t *buffer, ecex_draw_context_t *ctx, void *userdata) { + render_demo_state_t *s = (render_demo_state_t *)userdata; + int w; + int h; + int cx; + int cy; + int cw; + int ch; + int line_h; + int box; + int travel; + int x_milli; + int y_milli; + int box_x; + int box_y; + int travel_y; + int area_x; + int area_y; + + (void)buffer; + if (!ed || !ctx || !s) return 0; + + s->log_draw_count++; + if (s->log_draw_count <= 4 || (s->log_draw_count % 60) == 0) { + ecex_log_int("render_demo_draw: count=", s->log_draw_count); + } + + w = (int)ctx->w; + h = (int)ctx->h; + cx = (int)ctx->content_x; + cy = (int)ctx->content_y; + cw = (int)ctx->content_w; + ch = (int)ctx->content_h; + line_h = (int)ctx->line_height; + if (line_h < 18) line_h = 18; + if (cw < 1) cw = w - cx * 2; + if (ch < 1) ch = h - cy * 2; + if (cw < 1) cw = 1; + if (ch < 1) ch = 1; + + ecex_draw_color_rgba8(ctx, 26, 28, 36, 255); + ecex_draw_rect_i(ctx, 0, 0, w, h); + + ecex_draw_color_rgba8(ctx, 48, 96, 180, 255); + ecex_draw_rect_i(ctx, cx, cy, (cw * 55) / 100, line_h * 2); + + ecex_draw_color_rgba8(ctx, 242, 230, 191, 255); + ecex_draw_rect_outline_i(ctx, cx, cy, cw, ch, 2); + ecex_draw_label_i(ctx, cx + 12, cy + 10, RENDER_DEMO_LABEL_TITLE); + + ecex_draw_color_rgba8(ctx, 229, 89, 64, 255); + ecex_draw_line_i(ctx, cx, cy + ch, cx + cw, cy, 3); + + ecex_draw_color_rgba8(ctx, 160, 230, 180, 255); + ecex_draw_label_i(ctx, cx + 12, cy + line_h * 3, RENDER_DEMO_LABEL_SUBTITLE); + ecex_draw_label_i(ctx, cx + 12, cy + line_h * 4, RENDER_DEMO_LABEL_SAFE_DRAW); + ecex_draw_label_i(ctx, cx + 12, cy + line_h * 5, RENDER_DEMO_LABEL_ANIM); + ecex_draw_label_i(ctx, cx + 12, cy + line_h * 6, RENDER_DEMO_LABEL_CLICK); + + x_milli = render_demo_get(ed, s, RENDER_DEMO_VAR_X, 0); + y_milli = render_demo_get(ed, s, RENDER_DEMO_VAR_Y, 500); + ecex_draw_stat_i(ctx, cx + 12, cy + line_h * 8, RENDER_DEMO_LABEL_POSITION, x_milli); + + box = line_h * 2; + if (box < 36) box = 36; + if (box > 72) box = 72; + travel = cw - box - 24; + if (travel < 0) travel = 0; + area_x = cx + 12; + area_y = cy + line_h * 10; + travel_y = ch - (area_y - cy) - box - 12; + if (travel_y < 0) travel_y = 0; + if (x_milli < 0) x_milli = 0; + if (x_milli > 1000) x_milli = 1000; + if (y_milli < 0) y_milli = 0; + if (y_milli > 1000) y_milli = 1000; + box_x = area_x + (travel * x_milli) / 1000; + box_y = area_y + (travel_y * y_milli) / 1000; + + render_demo_set(ed, s, RENDER_DEMO_VAR_BOX_X, box_x); + render_demo_set(ed, s, RENDER_DEMO_VAR_BOX_Y, box_y); + render_demo_set(ed, s, RENDER_DEMO_VAR_BOX_SIZE, box); + render_demo_set(ed, s, RENDER_DEMO_VAR_TRAVEL_X, travel); + render_demo_set(ed, s, RENDER_DEMO_VAR_TRAVEL_Y, travel_y); + render_demo_set(ed, s, RENDER_DEMO_VAR_AREA_X, area_x); + render_demo_set(ed, s, RENDER_DEMO_VAR_AREA_Y, area_y); + + ecex_draw_color_rgba8(ctx, 70, 76, 90, 255); + ecex_draw_rect_outline_i(ctx, area_x, area_y, travel + box, travel_y + box, 1); + + ecex_draw_color_rgba8(ctx, 89, 242, 140, 255); + ecex_draw_rect_i(ctx, box_x, box_y, box, box); + ecex_draw_color_rgba8(ctx, 20, 24, 30, 255); + ecex_draw_rect_outline_i(ctx, box_x, box_y, box, box, 2); + + return 0; +} + + +static int render_demo_clamp_milli(int value) { + if (value < 0) return 0; + if (value > 1000) return 1000; + return value; +} + +static int render_demo_delta_step(int current, int target) { + int delta = target - current; + int step; + if (delta < 0) { + if (delta >= -4) return target; + step = delta / 5; + if (step == 0) step = -1; + return current + step; + } + if (delta <= 4) return target; + step = delta / 5; + if (step == 0) step = 1; + return current + step; +} + +static int render_demo_tick(ecex_t *ed, buffer_t *buffer, int now_ms, void *userdata) { + render_demo_state_t *s = (render_demo_state_t *)userdata; + int x; + int y; + int target_x; + int target_y; + int next_x; + int next_y; + + (void)buffer; + (void)now_ms; + if (!ed || !s) return 0; + if (!render_demo_get(ed, s, RENDER_DEMO_VAR_MOVING, 0)) return 0; + + x = render_demo_get(ed, s, RENDER_DEMO_VAR_X, 0); + y = render_demo_get(ed, s, RENDER_DEMO_VAR_Y, 500); + target_x = render_demo_get(ed, s, RENDER_DEMO_VAR_TARGET_X, x); + target_y = render_demo_get(ed, s, RENDER_DEMO_VAR_TARGET_Y, y); + + next_x = render_demo_delta_step(x, target_x); + next_y = render_demo_delta_step(y, target_y); + render_demo_set(ed, s, RENDER_DEMO_VAR_X, render_demo_clamp_milli(next_x)); + render_demo_set(ed, s, RENDER_DEMO_VAR_Y, render_demo_clamp_milli(next_y)); + + if (next_x == target_x && next_y == target_y) { + render_demo_set(ed, s, RENDER_DEMO_VAR_MOVING, 0); + ecex_log("render_demo_tick: target reached"); + } + return 1; +} + +static int render_demo_mouse(ecex_t *ed, buffer_t *buffer, int event, int x, int y, int button, void *userdata) { + render_demo_state_t *s = (render_demo_state_t *)userdata; + int box; + int area_x; + int area_y; + int travel_x; + int travel_y; + int nx; + int ny; + + (void)buffer; + if (!ed || !s || button != ECEX_MOUSE_BUTTON_LEFT) return 0; + if (event != ECEX_MOUSE_PRESS) return 0; + + box = render_demo_get(ed, s, RENDER_DEMO_VAR_BOX_SIZE, 0); + area_x = render_demo_get(ed, s, RENDER_DEMO_VAR_AREA_X, 0); + area_y = render_demo_get(ed, s, RENDER_DEMO_VAR_AREA_Y, 0); + travel_x = render_demo_get(ed, s, RENDER_DEMO_VAR_TRAVEL_X, 0); + travel_y = render_demo_get(ed, s, RENDER_DEMO_VAR_TRAVEL_Y, 0); + if (box <= 0) return 0; + + if (x < area_x || x >= area_x + travel_x + box || + y < area_y || y >= area_y + travel_y + box) { + return 0; + } + + nx = x - area_x - box / 2; + ny = y - area_y - box / 2; + if (travel_x <= 0) render_demo_set(ed, s, RENDER_DEMO_VAR_TARGET_X, 0); + else render_demo_set(ed, s, RENDER_DEMO_VAR_TARGET_X, render_demo_clamp_milli((nx * 1000) / travel_x)); + if (travel_y <= 0) render_demo_set(ed, s, RENDER_DEMO_VAR_TARGET_Y, 0); + else render_demo_set(ed, s, RENDER_DEMO_VAR_TARGET_Y, render_demo_clamp_milli((ny * 1000) / travel_y)); + + render_demo_set(ed, s, RENDER_DEMO_VAR_MOVING, 1); + ecex_log("render_demo_mouse: target set"); + return 1; +} + +static void render_demo_free_state(void *userdata) { + render_demo_state_t *s = (render_demo_state_t *)userdata; + if (!s) return; + ecex_var_free_owner(s->ed, s); + ecex_config_free(s); +} + +static int cmd_render_demo(ecex_t *ed) { + buffer_t *buffer; + render_demo_state_t *s; + + ecex_log("cmd_render_demo: enter"); + if (!ed) return -1; + + buffer = ecex_find_buffer(ed, RENDER_DEMO_BUF); + if (!buffer) buffer = ecex_create_interactive_buffer(ed, RENDER_DEMO_BUF); + if (!buffer) return -1; + + s = (render_demo_state_t *)ecex_buffer_renderer_userdata(buffer); + if (!s) { + s = (render_demo_state_t *)ecex_config_calloc(1, sizeof(*s)); + if (!s) return -1; + s->ed = ed; + render_demo_reset(ed, s); + + if (ecex_buffer_set_renderer(buffer, render_demo_draw, s, render_demo_free_state, ECEX_RENDER_REPLACE_CONTENT) != 0) { + render_demo_free_state(s); + return -1; + } + if (ecex_buffer_set_mouse_handler(buffer, render_demo_mouse, s, 0) != 0) { + ecex_buffer_clear_renderer(buffer); + return -1; + } + if (ecex_buffer_set_animation_ms(buffer, render_demo_tick, s, 0, 60) != 0) { + ecex_buffer_clear_mouse_handler(buffer); + ecex_buffer_clear_renderer(buffer); + return -1; + } + } + + if (ecex_buffer_replace_text(buffer, "Render demo. The renderer replaces normal buffer content.\n") != 0) { + return -1; + } + ecex_buffer_set_modified(buffer, 0); + + return ecex_switch_buffer(ed, RENDER_DEMO_BUF); +} + +int ecex_render_demo_plugin(ecex_t *ed) { + ECEX_CONFIG_COMMAND("render-demo", cmd_render_demo); + return 0; +} + +#ifndef ECEX_NO_STANDALONE_CONFIG +ECEX_CONFIG_BEGIN + ECEX_CONFIG_INCLUDE(ecex_render_demo_plugin); +ECEX_CONFIG_END +#endif diff --git a/config/tetris.c b/config/tetris.c new file mode 100644 index 0000000..542963a --- /dev/null +++ b/config/tetris.c @@ -0,0 +1,682 @@ +#include "ecex.h" + +#define TETRIS_W 10 +#define TETRIS_H 20 +#define TETRIS_CELLS (TETRIS_W * TETRIS_H) +#define TETRIS_BUF "*tetris*" +#define TETRIS_MODE "tetris-mode" +#define TETRIS_VAR_BOARD "board" +#define TETRIS_VAR_NEXT "next_piece" + +typedef struct tetris_state { + ecex_t *ed; + int *board; + int piece; + int rot; + int x; + int y; + int next_piece; + unsigned int rng; + int score; + int lines; + int level; + int game_over; + int paused; + int last_drop_ms; + int drop_interval_ms; + int log_tick_count; + int log_draw_count; + int log_state_id; +} tetris_state_t; + +static int tetris_idx(int x, int y) { + return y * TETRIS_W + x; +} + +static int tetris_shape_cell(int piece, int rot, int col, int row) { + return ecex_tetris_shape_cell(piece, rot, col, row); +} + +static int *tetris_board(ecex_t *ed, tetris_state_t *s) { + if (!ed || !s) return 0; + return (int *)ecex_var_get_or_alloc(ed, s, TETRIS_VAR_BOARD, (size_t)TETRIS_CELLS, sizeof(int)); +} + +static int tetris_next_piece(ecex_t *ed, tetris_state_t *s) { + return ecex_var_i32(ed, s, TETRIS_VAR_NEXT, 0); +} + +static void tetris_set_next_piece(ecex_t *ed, tetris_state_t *s, int piece) { + if (piece < 0) piece = 0; + if (piece > 6) piece = piece % 7; + ecex_var_i32_set_scalar(ed, s, TETRIS_VAR_NEXT, piece); +} + +static void tetris_clear_board(ecex_t *ed, tetris_state_t *s) { + int *board; + if (!s) { ecex_log("tetris_clear_board: null state"); return; } + board = tetris_board(ed, s); + ecex_log_ptr("tetris_clear_board: board=", board); + ecex_mem_zero(board, (size_t)TETRIS_CELLS * sizeof(int)); + ecex_log("tetris_clear_board: done"); +} + +static int tetris_pick(tetris_state_t *s) { + int piece; + if (!s) { ecex_log("tetris_pick: null state"); return 0; } + piece = ecex_random_bounded(7); + if (piece < 0) piece = 0; + if (piece > 6) piece = piece % 7; + ecex_log_int("tetris_pick: piece=", piece); + return piece; +} + +static int tetris_collides(ecex_t *ed, tetris_state_t *s, int piece, int rot, int px, int py) { + int r; + int c; + + int *board = tetris_board(ed, s); + if (!s || !board) return 1; + for (r = 0; r < 4; ++r) { + for (c = 0; c < 4; ++c) { + int x; + int y; + if (!tetris_shape_cell(piece, rot, c, r)) continue; + + x = px + c; + y = py + r; + if (x < 0 || x >= TETRIS_W || y >= TETRIS_H) return 1; + if (y >= 0 && ecex_i32_get(board, (size_t)tetris_idx(x, y))) return 1; + } + } + + return 0; +} + +static void tetris_spawn(ecex_t *ed, tetris_state_t *s) { + int queued; + + ecex_log_ptr("tetris_spawn: state=", s); + if (!s) return; + + /* The sidebar must show the piece that will become active on the next + * spawn. Keep that queued value in the host variable registry, then consume + * it here and immediately replace it with a freshly-picked preview. */ + queued = tetris_next_piece(ed, s); + if (queued < 0 || queued > 6) queued = tetris_pick(s); + s->piece = queued; + tetris_set_next_piece(ed, s, tetris_pick(s)); + + ecex_log_int("tetris_spawn: current_piece=", s->piece); + ecex_log_int("tetris_spawn: preview_piece=", tetris_next_piece(ed, s)); + + s->rot = 0; + s->x = 3; + s->y = -1; + s->last_drop_ms = 0; + + if (tetris_collides(ed, s, s->piece, s->rot, s->x, s->y)) { + ecex_log("tetris_spawn: immediate collision -> game over"); + s->game_over = 1; + } +} + +static void tetris_reset(ecex_t *ed, tetris_state_t *s) { + ecex_log_ptr("tetris_reset: state=", s); + if (!s) return; + + tetris_clear_board(ed, s); + ecex_log("tetris_reset: after clear"); + /* Avoid calling libc/time-returning host functions from CCDJIT plugin code. + * Seed from stable host-owned addresses instead; good enough for a toy game + * and much safer for the tiny JIT ABI. */ + s->rng = 0x9e3779b9u ^ (unsigned int)(size_t)s ^ (unsigned int)(size_t)tetris_board(ed, s); + ecex_log_int("tetris_reset: rng=", (int)s->rng); + s->score = 0; + s->lines = 0; + s->level = 1; + s->game_over = 0; + s->paused = 0; + s->last_drop_ms = 0; + s->drop_interval_ms = 650; + tetris_set_next_piece(ed, s, tetris_pick(s)); + s->log_tick_count = 0; + s->log_draw_count = 0; + ecex_log("tetris_reset: spawning first piece"); + tetris_spawn(ed, s); + ecex_log("tetris_reset: done"); +} + +static void tetris_lock(ecex_t *ed, tetris_state_t *s) { + ecex_log_ptr("tetris_lock: state=", s); + int r; + int c; + int y; + int cleared = 0; + int *board; + + if (!s) return; + board = tetris_board(ed, s); + if (!board) return; + + for (r = 0; r < 4; ++r) { + for (c = 0; c < 4; ++c) { + int x; + int yy; + if (!tetris_shape_cell(s->piece, s->rot, c, r)) continue; + + x = s->x + c; + yy = s->y + r; + if (x >= 0 && x < TETRIS_W && yy >= 0 && yy < TETRIS_H) { + ecex_i32_set(board, (size_t)tetris_idx(x, yy), s->piece + 1); + } + } + } + + for (y = TETRIS_H - 1; y >= 0; --y) { + int x; + int full = 1; + for (x = 0; x < TETRIS_W; ++x) { + if (!ecex_i32_get(board, (size_t)tetris_idx(x, y))) { + full = 0; + break; + } + } + + if (!full) continue; + ++cleared; + + { + int yy; + for (yy = y; yy > 0; --yy) { + for (x = 0; x < TETRIS_W; ++x) { + ecex_i32_set(board, (size_t)tetris_idx(x, yy), ecex_i32_get(board, (size_t)tetris_idx(x, yy - 1))); + } + } + } + + for (x = 0; x < TETRIS_W; ++x) ecex_i32_set(board, (size_t)tetris_idx(x, 0), 0); + ++y; + } + + if (cleared) { + int points = 0; + if (cleared == 1) points = 100; + else if (cleared == 2) points = 300; + else if (cleared == 3) points = 500; + else points = 800; + + s->score += points * s->level; + s->lines += cleared; + s->level = 1 + s->lines / 10; + s->drop_interval_ms = 650 - (s->level - 1) * 45; + if (s->drop_interval_ms < 80) s->drop_interval_ms = 80; + } + + ecex_log_int("tetris_lock: cleared=", cleared); + tetris_spawn(ed, s); +} + + +static int tetris_piece_has_visible_cells(tetris_state_t *s) { + int r; + int c; + + if (!s) return 0; + for (r = 0; r < 4; ++r) { + for (c = 0; c < 4; ++c) { + int y; + if (!tetris_shape_cell(s->piece, s->rot, c, r)) continue; + y = s->y + r; + if (y >= 0) return 1; + } + } + return 0; +} + +static int tetris_top_out_if_hidden(tetris_state_t *s, const char *where) { + if (!s) return 1; + if (tetris_piece_has_visible_cells(s)) return 0; + ecex_log(where); + ecex_log_int("tetris_top_out: piece=", s->piece); + ecex_log_int("tetris_top_out: y=", s->y); + s->game_over = 1; + return 1; +} + +static int tetris_soft_drop(ecex_t *ed, tetris_state_t *s) { + if (!s) { ecex_log("tetris_soft_drop: null state"); return 0; } + if (s->game_over || s->paused) return 0; + + if (!tetris_collides(ed, s, s->piece, s->rot, s->x, s->y + 1)) { + ++s->y; + return 1; + } + + ecex_log_int("tetris_soft_drop: locking piece=", s->piece); + ecex_log_int("tetris_soft_drop: y=", s->y); + if (tetris_top_out_if_hidden(s, "tetris_soft_drop: hidden lock -> game over")) return 1; + tetris_lock(ed, s); + return 0; +} + + +static int tetris_move_horizontal(ecex_t *ed, tetris_state_t *s, int dx) { + int old_x; + int old_y; + int old_rot; + + if (!s) { ecex_log("tetris_move_horizontal: null state"); return 0; } + if (s->game_over || s->paused) return 0; + + old_x = s->x; + old_y = s->y; + old_rot = s->rot; + + if (!tetris_collides(ed, s, s->piece, s->rot, old_x + dx, old_y)) { + s->x = old_x + dx; + /* Horizontal movement must never vertical-kick the active piece. + * Keep these assignments explicit because plugin/JIT callback bugs are + * otherwise very hard to distinguish from game movement. */ + s->y = old_y; + s->rot = old_rot; + + /* Restart the gravity phase after manual lateral input. This avoids the + * visual "bop" where a key repeat lands on the same frame as gravity + * and appears to couple side movement with a vertical step. */ + s->last_drop_ms = 0; + ecex_log_int("tetris_move_horizontal: x=", s->x); + ecex_log_int("tetris_move_horizontal: y=", s->y); + return 1; + } + + s->x = old_x; + s->y = old_y; + s->rot = old_rot; + return 0; +} + +static void tetris_hard_drop(ecex_t *ed, tetris_state_t *s) { + int moved = 0; + if (!s || s->game_over || s->paused) return; + + while (!tetris_collides(ed, s, s->piece, s->rot, s->x, s->y + 1)) { + ++s->y; + ++moved; + } + + s->score += moved * 2; + if (tetris_top_out_if_hidden(s, "tetris_hard_drop: hidden lock -> game over")) return; + tetris_lock(ed, s); +} + +static tetris_state_t *tetris_state_for_ed(ecex_t *ed) { + buffer_t *buffer; + tetris_state_t *s; + ecex_log("tetris_state_for_ed: enter"); + buffer = ecex_find_buffer(ed, TETRIS_BUF); + ecex_log_ptr("tetris_state_for_ed: buffer=", buffer); + if (!buffer) return 0; + s = (tetris_state_t *)ecex_buffer_renderer_userdata(buffer); + ecex_log_ptr("tetris_state_for_ed: state=", s); + return s; +} + +static int tetris_alpha8(int alpha) { + if (alpha < 0) return 0; + if (alpha > 255) return 255; + return alpha; +} + +static void tetris_color(ecex_draw_context_t *ctx, int cell, int alpha) { + alpha = tetris_alpha8(alpha); + if (cell == 1) ecex_draw_color_rgba8(ctx, 51, 191, 242, alpha); + else if (cell == 2) ecex_draw_color_rgba8(ctx, 242, 217, 51, alpha); + else if (cell == 3) ecex_draw_color_rgba8(ctx, 179, 89, 242, alpha); + else if (cell == 4) ecex_draw_color_rgba8(ctx, 77, 217, 89, alpha); + else if (cell == 5) ecex_draw_color_rgba8(ctx, 242, 64, 64, alpha); + else if (cell == 6) ecex_draw_color_rgba8(ctx, 64, 102, 242, alpha); + else if (cell == 7) ecex_draw_color_rgba8(ctx, 242, 140, 51, alpha); + else ecex_draw_color_rgba8(ctx, 41, 41, 46, alpha); +} + +static void tetris_draw_piece(ecex_draw_context_t *ctx, + int piece, + int rot, + int px, + int py, + int ox, + int oy, + int cell, + int alpha) { + int r; + int c; + + for (r = 0; r < 4; ++r) { + for (c = 0; c < 4; ++c) { + int bx; + int by; + if (!tetris_shape_cell(piece, rot, c, r)) continue; + + bx = px + c; + by = py + r; + if (by < 0) continue; + tetris_color(ctx, piece + 1, alpha); + ecex_draw_rect_i(ctx, ox + bx * cell + 1, oy + by * cell + 1, cell - 2, cell - 2); + } + } +} + +static int tetris_tick(ecex_t *ed, buffer_t *buffer, int now_ms, void *userdata) { + tetris_state_t *s = (tetris_state_t *)userdata; + (void)ed; + (void)buffer; + + if (!s) { ecex_log("tetris_tick: null state"); return 0; } + s->log_tick_count += 1; + if (s->log_tick_count <= 8 || (s->log_tick_count % 60) == 0) { + ecex_log_int("tetris_tick: count=", s->log_tick_count); + ecex_log_int("tetris_tick: now_ms=", now_ms); + } + + if (s->last_drop_ms <= 0) { + s->last_drop_ms = now_ms; + return 1; + } + + if (!s->game_over && !s->paused && now_ms - s->last_drop_ms >= s->drop_interval_ms) { + ecex_log("tetris_tick: dropping piece"); + tetris_soft_drop(ed, s); + s->last_drop_ms = now_ms; + return 1; + } + + return 0; +} + +static int tetris_draw(ecex_t *ed, buffer_t *buffer, ecex_draw_context_t *ctx, void *userdata) { + tetris_state_t *s = (tetris_state_t *)userdata; + int max_board_w; + int max_board_h; + int cell; + int board_w; + int board_h; + int ox; + int oy; + int sx; + int sy; + int x; + int y; + int *board; + (void)ed; + (void)buffer; + + if (!s) { ecex_log("tetris_draw: null state"); return 0; } + if (!ctx) { ecex_log("tetris_draw: null ctx"); return 0; } + board = tetris_board(ed, s); + if (!board) { ecex_log("tetris_draw: no registry board"); return 0; } + s->log_draw_count += 1; + if (s->log_draw_count <= 8 || (s->log_draw_count % 60) == 0) { + ecex_log_int("tetris_draw: count=", s->log_draw_count); + ecex_log_int("tetris_draw: ctx_w=", (int)ctx->w); + ecex_log_int("tetris_draw: ctx_h=", (int)ctx->h); + } + + if (s->log_draw_count <= 3) ecex_log("tetris_draw: background"); + ecex_draw_color_rgba8(ctx, 20, 23, 28, 255); + ecex_draw_rect_i(ctx, 0, 0, (int)ctx->w, (int)ctx->h); + + max_board_w = ((int)ctx->content_w * 62) / 100; + max_board_h = (int)ctx->content_h - 24; + cell = max_board_w / TETRIS_W; + if (cell * TETRIS_H > max_board_h) cell = max_board_h / TETRIS_H; + if (cell < 6) cell = 6; + + board_w = cell * TETRIS_W; + board_h = cell * TETRIS_H; + ox = (int)ctx->content_x + 12; + oy = (int)ctx->content_y + 12; + + if (s->log_draw_count <= 3) ecex_log("tetris_draw: board frame"); + ecex_draw_color_rgba8(ctx, 8, 9, 12, 255); + ecex_draw_rect_i(ctx, ox - 6, oy - 6, board_w + 12, board_h + 12); + ecex_draw_color_rgba8(ctx, 97, 102, 115, 255); + ecex_draw_rect_outline_i(ctx, ox - 6, oy - 6, board_w + 12, board_h + 12, 2); + + if (s->log_draw_count <= 3) ecex_log("tetris_draw: cells"); + for (y = 0; y < TETRIS_H; ++y) { + for (x = 0; x < TETRIS_W; ++x) { + int cell_value = ecex_i32_get(board, (size_t)tetris_idx(x, y)); + tetris_color(ctx, cell_value, cell_value ? 255 : 140); + ecex_draw_rect_i(ctx, ox + x * cell + 1, oy + y * cell + 1, cell - 2, cell - 2); + } + } + + if (s->log_draw_count <= 3) ecex_log("tetris_draw: active piece"); + tetris_draw_piece(ctx, s->piece, s->rot, s->x, s->y, ox, oy, cell, 255); + + sx = ox + board_w + 28; + sy = oy; + + if (s->log_draw_count <= 3) ecex_log("tetris_draw: sidebar"); + ecex_draw_color_rgba8(ctx, 235, 235, 214, 255); + ecex_log("tetris_draw: sidebar title"); + ecex_draw_label_i(ctx, sx, sy, 1); + sy += ((int)ctx->line_height * 16) / 10; + + ecex_draw_stat_i(ctx, sx, sy, 2, s->score); + sy += (int)ctx->line_height; + + ecex_draw_stat_i(ctx, sx, sy, 3, s->lines); + sy += (int)ctx->line_height; + + ecex_draw_stat_i(ctx, sx, sy, 4, s->level); + sy += ((int)ctx->line_height * 15) / 10; + + ecex_draw_label_i(ctx, sx, sy, 5); + sy += ((int)ctx->line_height * 8) / 10; + ecex_log_int("tetris_draw: preview_piece=", tetris_next_piece(ed, s)); + ecex_draw_tetris_preview_i(ctx, tetris_next_piece(ed, s), sx, sy, (cell * 72) / 100, 255); + sy += cell * 4; + + ecex_draw_color_rgba8(ctx, 184, 189, 199, 255); + ecex_log("tetris_draw: sidebar help"); + ecex_draw_label_i(ctx, sx, sy, 6); sy += (int)ctx->line_height; + ecex_draw_label_i(ctx, sx, sy, 7); sy += (int)ctx->line_height; + ecex_draw_label_i(ctx, sx, sy, 8); sy += (int)ctx->line_height; + ecex_draw_label_i(ctx, sx, sy, 9); sy += (int)ctx->line_height; + ecex_draw_label_i(ctx, sx, sy, 10); sy += (int)ctx->line_height; + ecex_draw_label_i(ctx, sx, sy, 11); + + if (s->paused || s->game_over) { + ecex_draw_color_rgba8(ctx, 0, 0, 0, 174); + ecex_draw_rect_i(ctx, ox, oy + (board_h * 43) / 100, board_w, ((int)ctx->line_height * 22) / 10); + ecex_draw_color_rgba8(ctx, 255, 235, 89, 255); + ecex_draw_label_i(ctx, ox + cell, oy + (board_h * 43) / 100 + ((int)ctx->line_height * 55) / 100, s->game_over ? 12 : 13); + } + + if (s->log_draw_count <= 3) ecex_log("tetris_draw: done"); + return 0; +} + +static void tetris_free_state(void *userdata) { + tetris_state_t *s = (tetris_state_t *)userdata; + if (!s) return; + ecex_var_free_owner(s->ed, s); + ecex_config_free(s); +} + +static int cmd_tetris(ecex_t *ed) { + buffer_t *buffer; + tetris_state_t *s; + + ecex_log("cmd_tetris: enter"); + ecex_log_ptr("cmd_tetris: ed=", ed); + if (!ed) { ecex_log("cmd_tetris: no editor"); return -1; } + + buffer = ecex_find_buffer(ed, TETRIS_BUF); + ecex_log_ptr("cmd_tetris: existing buffer=", buffer); + if (!buffer) { + ecex_log("cmd_tetris: creating interactive buffer"); + buffer = ecex_create_interactive_buffer(ed, TETRIS_BUF); + } + ecex_log_ptr("cmd_tetris: buffer=", buffer); + if (!buffer) { ecex_log("cmd_tetris: buffer creation failed"); return -1; } + + s = (tetris_state_t *)ecex_buffer_renderer_userdata(buffer); + ecex_log_ptr("cmd_tetris: existing state=", s); + if (!s) { + ecex_log_int("cmd_tetris: allocating state bytes=", (int)sizeof(*s)); + s = (tetris_state_t *)ecex_config_calloc(1, sizeof(*s)); + ecex_log_ptr("cmd_tetris: allocated state=", s); + if (!s) { ecex_log("cmd_tetris: allocation failed"); return -1; } + s->ed = ed; + ecex_log_int("cmd_tetris: registry board bytes=", (int)((size_t)TETRIS_CELLS * sizeof(int))); + ecex_log_ptr("cmd_tetris: registry board=", tetris_board(ed, s)); + if (!tetris_board(ed, s)) { + ecex_log("cmd_tetris: registry board allocation failed"); + ecex_var_free_owner(ed, s); + ecex_config_free(s); + return -1; + } + + tetris_reset(ed, s); + + ecex_log("cmd_tetris: setting renderer"); + if (ecex_buffer_set_renderer(buffer, tetris_draw, s, tetris_free_state, ECEX_RENDER_REPLACE_CONTENT) != 0) { + ecex_log("cmd_tetris: set_renderer failed"); + ecex_config_free(s); + return -1; + } + + ecex_log("cmd_tetris: setting animation"); + if (ecex_buffer_set_animation_ms(buffer, tetris_tick, s, 0, 60) != 0) { + ecex_log("cmd_tetris: set_animation failed"); + ecex_buffer_clear_renderer(buffer); + return -1; + } + } + + ecex_log("cmd_tetris: replacing text"); + if (ecex_buffer_replace_text(buffer, "Tetris renderer. Press n for a new game, q to quit the window.\n") != 0) { + ecex_log("cmd_tetris: replace text failed"); + return -1; + } + + ecex_log("cmd_tetris: setting modified false"); + ecex_buffer_set_modified(buffer, 0); + ecex_log("cmd_tetris: setting major mode"); + if (ecex_buffer_set_major_mode_by_name(ed, buffer, TETRIS_MODE) != 0) { ecex_log("cmd_tetris: set mode failed"); return -1; } + ecex_log("cmd_tetris: switching buffer"); + if (ecex_switch_buffer(ed, TETRIS_BUF) != 0) { ecex_log("cmd_tetris: switch failed"); return -1; } + ecex_log("cmd_tetris: success"); + return 0; +} + +static int cmd_tetris_new(ecex_t *ed) { + ecex_log("cmd_tetris_new: enter"); + tetris_state_t *s = tetris_state_for_ed(ed); + if (!s) return cmd_tetris(ed); + tetris_reset(ed, s); + return 0; +} + +static int cmd_tetris_left(ecex_t *ed) { + ecex_log("cmd_tetris_left: enter"); + tetris_move_horizontal(ed, tetris_state_for_ed(ed), -1); + return 0; +} + +static int cmd_tetris_right(ecex_t *ed) { + ecex_log("cmd_tetris_right: enter"); + tetris_move_horizontal(ed, tetris_state_for_ed(ed), 1); + return 0; +} + +static int cmd_tetris_down(ecex_t *ed) { + ecex_log("cmd_tetris_down: enter"); + tetris_state_t *s = tetris_state_for_ed(ed); + if (s) { + if (tetris_soft_drop(ed, s)) s->score += 1; + s->last_drop_ms = 0; + } + return 0; +} + +static int cmd_tetris_rotate(ecex_t *ed) { + ecex_log("cmd_tetris_rotate: enter"); + tetris_state_t *s = tetris_state_for_ed(ed); + int nr; + + if (!s || s->game_over || s->paused) return 0; + + nr = (s->rot + 1) & 3; + if (!tetris_collides(ed, s, s->piece, nr, s->x, s->y)) { + s->rot = nr; + } else if (!tetris_collides(ed, s, s->piece, nr, s->x - 1, s->y)) { + --s->x; + s->rot = nr; + } else if (!tetris_collides(ed, s, s->piece, nr, s->x + 1, s->y)) { + ++s->x; + s->rot = nr; + } + + return 0; +} + +static int cmd_tetris_drop(ecex_t *ed) { + ecex_log("cmd_tetris_drop: enter"); + tetris_hard_drop(ed, tetris_state_for_ed(ed)); + return 0; +} + +static int cmd_tetris_pause(ecex_t *ed) { + ecex_log("cmd_tetris_pause: enter"); + tetris_state_t *s = tetris_state_for_ed(ed); + if (s && !s->game_over) { + s->paused = !s->paused; + s->last_drop_ms = 0; + } + return 0; +} + +static int cmd_tetris_quit(ecex_t *ed) { + ecex_log("cmd_tetris_quit: enter"); + return ecex_execute_command(ed, "quit-window"); +} + +int ecex_tetris_plugin(ecex_t *ed) { + ecex_log("ecex_tetris_plugin: enter"); + ECEX_CONFIG_MODE(TETRIS_MODE); + ECEX_CONFIG_COMMAND("tetris", cmd_tetris); + ECEX_CONFIG_COMMAND("tetris-new", cmd_tetris_new); + ECEX_CONFIG_COMMAND("tetris-left", cmd_tetris_left); + ECEX_CONFIG_COMMAND("tetris-right", cmd_tetris_right); + ECEX_CONFIG_COMMAND("tetris-down", cmd_tetris_down); + ECEX_CONFIG_COMMAND("tetris-rotate", cmd_tetris_rotate); + ECEX_CONFIG_COMMAND("tetris-drop", cmd_tetris_drop); + ECEX_CONFIG_COMMAND("tetris-pause", cmd_tetris_pause); + ECEX_CONFIG_COMMAND("tetris-quit", cmd_tetris_quit); + + ECEX_CONFIG_MODE_BIND(TETRIS_MODE, "LEFT", "tetris-left"); + ECEX_CONFIG_MODE_BIND(TETRIS_MODE, "h", "tetris-left"); + ECEX_CONFIG_MODE_BIND(TETRIS_MODE, "RIGHT", "tetris-right"); + ECEX_CONFIG_MODE_BIND(TETRIS_MODE, "l", "tetris-right"); + ECEX_CONFIG_MODE_BIND(TETRIS_MODE, "DOWN", "tetris-down"); + ECEX_CONFIG_MODE_BIND(TETRIS_MODE, "j", "tetris-down"); + ECEX_CONFIG_MODE_BIND(TETRIS_MODE, "UP", "tetris-rotate"); + ECEX_CONFIG_MODE_BIND(TETRIS_MODE, "k", "tetris-rotate"); + ECEX_CONFIG_MODE_BIND(TETRIS_MODE, "SPC", "tetris-drop"); + ECEX_CONFIG_MODE_BIND(TETRIS_MODE, "p", "tetris-pause"); + ECEX_CONFIG_MODE_BIND(TETRIS_MODE, "n", "tetris-new"); + ECEX_CONFIG_MODE_BIND(TETRIS_MODE, "q", "tetris-quit"); + + ecex_log("ecex_tetris_plugin: registered all commands and keybinds"); + return 0; +} + +#ifndef ECEX_NO_STANDALONE_CONFIG +ECEX_CONFIG_BEGIN + ECEX_CONFIG_INCLUDE(ecex_tetris_plugin); +ECEX_CONFIG_END +#endif |
