Back to shtanton's homepage
summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/all.c280
-rw-r--r--src/index.html.in26
-rw-r--r--src/levels.c37
-rw-r--r--src/tick.c335
-rw-r--r--src/types.c108
5 files changed, 639 insertions, 147 deletions
diff --git a/src/all.c b/src/all.c
index 8e5170c..372690f 100644
--- a/src/all.c
+++ b/src/all.c
@@ -1,114 +1,94 @@
+#ifndef INCLUDE_ALL_C
+#define INCLUDE_ALL_C
+
#include <stddef.h>
#include <stdint.h>
-#if SDL
-#include <string.h>
-#define xmemcpy memcpy
-#else
-#define xmemcpy __builtin_memcpy
-#endif
-
-typedef struct {
- unsigned char r, g, b, a;
-} Color;
-
-typedef enum {
- BUTTON_STATE_IDLE,
- BUTTON_STATE_HOVERED,
- BUTTON_STATE_PRESSED
-} ButtonState;
-
-typedef struct {
- int x, y, w, h;
- Color fill;
- Color border;
-} DrawElement;
-
-typedef struct {
- DrawElement bg;
-} SidePanel;
-
-typedef struct {
- int len;
- DrawElement els[512];
-} DrawList;
-
-#define MEM_SIZE (1<<16)
-#define GRIDWIDTH 16
-#define GRIDHEIGHT 16
-#define TICK_LENGTH 1000
-#define GRID_OFFSET_X 256
-
-typedef struct {
- char *start;
- char *end;
-} Arena;
-
-#define new(a, c, t) ((t *) alloc(a, c, sizeof(t), _Alignof(t)))
-#define affirm(c) while (!(c)) *(volatile int *)0 = 0
-
-static void *alloc(Arena *a, ptrdiff_t count, ptrdiff_t size, ptrdiff_t align) {
- ptrdiff_t pad = -(size_t) a->start & (align - 1);
- affirm(count < (a->end - a->start - pad) / size);
- char *r = a->start + pad;
- a->start += pad + size * count;
- for (ptrdiff_t i = 0; i < count * size; i++) {
- r[i] = 0;
- }
-
- return r;
-}
-
-typedef struct {
- int width, height;
-} UI;
-
-enum {
- BUTTON_RETRY,
- BUTTON_BACK,
- N_BUTTONS,
+#include "types.c"
+#include "levels.c"
+#include "tick.c"
+
+static const Color colors[N_COLORS][4] = {
+ {
+ {255, 255, 255, 255},
+ {255, 255, 255, 255},
+ {255, 255, 255, 255},
+ {255, 255, 255, 255},
+ },
+ {
+ {0, 0, 0, 255},
+ {0, 0, 0, 255},
+ {0, 0, 0, 255},
+ {0, 0, 0, 255},
+ },
+ {
+ {255, 0, 0, 255},
+ {255, 0, 0, 255},
+ {255, 0, 0, 255},
+ {255, 0, 0, 255},
+ },
+ {
+ {255, 255, 0, 255},
+ {255, 255, 0, 255},
+ {255, 255, 0, 255},
+ {255, 255, 0, 255},
+ },
+ {
+ {255, 0, 0, 255},
+ {255, 0, 0, 255},
+ {255, 255, 0, 255},
+ {255, 255, 0, 255},
+ },
+ {
+ {255, 255, 0, 255},
+ {255, 255, 0, 255},
+ {255, 0, 0, 255},
+ {255, 0, 0, 255},
+ },
+ {
+ {255, 0, 0, 255},
+ {255, 255, 0, 255},
+ {255, 0, 0, 255},
+ {255, 255, 0, 255},
+ },
+ {
+ {255, 255, 0, 255},
+ {255, 0, 0, 255},
+ {255, 255, 0, 255},
+ {255, 0, 0, 255},
+ },
+ {
+ {0, 0, 255, 255},
+ {0, 0, 255, 255},
+ {0, 0, 255, 255},
+ {0, 0, 255, 255},
+ },
+ {
+ {0, 0, 255, 255},
+ {0, 0, 255, 255},
+ {255, 255, 255, 255},
+ {255, 255, 255, 255},
+ },
+ {
+ {255, 255, 255, 255},
+ {255, 255, 255, 255},
+ {0, 0, 255, 255},
+ {0, 0, 255, 255},
+ },
+ {
+ {0, 0, 255, 255},
+ {255, 255, 255, 255},
+ {0, 0, 255, 255},
+ {255, 255, 255, 255},
+ },
+ {
+ {255, 255, 255, 255},
+ {0, 0, 255, 255},
+ {255, 255, 255, 255},
+ {0, 0, 255, 255},
+ },
};
-typedef struct {
- uint64_t lastTick;
- char playing;
- int grid[GRIDWIDTH * GRIDHEIGHT];
- ButtonState buttonStates[N_BUTTONS];
-} State;
-
-// Mirror these in src/index.html.in
-enum {
- INPUT_NONE,
- INPUT_CLICK,
- INPUT_PAUSE_PLAY,
-};
-
-typedef struct {
- State state;
- UI ui;
- int input;
- int mousex, mousey;
-} Game;
-
-enum {
- EMPTY,
- BLACK,
- RED,
- YELLOW,
- N_COLORS,
-};
-
-static const Color colors[N_COLORS] = {
- {255, 255, 255, 255},
- {0, 0, 0, 255},
- {255, 0, 0, 255},
- {255, 255, 0, 255}
-};
-
-typedef struct {
- int x, y, w, h;
-} Button;
-
static const Button buttons[N_BUTTONS] = {
[BUTTON_RETRY] = {.x = 16, .y = 16, .w = 128, .h = 32},
[BUTTON_BACK] = {.x = 16, .y = 48, .w = 128, .h = 32},
@@ -129,32 +109,53 @@ static DrawList *render(State *state, UI *ui, Arena *a) {
.y = cellHeight * y,
.w = cellWidth,
.h = cellHeight,
- .fill = colors[state->grid[x + GRIDWIDTH * y]],
+ .fill = {0, 0, 0, 0},
.border = {0, 0, 0, 255},
};
+ for (int subx = 0; subx < 2; subx++) {
+ for (int suby = 0; suby < 2; suby++) {
+ drawList->els[drawList->len++] = (DrawElement) {
+ .x = cellWidth * x + subx * cellWidth / 2,
+ .y = cellHeight * y + suby * cellHeight / 2,
+ .w = cellWidth / 2,
+ .h = cellHeight / 2,
+ .fill = colors[state->grid[x + GRIDWIDTH * y]][subx + 2 * suby],
+ .border = {0, 0, 0, 0},
+ };
+ }
+ }
}
}
drawList->els[drawList->len++] = (DrawElement) {
+ .x = cellWidth * state->goalx,
+ .y = cellHeight * state->goaly,
+ .w = cellWidth,
+ .h = cellHeight,
+ .fill = {255, 0, 0, 63},
+ .border = {255, 0, 0, 255},
+ };
+
+ drawList->els[drawList->len++] = (DrawElement) {
.x = 0,
.y = 0,
.w = GRID_OFFSET_X,
.h = ui->height,
- .fill = colors[YELLOW],
- .border = {0, 0, 0, 255}
+ .fill = {255, 255, 0, 255},
+ .border = {0, 0, 0, 255},
};
for (int i = 0; i < N_BUTTONS; i++) {
- int colour = BLACK;
+ Color colour = {0, 0, 0, 255};
switch (state->buttonStates[i]) {
case BUTTON_STATE_IDLE:
- colour = BLACK;
+ colour = (Color) {0, 0, 0, 255};
break;
case BUTTON_STATE_HOVERED:
- colour = YELLOW;
+ colour = (Color) {255, 255, 0, 255};
break;
case BUTTON_STATE_PRESSED:
- colour = RED;
+ colour = (Color) {255, 0, 0, 255};
break;
}
drawList->els[drawList->len++] = (DrawElement) {
@@ -162,7 +163,7 @@ static DrawList *render(State *state, UI *ui, Arena *a) {
.y = buttons[i].y,
.w = buttons[i].w,
.h = buttons[i].h,
- .fill = colors[colour],
+ .fill = colour,
.border = {0, 0, 0, 255}
};
}
@@ -189,6 +190,11 @@ static void update(Game *game, uint64_t now, Arena a) {
}
}
break;
+ case INPUT_RCLICK:
+ x = game->mousex * GRIDWIDTH / game->ui.width;
+ y = game->mousey * GRIDHEIGHT / game->ui.height;
+ game->state.grid[x + y * GRIDWIDTH] = EMPTY;
+ break;
case INPUT_PAUSE_PLAY:
game->state.playing = !game->state.playing;
break;
@@ -198,31 +204,8 @@ static void update(Game *game, uint64_t now, Arena a) {
if (game->state.playing && game->state.lastTick + TICK_LENGTH <= now) {
game->state.lastTick = now;
- State *lastState = new(&a, 1, State);
- xmemcpy(lastState, game, sizeof(State));
-
- for (int x = 0; x < GRIDWIDTH; x++) {
- for (int y = 0; y < GRIDHEIGHT; y++) {
- if (
- lastState->grid[x + y * GRIDWIDTH] == BLACK && (
- (x > 0 && lastState->grid[x - 1 + y * GRIDWIDTH] == RED) ||
- (x < GRIDWIDTH - 1 && lastState->grid[x + 1 + y * GRIDWIDTH] == RED) ||
- (y > 0 && lastState->grid[x + (y - 1) * GRIDWIDTH] == RED) ||
- (y < GRIDHEIGHT - 1 && lastState->grid[x + (y + 1) * GRIDWIDTH] == RED)
- )
- ) {
- game->state.grid[x + y * GRIDWIDTH] = RED;
- }
-
- if (lastState->grid[x + y * GRIDWIDTH] == RED) {
- game->state.grid[x + y * GRIDWIDTH] = YELLOW;
- }
- if (lastState->grid[x + y * GRIDWIDTH] == YELLOW) {
- game->state.grid[x + y * GRIDWIDTH] = BLACK;
- }
- }
- }
+ tick(game, a);
}
}
@@ -241,7 +224,9 @@ int main(int argc, char **argv) {
};
Game *game = new(&a, 1, Game);
- game->state.grid[0] = 0;
+ xmemcpy(&game->state.grid, &levels[0].grid, sizeof(game->state.grid));
+ game->state.goalx = levels[0].goalx;
+ game->state.goaly = levels[0].goaly;
game->state.playing = 0;
game->ui = (UI) {
.width = 640,
@@ -263,7 +248,8 @@ int main(int argc, char **argv) {
SDL_SetNumberProperty(renderProps, SDL_PROP_RENDERER_CREATE_PRESENT_VSYNC_NUMBER, 1);
SDL_Renderer *r = SDL_CreateRendererWithProperties(renderProps);
SDL_DestroyProperties(renderProps);
-
+ SDL_SetRenderDrawBlendMode(r, SDL_BLENDMODE_BLEND);
+
for (;;) {
uint64_t now = SDL_GetTicks();
game->input = INPUT_NONE;
@@ -273,9 +259,13 @@ int main(int argc, char **argv) {
case SDL_EVENT_QUIT:
return 0;
case SDL_EVENT_MOUSE_BUTTON_DOWN:
- game->input = INPUT_CLICK;
game->mousex = (int) e.button.x;
game->mousey = (int) e.button.y;
+ if (e.button.button == 1) {
+ game->input = INPUT_CLICK;
+ } else if (e.button.button == 3) {
+ game->input = INPUT_RCLICK;
+ }
break;
case SDL_EVENT_KEY_DOWN:
switch (e.key.key) {
@@ -290,8 +280,6 @@ int main(int argc, char **argv) {
}
}
- SDL_Log("Button 1 state: %d", game->state.buttonStates[0]);
-
Arena scratch = a;
DrawList *drawList = render(&game->state, &game->ui, &scratch);
SDL_SetRenderDrawColor(r, 0, 0, 0, 255);
@@ -355,11 +343,13 @@ DrawList *game_render(int width, int height) {
}
__attribute((export_name("game_update")))
-void game_update(int input, int mousex, int mousey) {
+void game_update(int input, int mousex, int mousey, int now) {
game->input = input;
game->mousex = mousex;
game->mousey = mousey;
- update(game, perm);
+ update(game, now, perm);
}
#endif
+
+#endif
diff --git a/src/index.html.in b/src/index.html.in
index 07bd618..8d0ccea 100644
--- a/src/index.html.in
+++ b/src/index.html.in
@@ -30,6 +30,8 @@ button {
// Mirror these in src/all.c
const INPUT_NONE = 0;
const INPUT_CLICK = 1;
+const INPUT_RCLICK = 2;
+const INPUT_PAUSE_PLAY = 3;
const WASM =
#include "../build/main.wasm.b64"
@@ -46,6 +48,11 @@ async function main() {
let ctx = canvas.getContext("2d");
let memory = exports.memory;
+ const start = Date.now();
+ function now() {
+ return Date.now() - start;
+ }
+
function min(a, b) {
return b<a ? b : a;
}
@@ -66,8 +73,10 @@ async function main() {
let op = ops.subarray(6*i, 6*i+6);
const color = new Uint8Array(new Uint32Array(op.subarray(4, 6)).buffer);
ctx.fillStyle = `#${color[0].toString(16).padStart(2, "0")}${color[1].toString(16).padStart(2, "0")}${color[2].toString(16).padStart(2, "0")}`;
+ ctx.globalAlpha = color[3] / 255;
ctx.fillRect(op[0], op[1], op[2], op[3]);
ctx.strokeStyle = `#${color[4].toString(16).padStart(2, "0")}${color[5].toString(16).padStart(2, "0")}${color[6].toString(16).padStart(2, "0")}`;
+ ctx.globalAlpha = color[7] / 255;
ctx.strokeRect(op[0], op[1], op[2], op[3]);
}
}
@@ -80,14 +89,27 @@ async function main() {
const mousex = e.clientX;
const mousey = e.clientY;
if (e.button == 0) {
- exports.game_update(INPUT_CLICK, mousex, mousey);
+ exports.game_update(INPUT_CLICK, mousex, mousey, now());
+ } else if (e.button == 2) {
+ e.preventDefault();
+ exports.game_update(INPUT_RCLICK, mousex, mousey, now());
+ }
+ });
+
+ canvas.addEventListener("contextmenu", function (e) {
+ e.preventDefault();
+ });
+
+ document.addEventListener("keydown", function (e) {
+ if (e.key === " ") {
+ exports.game_update(INPUT_PAUSE_PLAY, 0, 0, now());
}
});
function animate() {
// TODO: stop requesting frames when state is static
requestAnimationFrame(animate);
- exports.game_update(INPUT_NONE, 0, 0);
+ exports.game_update(INPUT_NONE, 0, 0, now());
render();
}
requestAnimationFrame(animate);
diff --git a/src/levels.c b/src/levels.c
new file mode 100644
index 0000000..22ad76a
--- /dev/null
+++ b/src/levels.c
@@ -0,0 +1,37 @@
+#include "all.c"
+
+typedef struct {
+ int grid[GRIDWIDTH * GRIDHEIGHT];
+ int goalx, goaly;
+} Level;
+
+#define _ EMPTY,
+#define B BLACK,
+#define O BLUE,
+static Level levels[] = {
+ {
+ .grid = {
+ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
+ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
+ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
+ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
+ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
+ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
+ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
+ _ _ _ _ B O _ _ _ _ _ _ _ _ _ _ _ _ _ _
+ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
+ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
+ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
+ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
+ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
+ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
+ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
+ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
+ },
+ .goalx = 18,
+ .goaly = 7,
+ },
+};
+#undef _
+#undef B
+#undef O
diff --git a/src/tick.c b/src/tick.c
new file mode 100644
index 0000000..8b8f731
--- /dev/null
+++ b/src/tick.c
@@ -0,0 +1,335 @@
+#include "all.c"
+
+static int isRed(int cell) {
+ return (
+ cell == RED ||
+ cell == RED_LEFT ||
+ cell == RED_RIGHT ||
+ cell == RED_UP ||
+ cell == RED_DOWN
+ );
+}
+
+static int isBlue(int cell) {
+ return (
+ cell == BLUE ||
+ cell == BLUE_LEFT ||
+ cell == BLUE_RIGHT ||
+ cell == BLUE_UP ||
+ cell == BLUE_DOWN
+ );
+}
+
+static void tick(Game *game, Arena a) {
+ State *lastState = new(&a, 1, State);
+ xmemcpy(lastState, game, sizeof(State));
+
+ for (int x = 0; x < GRIDWIDTH; x++) {
+ for (int y = 0; y < GRIDHEIGHT; y++) {
+ switch (lastState->grid[x + y * GRIDWIDTH]) {
+ int reds, red;
+ int blues, blue;
+ case BLACK:
+ reds = 0;
+ if (
+ x < GRIDWIDTH - 1 && (
+ lastState->grid[x + 1 + y * GRIDWIDTH] == RED_LEFT ||
+ lastState->grid[x + 1 + y * GRIDWIDTH] == RED
+ )
+ ) {
+ reds++;
+ if (
+ x < GRIDWIDTH - 2 &&
+ y > 0 &&
+ y < GRIDHEIGHT - 1 &&
+ lastState->grid[x + 2 + y * GRIDWIDTH] == YELLOW &&
+ lastState->grid[x + 1 + (y - 1) * GRIDWIDTH] == YELLOW &&
+ lastState->grid[x + 1 + (y + 1) * GRIDWIDTH] == YELLOW
+ ) {
+ red = RED_LEFT;
+ } else {
+ red = lastState->grid[x + 1 + y * GRIDWIDTH];
+ }
+ }
+
+ if (
+ x > 0 && (
+ lastState->grid[x - 1 + y * GRIDWIDTH] == RED_RIGHT ||
+ lastState->grid[x - 1 + y * GRIDWIDTH] == RED
+ )
+ ) {
+ reds++;
+ if (
+ x > 1 &&
+ y > 0 &&
+ y < GRIDHEIGHT - 1 &&
+ lastState->grid[x - 2 + y * GRIDWIDTH] == YELLOW &&
+ lastState->grid[x - 1 + (y - 1) * GRIDWIDTH] == YELLOW &&
+ lastState->grid[x - 1 + (y + 1) * GRIDWIDTH] == YELLOW
+ ) {
+ red = RED_RIGHT;
+ } else {
+ red = lastState->grid[x - 1 + y * GRIDWIDTH];
+ }
+ }
+
+ if (
+ y > 0 && (
+ lastState->grid[x + (y - 1) * GRIDWIDTH] == RED_DOWN ||
+ lastState->grid[x + (y - 1) * GRIDWIDTH] == RED
+ )
+ ) {
+ reds++;
+ if (
+ x > 0 &&
+ x < GRIDWIDTH - 1 &&
+ y > 1 &&
+ lastState->grid[x + (y - 2) * GRIDWIDTH] == YELLOW &&
+ lastState->grid[x - 1 + (y - 1) * GRIDWIDTH] == YELLOW &&
+ lastState->grid[x + 1 + (y - 1) * GRIDWIDTH] == YELLOW
+ ) {
+ red = RED_DOWN;
+ } else {
+ red = lastState->grid[x + (y - 1) * GRIDWIDTH];
+ }
+ }
+
+ if (
+ y < GRIDHEIGHT - 1 && (
+ lastState->grid[x + (y + 1) * GRIDWIDTH] == RED_UP ||
+ lastState->grid[x + (y + 1) * GRIDWIDTH] == RED
+ )
+ ) {
+ reds++;
+ if (
+ x > 0 &&
+ x < GRIDWIDTH - 1 &&
+ y < GRIDHEIGHT - 2 &&
+ lastState->grid[x + (y + 2) * GRIDWIDTH] == YELLOW &&
+ lastState->grid[x - 1 + (y + 1) * GRIDWIDTH] == YELLOW &&
+ lastState->grid[x + 1 + (y + 1) * GRIDWIDTH] == YELLOW
+ ) {
+ red = RED_UP;
+ } else {
+ red = lastState->grid[x + (y + 1) * GRIDWIDTH];
+ }
+ }
+
+ if (reds == 1) {
+ game->state.grid[x + y * GRIDWIDTH] = red;
+ } else if (reds >= 2) {
+ game->state.grid[x + y * GRIDWIDTH] = RED;
+ }
+ break;
+ case RED:
+ game->state.grid[x + y * GRIDWIDTH] = YELLOW;
+ break;
+ case RED_LEFT:
+ if (
+ x > 0 &&
+ lastState->grid[x - 1 + y * GRIDWIDTH] == BLACK
+ ) {
+ game->state.grid[x + y * GRIDWIDTH] = YELLOW;
+ } else {
+ game->state.grid[x + y * GRIDWIDTH] = RED_UP;
+ }
+ break;
+ case RED_RIGHT:
+ if (
+ x < GRIDWIDTH - 1 &&
+ lastState->grid[x + 1 + y * GRIDWIDTH] == BLACK
+ ) {
+ game->state.grid[x + y * GRIDWIDTH] = YELLOW;
+ } else {
+ game->state.grid[x + y * GRIDWIDTH] = RED_DOWN;
+ }
+ break;
+ case RED_UP:
+ if (
+ y > 0 &&
+ lastState->grid[x + (y - 1) * GRIDWIDTH] == BLACK
+ ) {
+ game->state.grid[x + y * GRIDWIDTH] = YELLOW;
+ } else {
+ game->state.grid[x + y * GRIDWIDTH] = RED_RIGHT;
+ }
+ break;
+ case RED_DOWN:
+ if (
+ y < GRIDHEIGHT - 1 &&
+ lastState->grid[x + (y + 1) * GRIDWIDTH] == BLACK
+ ) {
+ game->state.grid[x + y * GRIDWIDTH] = YELLOW;
+ } else {
+ game->state.grid[x + y * GRIDWIDTH] = RED_LEFT;
+ }
+ break;
+ case YELLOW:
+ if (
+ (x > 0 && lastState->grid[x - 1 + y * GRIDWIDTH] == BLUE_RIGHT) ||
+ (x < GRIDWIDTH - 1 && lastState->grid[x + 1 + y * GRIDWIDTH] == BLUE_LEFT) ||
+ (y > 0 && lastState->grid[x + (y - 1) * GRIDWIDTH] == BLUE_DOWN) ||
+ (y < GRIDHEIGHT - 1 && lastState->grid[x + (y + 1) * GRIDWIDTH] == BLUE_UP)
+ ) {
+ game->state.grid[x + y * GRIDWIDTH] = RED;
+ } else {
+ game->state.grid[x + y * GRIDWIDTH] = BLACK;
+ }
+ break;
+ case BLUE:
+ blues = 0;
+ if (
+ x > 0 && (
+ lastState->grid[x - 1 + y * GRIDWIDTH] == RED ||
+ lastState->grid[x - 1 + y * GRIDWIDTH] == RED_LEFT ||
+ lastState->grid[x - 1 + y * GRIDWIDTH] == RED_RIGHT ||
+ lastState->grid[x - 1 + y * GRIDWIDTH] == RED_DOWN ||
+ lastState->grid[x - 1 + y * GRIDWIDTH] == RED_UP
+ )
+ ) {
+ blues++;
+ blue = BLUE_RIGHT;
+ }
+ if (
+ x < GRIDWIDTH - 1 && (
+ lastState->grid[x + 1 + y * GRIDWIDTH] == RED ||
+ lastState->grid[x + 1 + y * GRIDWIDTH] == RED_LEFT ||
+ lastState->grid[x + 1 + y * GRIDWIDTH] == RED_RIGHT ||
+ lastState->grid[x + 1 + y * GRIDWIDTH] == RED_DOWN ||
+ lastState->grid[x + 1 + y * GRIDWIDTH] == RED_UP
+ )
+ ) {
+ blues++;
+ blue = BLUE_LEFT;
+ }
+ if (
+ y > 0 && (
+ lastState->grid[x + (y - 1) * GRIDWIDTH] == RED ||
+ lastState->grid[x + (y - 1) * GRIDWIDTH] == RED_LEFT ||
+ lastState->grid[x + (y - 1) * GRIDWIDTH] == RED_RIGHT ||
+ lastState->grid[x + (y - 1) * GRIDWIDTH] == RED_DOWN ||
+ lastState->grid[x + (y - 1) * GRIDWIDTH] == RED_UP
+ )
+ ) {
+ blues++;
+ blue = BLUE_DOWN;
+ }
+ if (
+ y < GRIDHEIGHT - 1 && (
+ lastState->grid[x + (y + 1) * GRIDWIDTH] == RED ||
+ lastState->grid[x + (y + 1) * GRIDWIDTH] == RED_LEFT ||
+ lastState->grid[x + (y + 1) * GRIDWIDTH] == RED_RIGHT ||
+ lastState->grid[x + (y + 1) * GRIDWIDTH] == RED_DOWN ||
+ lastState->grid[x + (y + 1) * GRIDWIDTH] == RED_UP
+ )
+ ) {
+ blues++;
+ blue = BLUE_UP;
+ }
+ if (blues == 1) {
+ game->state.grid[x + y * GRIDWIDTH] = blue;
+ }
+ break;
+ case BLUE_LEFT:
+ if (
+ (x > 0 && isRed(lastState->grid[x - 1 + y * GRIDWIDTH])) ||
+ (x < GRIDWIDTH - 1 && isRed(lastState->grid[x + 1 + y * GRIDWIDTH])) ||
+ (y > 0 && isRed(lastState->grid[x + (y - 1) * GRIDWIDTH])) ||
+ (y < GRIDHEIGHT - 1 && isRed(lastState->grid[x + (y + 1) * GRIDWIDTH]))
+ ) {
+ game->state.grid[x + y * GRIDWIDTH] = BLUE;
+ } else if (
+ x > 0 &&
+ lastState->grid[x - 1 + y * GRIDWIDTH] == EMPTY
+ ) {
+ game->state.grid[x + y * GRIDWIDTH] = EMPTY;
+ } else {
+ game->state.grid[x + y * GRIDWIDTH] = BLUE;
+ }
+ break;
+ case BLUE_RIGHT:
+ if (
+ (x > 0 && isRed(lastState->grid[x - 1 + y * GRIDWIDTH])) ||
+ (x < GRIDWIDTH - 1 && isRed(lastState->grid[x + 1 + y * GRIDWIDTH])) ||
+ (y > 0 && isRed(lastState->grid[x + (y - 1) * GRIDWIDTH])) ||
+ (y < GRIDHEIGHT - 1 && isRed(lastState->grid[x + (y + 1) * GRIDWIDTH]))
+ ) {
+ game->state.grid[x + y * GRIDWIDTH] = BLUE;
+ } else if (
+ x < GRIDWIDTH - 1 &&
+ lastState->grid[x + 1 + y * GRIDWIDTH] == EMPTY
+ ) {
+ game->state.grid[x + y * GRIDWIDTH] = EMPTY;
+ } else {
+ game->state.grid[x + y * GRIDWIDTH] = BLUE;
+ }
+ break;
+ case BLUE_UP:
+ if (
+ (x > 0 && isRed(lastState->grid[x - 1 + y * GRIDWIDTH])) ||
+ (x < GRIDWIDTH - 1 && isRed(lastState->grid[x + 1 + y * GRIDWIDTH])) ||
+ (y > 0 && isRed(lastState->grid[x + (y - 1) * GRIDWIDTH])) ||
+ (y < GRIDHEIGHT - 1 && isRed(lastState->grid[x + (y + 1) * GRIDWIDTH]))
+ ) {
+ game->state.grid[x + y * GRIDWIDTH] = BLUE;
+ } else if (
+ y > 0 &&
+ lastState->grid[x + (y - 1) * GRIDWIDTH] == EMPTY
+ ) {
+ game->state.grid[x + y * GRIDWIDTH] = EMPTY;
+ } else {
+ game->state.grid[x + y * GRIDWIDTH] = BLUE;
+ }
+ break;
+ case BLUE_DOWN:
+ if (
+ (x > 0 && isRed(lastState->grid[x - 1 + y * GRIDWIDTH])) ||
+ (x < GRIDWIDTH - 1 && isRed(lastState->grid[x + 1 + y * GRIDWIDTH])) ||
+ (y > 0 && isRed(lastState->grid[x + (y - 1) * GRIDWIDTH])) ||
+ (y < GRIDHEIGHT - 1 && isRed(lastState->grid[x + (y + 1) * GRIDWIDTH]))
+ ) {
+ game->state.grid[x + y * GRIDWIDTH] = BLUE;
+ } else if (
+ y < GRIDHEIGHT - 1 &&
+ lastState->grid[x + (y + 1) * GRIDWIDTH] == EMPTY
+ ) {
+ game->state.grid[x + y * GRIDWIDTH] = EMPTY;
+ } else {
+ game->state.grid[x + y * GRIDWIDTH] = BLUE;
+ }
+ break;
+ case EMPTY:
+ // TODO: same as multiple reds
+ if (
+ x > 0 &&
+ lastState->grid[x - 1 + y * GRIDWIDTH] == BLUE_RIGHT
+ ) {
+ game->state.grid[x + y * GRIDWIDTH] = BLUE_RIGHT;
+ }
+ if (
+ x < GRIDWIDTH - 1 &&
+ lastState->grid[x + 1 + y * GRIDWIDTH] == BLUE_LEFT
+ ) {
+ game->state.grid[x + y * GRIDWIDTH] = BLUE_LEFT;
+ }
+ if (
+ y > 0 &&
+ lastState->grid[x + (y - 1) * GRIDWIDTH] == BLUE_DOWN
+ ) {
+ game->state.grid[x + y * GRIDWIDTH] = BLUE_DOWN;
+ }
+ if (
+ y < GRIDHEIGHT - 1 &&
+ lastState->grid[x + (y + 1) * GRIDWIDTH] == BLUE_UP
+ ) {
+ game->state.grid[x + y * GRIDWIDTH] = BLUE_UP;
+ }
+ break;
+ }
+ }
+ }
+
+ if (isBlue(game->state.grid[game->state.goalx + game->state.goaly * GRIDWIDTH])) {
+ // TODO: Win conditions
+ }
+}
diff --git a/src/types.c b/src/types.c
new file mode 100644
index 0000000..cbd9315
--- /dev/null
+++ b/src/types.c
@@ -0,0 +1,108 @@
+#include "all.c"
+
+#define MEM_SIZE (1<<24)
+#define GRIDWIDTH 20
+#define GRIDHEIGHT 16
+#define TICK_LENGTH 200
+#define GRID_OFFSET_X 256
+
+typedef struct {
+ unsigned char r, g, b, a;
+} Color;
+
+typedef struct {
+ int x, y, w, h;
+ Color fill;
+ Color border;
+} DrawElement;
+
+typedef struct {
+ int len;
+ DrawElement els[2 * GRIDWIDTH * GRIDHEIGHT * 4];
+} DrawList;
+
+typedef struct {
+ char *start;
+ char *end;
+} Arena;
+
+enum {
+ EMPTY,
+ BLACK,
+ RED,
+ YELLOW,
+ RED_UP,
+ RED_DOWN,
+ RED_LEFT,
+ RED_RIGHT,
+ BLUE,
+ BLUE_UP,
+ BLUE_DOWN,
+ BLUE_LEFT,
+ BLUE_RIGHT,
+ N_COLORS,
+};
+
+enum {
+ BUTTON_RETRY,
+ BUTTON_BACK,
+ N_BUTTONS,
+};
+
+typedef struct {
+ int width, height;
+} UI;
+
+typedef enum {
+ BUTTON_STATE_IDLE,
+ BUTTON_STATE_HOVERED,
+ BUTTON_STATE_PRESSED
+} ButtonState;
+
+typedef struct {
+ int x, y, w, h;
+} Button;
+
+typedef struct {
+ uint64_t lastTick;
+ char playing;
+ int grid[GRIDWIDTH * GRIDHEIGHT];
+ int goalx, goaly;
+ ButtonState buttonStates[N_BUTTONS];
+} State;
+
+// Mirror these in src/index.html.in
+enum {
+ INPUT_NONE,
+ INPUT_CLICK,
+ INPUT_RCLICK,
+ INPUT_PAUSE_PLAY,
+};
+
+typedef struct {
+ State state;
+ UI ui;
+ int input;
+ int mousex, mousey;
+} Game;
+
+#define new(a, c, t) ((t *) alloc(a, c, sizeof(t), _Alignof(t)))
+#define affirm(c) while (!(c)) *(volatile int *)0 = 0
+
+static void xmemcpy(void *dst, void *src, ptrdiff_t size) {
+ for (ptrdiff_t i = 0; i < size; i++) {
+ ((char *) dst)[i] = ((char *) src)[i];
+ }
+}
+
+static void *alloc(Arena *a, ptrdiff_t count, ptrdiff_t size, ptrdiff_t align) {
+ ptrdiff_t pad = -(size_t) a->start & (align - 1);
+ affirm(count < (a->end - a->start - pad) / size);
+ char *r = a->start + pad;
+ a->start += pad + size * count;
+ for (ptrdiff_t i = 0; i < count * size; i++) {
+ r[i] = 0;
+ }
+
+ return r;
+}