aboutsummaryrefslogtreecommitdiff
path: root/config/tetris.c
diff options
context:
space:
mode:
Diffstat (limited to 'config/tetris.c')
-rw-r--r--config/tetris.c682
1 files changed, 682 insertions, 0 deletions
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