aboutsummaryrefslogtreecommitdiff
path: root/config
diff options
context:
space:
mode:
Diffstat (limited to 'config')
-rw-r--r--config/ecexrc.c55
-rw-r--r--config/filebrowser_plugin.c20
-rw-r--r--config/markdown_plugin.c126
-rw-r--r--config/render_demo.c302
-rw-r--r--config/tetris.c682
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