diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/all.c | 280 | ||||
| -rw-r--r-- | src/index.html.in | 26 | ||||
| -rw-r--r-- | src/levels.c | 37 | ||||
| -rw-r--r-- | src/tick.c | 335 | ||||
| -rw-r--r-- | src/types.c | 108 | 
5 files changed, 639 insertions, 147 deletions
| @@ -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; +} | 
