diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/all.c | 692 | ||||
| -rw-r--r-- | src/img.c | 95 | ||||
| -rw-r--r-- | src/index.html.in | 234 | ||||
| -rw-r--r-- | src/levels.c | 488 | ||||
| -rw-r--r-- | src/tick.c | 376 | ||||
| -rw-r--r-- | src/types.c | 166 | 
6 files changed, 1921 insertions, 130 deletions
| @@ -1,103 +1,525 @@ +#ifndef INCLUDE_ALL_C +#define INCLUDE_ALL_C +  #include <stddef.h>  #include <stdint.h> -typedef struct { -	int x, y, w, h; -	unsigned char r, g, b, a; -} DrawElement; +#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 { -	int len; -	DrawElement els[256]; -} DrawList; +#define BUTTON_SIZE 32 +#define BUTTON_SPACING 16 +#define BUTTON_SPACER(i) (BUTTON_SPACING + BUTTON_SIZE + (BUTTON_SIZE * (i) + (i) * BUTTON_SPACING)) -#define MEM_SIZE (1<<16) +// reverse order as they're anchored to bottom +static const Button buttons[N_BUTTONS] = { +	[BUTTON_BACK] = {.x = BUTTON_SPACING, .y = BUTTON_SPACER(BUTTON_BACK), .w = BUTTON_SIZE, .h = BUTTON_SIZE}, +	[BUTTON_RETRY] = {.x = BUTTON_SPACING, .y = BUTTON_SPACER(BUTTON_RETRY), .w = BUTTON_SIZE, .h = BUTTON_SIZE}, +	[BUTTON_PLAY] = {.x = BUTTON_SPACING, .y = BUTTON_SPACER(BUTTON_PLAY), .w = BUTTON_SIZE, .h = BUTTON_SIZE}, +	[BUTTON_CONTINUE] = {.x = BUTTON_SPACING, .y = BUTTON_SPACER(BUTTON_CONTINUE), .w = BUTTON_SIZE, .h = BUTTON_SIZE}, +}; -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; +// This returns a positive color index or a negated image index +static int getPlaceAction(int currentColor, int cellx, int celly, float cellWidth, float cellHeight) { +	int hoverColor = EMPTY; + +	// TODO yellow when there are no reds available +	switch (currentColor) { +		case BLACK: +			if (cellx < cellWidth / 4 && cellx < celly && cellx < cellHeight - celly) { +				hoverColor = RED_LEFT; +			} else if (celly < cellHeight / 4 && celly < cellx && celly < cellWidth - cellx) { +				hoverColor = RED_UP; +			} else if (cellx > cellWidth / 4 * 3 && cellWidth - cellx < celly && cellWidth - cellx < cellHeight - celly) { +				hoverColor = RED_RIGHT; +			} else if (celly > cellHeight / 4 * 3 && cellHeight - celly < cellx && cellHeight - celly < cellWidth - cellx) { +				hoverColor = RED_DOWN; +			} else { +				hoverColor = RED; +			} +			break; +		case EMPTY: +			hoverColor = BLACK; +			break;  	} -	return r; +	return hoverColor;  } -typedef struct { -	int width, height; -} UI; +static DrawList *render(State *state, UI *ui, Arena *a) { +	DrawList *drawList = new(a, 1, DrawList); -typedef struct { -	int x, y; -} State; - -// Mirror these in src/index.html.in -enum { -	INPUT_NONE, -	INPUT_UP, -	INPUT_DOWN, -	INPUT_LEFT, -	INPUT_RIGHT, -}; +	float cellWidth = (float) (ui->width - GRID_OFFSET_X) / GRIDWIDTH; +	float cellHeight = (float) ui->height / GRIDHEIGHT; + +	// Actual cells +	for (int x = 0; x < GRIDWIDTH; x++) { +		for (int y = 0; y < GRIDHEIGHT; y++) { +			if (colorImages[(int) state->grid[x + GRIDWIDTH * y]]) { +				drawList->els[drawList->len++] = (DrawElement) { +					.x = cellWidth * x + GRID_OFFSET_X, +					.y = cellHeight * y, +					.w = cellWidth, +					.h = cellHeight, +					.image = (ImageRef) { +						.index = colorImages[(int) state->grid[x + GRIDWIDTH * y]], +						.opacity = 255, +					}, +				}; +			} else { +				for (int subx = 0; subx < 2; subx++) { +					for (int suby = 0; suby < 2; suby++) { +						drawList->els[drawList->len++] = (DrawElement) { +							.x = cellWidth * x + GRID_OFFSET_X + subx * cellWidth / 2, +							.y = cellHeight * y + suby * cellHeight / 2, +							.w = cellWidth / 2 + (1 - subx), +							.h = cellHeight / 2 + (1 - suby), +							.fill = colors[(int) state->grid[x + GRIDWIDTH * y]][subx + 2 * suby], +						}; +					} +				} +			} +		} +	} -typedef struct { -	State state; -	UI ui; -	int input; -} Game; +	// Vertical grid lines +	for (int x = 1; x < GRIDWIDTH; x++) { +		drawList->els[drawList->len++] = (DrawElement) { +			.x = cellWidth * x + GRID_OFFSET_X, +			.y = 0, +			.w = 1, +			.h = ui->height, +			.fill = {0, 0, 0, 255}, +		}; +	} + +	// Horizontal grid lines +	for (int y = 1; y < GRIDHEIGHT; y++) { +		drawList->els[drawList->len++] = (DrawElement) { +			.x = GRID_OFFSET_X, +			.y = y * cellHeight, +			.w = ui->width - GRID_OFFSET_X, +			.h = 1, +			.fill = {0, 0, 0, 255}, +		}; +	} -static DrawList *render(State state, UI *ui, Arena *a) { -	(void) ui; +	// Spawner +	if (state->spawnerx != 0 && state->spawnery != 0) { +		drawList->els[drawList->len++] = (DrawElement) { +			.x = cellWidth * state->spawnerx + GRID_OFFSET_X, +			.y = cellHeight * state->spawnery, +			.w = cellWidth, +			.h = cellHeight, +			.fill = {0, 0, 255, 63}, +			.border = {0, 0, 255, 255}, +		}; +	} -	DrawList *drawList = new(a, 1, DrawList); -	drawList->len = 1; -	drawList->els[0] = (DrawElement) { -		.x = state.x, -		.y = state.y, -		.w = 100, -		.h = 100, -		.r = 255, -		.g = 0, -		.b = 0, -		.a = 255, +	// Goal +	if (state->levelComplete) { +		drawList->els[drawList->len++] = (DrawElement) { +			.x = cellWidth * state->goalx + GRID_OFFSET_X, +			.y = cellHeight * state->goaly, +			.w = cellWidth, +			.h = cellHeight, +			.fill = {0, 255, 0, 63}, +			.border = {0, 255, 0, 255}, +		}; +	} else { +		drawList->els[drawList->len++] = (DrawElement) { +			.x = cellWidth * state->goalx + GRID_OFFSET_X, +			.y = cellHeight * state->goaly, +			.w = cellWidth, +			.h = cellHeight, +			.fill = {255, 0, 0, 63}, +			.border = {255, 0, 0, 255}, +		}; + +		if (state->need9) { +			for (int i = 0; i < state->blues; i++) { +				drawList->els[drawList->len++] = (DrawElement) { +					.x = cellWidth * state->goalx + GRID_OFFSET_X + cellWidth / 3 * (i % 3), +					.y = cellHeight * state->goaly + cellHeight / 3 * (i / 3), +					.w = cellWidth / 3, +					.h = cellHeight / 3, +					.fill = {196, 255, 196, 255}, +					.border = {0, 200, 0, 255}, +				}; +			} +		} +	} + +	// Hover +	if (ui->mousex >= GRID_OFFSET_X) { +		int hoverx = (ui->mousex - GRID_OFFSET_X) * GRIDWIDTH / (ui->width - GRID_OFFSET_X); +		int hovery = ui->mousey * GRIDHEIGHT / ui->height; +		 +		int cellx = ui->mousex - GRID_OFFSET_X - hoverx * cellWidth; +		int celly = ui->mousey - hovery * cellHeight; + +		int hoverColor = getPlaceAction( +			state->grid[hoverx + hovery * GRIDWIDTH], +			cellx, +			celly, +			cellWidth, +			cellHeight +		); + +		if (hoverColor != EMPTY) { +			for (int i = 0; i < MAX_PLACEABLE_CELLS; i++) { +				if (state->placeableCells[i] == hoverColor) { +					if (colorImages[hoverColor]) { +						drawList->els[drawList->len++] = (DrawElement) { +							.x = cellWidth * hoverx + GRID_OFFSET_X, +							.y = cellHeight * hovery, +							.w = cellWidth, +							.h = cellHeight, +							.image = { +								.index = colorImages[hoverColor], +								.opacity = 127, +							}, +						}; +					} else { +						int subCellWidth = cellWidth / 2; +						int subCellHeight = cellHeight / 2; +						for (int subx = 0; subx < 2; subx++) { +							for (int suby = 0; suby < 2; suby++) { +								Color fill = colors[hoverColor][subx + 2 * suby]; +								fill.a = 127; +								drawList->els[drawList->len++] = (DrawElement) { +									.x = cellWidth * hoverx + GRID_OFFSET_X + (subx * subCellWidth), +									.y = cellHeight * hovery + (suby * subCellHeight), +									.w = subCellWidth, +									.h = subCellHeight, +									.fill = fill, +								}; +							} +						} +					} +					break; +				} +			} +		} +	} + +	// Side panel +	drawList->els[drawList->len++] = (DrawElement) { +		.x = 0, +		.y = 0, +		.w = GRID_OFFSET_X, +		.h = ui->height, +		.fill = {100, 100, 100, 255}, +		.border = {0, 0, 0, 255},  	}; +	// render buttons +	for (int i = 0; i < N_BUTTONS; i++) { +		Color colour = {0, 0, 0, 255}; +		switch (state->buttonStates[i]) { +			case BUTTON_STATE_IDLE: +				colour = (Color) {0, 0, 0, 255}; +				break; +			case BUTTON_STATE_HOVERED: +				colour = (Color) {255, 255, 0, 255}; +				break; +			case BUTTON_STATE_PRESSED: +				colour = (Color) {255, 0, 0, 255}; +				break; +		} +		int image = IMAGE_NULL; +		switch (i) { +			case BUTTON_CONTINUE: +				image = IMAGE_CONTINUE; +				break; +			case BUTTON_BACK: +				image = IMAGE_EXIT; +				break; +			case BUTTON_RETRY: +				image = IMAGE_RETRY; +				break; +			case BUTTON_PLAY: +				image = state->playing ? IMAGE_PAUSE : IMAGE_PLAY; +				break; +		} +		Color fill = {0}; +		if (i == BUTTON_CONTINUE && state->levelComplete) { +			fill = (Color) {127, 255, 127, 200}; +		}; +		drawList->els[drawList->len++] = (DrawElement) { +			.x = buttons[i].x, +			.y = ui->height - buttons[i].y, +			.w = buttons[i].w, +			.h = buttons[i].h, +			.fill = fill, +			.image = (ImageRef) { +				.index = image, +				.opacity = 255, +			}, +		}; +	} + +	// Placeable cells +	for (int i = 0; i < MAX_PLACEABLE_CELLS; i++) { +		if (state->placeableCells[i] == EMPTY) { +			break; +		} + +		if (colorImages[(int) state->placeableCells[i]]) { +			drawList->els[drawList->len++] = (DrawElement) { +				.x = BUTTON_SIZE / 2, +				.y = BUTTON_SIZE / 2 + i * (BUTTON_SIZE + BUTTON_SPACING), +				.w = BUTTON_SIZE, +				.h = BUTTON_SIZE, +				.image = { +					.index = colorImages[(int) state->placeableCells[i]], +					.opacity = 255, +				}, +			}; +		} else { +			for (int x = 0; x < 2; x++) { +				for (int y = 0; y < 2; y++) { +					drawList->els[drawList->len++] = (DrawElement) { +						.x = BUTTON_SIZE / 2 + x * (BUTTON_SIZE / 2), +						.y = BUTTON_SIZE / 2 + i * (BUTTON_SIZE + BUTTON_SPACING) + y * (BUTTON_SIZE / 2), +						.w = BUTTON_SIZE / 2, +						.h = BUTTON_SIZE / 2, +						.fill = colors[(int) state->placeableCells[i]][x + 2 * y], +					}; +				} +			} +		} +	} +  	return drawList;  } -static void update(Game *game, Arena a) { +static void restart_level(Game* game) { +	const int level = game->state.currentLevel; +	xmemcpy(&game->state.grid, &levels[level].grid, sizeof(game->state.grid)); +	game->state.goalx = levels[level].goalx; +	game->state.goaly = levels[level].goaly; +	game->state.spawnerx = levels[level].spawnerx; +	game->state.spawnery = levels[level].spawnery; +	game->state.spawnerCount = 0; +	game->state.playing = 0; +	game->state.blues = 0; +	game->state.need9 = levels[level].need9; +	xmemcpy(&game->state.placeableCells, &levels[level].placeableCells, sizeof(game->state.placeableCells)); +} + +static int update(Game *game, uint64_t now, Arena a) { +	const int offset_width = game->ui.width - GRID_OFFSET_X; + +	int tutorial = 0; +	  	switch (game->input) { -		case INPUT_UP: -			game->state.y -= 20; +		int x, y; +		case INPUT_CLICK: +			x = (game->ui.mousex - GRID_OFFSET_X) * GRIDWIDTH / offset_width; +			y = game->ui.mousey * GRIDHEIGHT / game->ui.height; +			if (game->ui.mousex >= GRID_OFFSET_X) { +				float cellWidth = (float) (game->ui.width - GRID_OFFSET_X) / GRIDWIDTH; +				float cellHeight = (float) game->ui.height / GRIDHEIGHT; +				int cellx = game->ui.mousex - GRID_OFFSET_X - x * cellWidth; +				int celly = game->ui.mousey - y * cellHeight; +				// Keeping this around for testing purposes +				// game->state.grid[x + y * GRIDWIDTH] = (game->state.grid[x + y * GRIDWIDTH] + 1) % (sizeof(colors) / sizeof(colors[0])); +				char placed = getPlaceAction( +					game->state.grid[x + y * GRIDWIDTH], +					cellx, +					celly, +					cellWidth, +					cellHeight +				); + +				if (placed != EMPTY) { +					for (int i = 0; i < MAX_PLACEABLE_CELLS; i++) { +						if (game->state.placeableCells[i] == placed) { +							game->state.grid[x + y * GRIDWIDTH] = placed; +							for (int j = i; j < MAX_PLACEABLE_CELLS - 1; j++) { +								game->state.placeableCells[j] = game->state.placeableCells[j + 1]; +							} +							game->state.placeableCells[MAX_PLACEABLE_CELLS - 1] = EMPTY; +							break; +						} +					} +				} +			} + +			for (int i = 0; i < N_BUTTONS; i++) { +				if ( +					game->ui.mousex > buttons[i].x && game->ui.mousex < buttons[i].x + buttons[i].w && +					game->ui.mousey > game->ui.height - buttons[i].y && +					game->ui.mousey < game->ui.height - buttons[i].y + buttons[i].h +				) { +					// TODO - CLICK THINGS +					//game->state.buttonStates[i] = BUTTON_STATE_PRESSED; +					switch (i) { +						case BUTTON_RETRY: +							restart_level(game); +							break; +						case BUTTON_CONTINUE: +							if ( +								!game->state.levelComplete || +								game->state.currentLevel + 1 >= sizeof(levels) / sizeof(levels[0]) +							) { +								break; +							} +							game->state.levelComplete = 0; +							game->state.currentLevel++; +							restart_level(game); +							break; +						case BUTTON_BACK: +							tutorial = 1; +							break; +						case BUTTON_PLAY: +							game->state.playing = !game->state.playing; +							break; +					} +				} +			}  			break; -		case INPUT_DOWN: -			game->state.y += 20; +		case INPUT_RCLICK: +			if (game->ui.mousex > GRID_OFFSET_X) { +				x = (game->ui.mousex - GRID_OFFSET_X) * GRIDWIDTH / (game->ui.width - GRID_OFFSET_X); +				y = game->ui.mousey * GRIDHEIGHT / game->ui.height; + +				if (game->state.grid[x + y * GRIDWIDTH] != BLACK) { +					break; +				} + +				for (int i = 0; i < MAX_PLACEABLE_CELLS; i++) { +					if (game->state.placeableCells[i] == YELLOW) { +						game->state.grid[x + y * GRIDWIDTH] = YELLOW; +						for (int j = i; j < MAX_PLACEABLE_CELLS - 1; j++) { +							game->state.placeableCells[j] = game->state.placeableCells[j + 1]; +						} +						game->state.placeableCells[MAX_PLACEABLE_CELLS - 1] = EMPTY; +						break; +					} +				} +			}  			break; -		case INPUT_LEFT: -			game->state.x -= 20; +		case INPUT_PAUSE_PLAY: +			game->state.playing = !game->state.playing;  			break; -		case INPUT_RIGHT: -			game->state.x += 20; +		case INPUT_RESTART: +			restart_level(game);  			break;  		default:  			break;  	} + +	if (game->state.playing && game->state.lastTick + TICK_LENGTH <= now) { +		game->state.lastTick = now; + +		tick(game, a); +	} + +	return tutorial;  }  #if SDL  #include <SDL3/SDL.h> +typedef struct { +	int width, height; +	Color colors[16]; +	const char *pixels; +} Image; + +#include "../build/images.c" + +SDL_Texture *textures[sizeof(images) / sizeof(images[0])]; + +#include "../build/music.c" + +SDL_AudioStream *stream; +  int main(int argc, char **argv) {  	(void) argc;  	(void) argv; @@ -109,76 +531,138 @@ int main(int argc, char **argv) {  	};  	Game *game = new(&a, 1, Game); -	game->state = (State) { -		.x = 100, -		.y = 100, -	}; +	game->state.currentLevel = 0; +	restart_level(game);  	game->ui = (UI) {  		.width = 640,  		.height = 480, +		.mousex = 0, +		.mousey = 0,  	}; +	for (int i = 0; i < N_BUTTONS; i++) { +		game->state.buttonStates[i] = BUTTON_STATE_IDLE; +	} -	SDL_Init(SDL_INIT_VIDEO); +	SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO);  	SDL_Window *w = SDL_CreateWindow(  		"LDJam 57",  		game->ui.width,  		game->ui.height, -		0 +		SDL_WINDOW_RESIZABLE  	);  	SDL_PropertiesID renderProps = SDL_CreateProperties();  	SDL_SetPointerProperty(renderProps, SDL_PROP_RENDERER_CREATE_WINDOW_POINTER, w);  	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 (int j = 1; j < (int) (sizeof(images) / sizeof(images[0])); j++) { +		textures[j] = SDL_CreateTexture( +			r, +			SDL_PIXELFORMAT_ABGR8888, +			SDL_TEXTUREACCESS_STATIC, +			images[j].width, +			images[j].height +		); +		SDL_SetTextureScaleMode(textures[j], SDL_SCALEMODE_NEAREST); +		Arena scratch = a; +		Color *pixels = new(&scratch, images[j].width * images[j].height, Color); +		for (int i = 0; i < images[j].width * images[j].height; i++) { +			pixels[i] = images[j].colors[(int) images[j].pixels[i]]; +		} +		SDL_UpdateTexture(textures[j], NULL, pixels, images[j].width * 4); +	} + +	SDL_AudioSpec audioSpec = { +		.format = SDL_AUDIO_S16LE, +		.channels = 2, +		.freq = 48000, +	}; +	stream = SDL_OpenAudioDeviceStream(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &audioSpec, NULL, NULL); +	SDL_PutAudioStreamData(stream, musicBytes, sizeof(musicBytes)); +	SDL_ResumeAudioStreamDevice(stream);  	for (;;) { +		uint64_t now = SDL_GetTicks();  		game->input = INPUT_NONE;  		SDL_Event e = {0};  		while (SDL_PollEvent(&e)) {  			switch (e.type) {  				case SDL_EVENT_QUIT:  					return 0; +				case SDL_EVENT_MOUSE_BUTTON_DOWN: +					game->ui.mousex = (int) e.button.x; +					game->ui.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_MOUSE_MOTION: +					game->ui.mousex = (int) e.motion.x; +					game->ui.mousey = (int) e.motion.y; +					game->input = INPUT_MOVE; +					break;  				case SDL_EVENT_KEY_DOWN:  					switch (e.key.key) { -						case 'w': -							game->input = INPUT_UP; -							break; -						case 'a': -							game->input = INPUT_LEFT; +						case SDLK_SPACE: +							game->input = INPUT_PAUSE_PLAY;  							break; -						case 's': -							game->input = INPUT_DOWN; -							break; -						case 'd': -							game->input = INPUT_RIGHT; +						case SDLK_R: +							game->input = INPUT_RESTART;  							break;  					}  					break; +				case SDL_EVENT_WINDOW_RESIZED: +					SDL_GetWindowSize(w, &game->ui.width, &game->ui.height); +					break;  			}  		}  		Arena scratch = a; -		DrawList *drawList = render(game->state, &game->ui, &scratch); +		DrawList *drawList = render(&game->state, &game->ui, &scratch);  		SDL_SetRenderDrawColor(r, 0, 0, 0, 255);  		SDL_RenderFillRect(r, NULL); +  		for (int i = 0; i < drawList->len; i++) { -			SDL_SetRenderDrawColor( -				r, -				drawList->els[i].r, -				drawList->els[i].g, -				drawList->els[i].b, -				drawList->els[i].a -			);  			SDL_FRect rect = {  				.x = (float) drawList->els[i].x,  				.y = (float) drawList->els[i].y,  				.w = (float) drawList->els[i].w,  				.h = (float) drawList->els[i].h  			}; + +			SDL_SetRenderDrawColor( +				r, +				drawList->els[i].fill.r, +				drawList->els[i].fill.g, +				drawList->els[i].fill.b, +				drawList->els[i].fill.a +			);  			SDL_RenderFillRect(r, &rect); + +			SDL_SetRenderDrawColor( +				r, +				drawList->els[i].border.r, +				drawList->els[i].border.g, +				drawList->els[i].border.b, +				drawList->els[i].border.a +			); +			SDL_RenderRect(r, &rect); + +			if (drawList->els[i].image.index != 0) { +				SDL_SetTextureAlphaMod(textures[(int) drawList->els[i].image.index], drawList->els[i].image.opacity); +				SDL_RenderTexture(r, textures[(int) drawList->els[i].image.index], NULL, &rect); +			}  		} -		update(game, a); +		update(game, now, a); + +		if (SDL_GetAudioStreamQueued(stream) < (int) sizeof(musicBytes) / 8) { +			SDL_PutAudioStreamData(stream, musicBytes, sizeof(musicBytes)); +		}  		SDL_RenderPresent(r);  	} @@ -198,24 +682,32 @@ void game_init(void) {  	perm.end = heap + MEM_SIZE;  	game = new(&perm, 1, Game); -	game->state = (State) { -		.x = 100, -		.y = 100, -	}; +	game->state.currentLevel = 0; +	restart_level(game); + +	for (int i = 0; i < N_BUTTONS; i++) { +		game->state.buttonStates[i] = BUTTON_STATE_IDLE; +	}  }  __attribute((export_name("game_render"))) -DrawList *game_render(int width, int height) { +DrawList *game_render(int width, int height, int mousex, int mousey) {  	game->ui.width = width;  	game->ui.height = height; +	game->ui.mousex = mousex; +	game->ui.mousey = mousey;  	Arena scratch = perm; -	return render(game->state, &game->ui, &scratch); +	return render(&game->state, &game->ui, &scratch);  }  __attribute((export_name("game_update"))) -void game_update(int input) { +int game_update(int input, int mousex, int mousey, int now) {  	game->input = input; -	update(game, perm); +	game->ui.mousex = mousex; +	game->ui.mousey = mousey; +	return update(game, now, perm);  }  #endif + +#endif diff --git a/src/img.c b/src/img.c new file mode 100644 index 0000000..09f000b --- /dev/null +++ b/src/img.c @@ -0,0 +1,95 @@ +#define QOI_IMPLEMENTATION +#include "../lib/qoi/qoi.h" + +#include <stdio.h> + +typedef struct { +	unsigned char r, g, b, a; +} Color; + +int pixels(char *index, char *filename) { +	qoi_desc desc; +	Color *pixels = qoi_read(filename, &desc, 4); + +	Color colors[16]; +	int colorsLen = 0; +	char *compressed = malloc(desc.width * desc.height); + +	for (int i = 0; i < (int) (desc.width * desc.height); i++) { +		char c = -1; +		for (int j = 0; j < colorsLen; j++) { +			if (*((int *) &colors[j]) == *((int *) pixels + i)) { +				c = j; +				break; +			} +		} +		if (c == -1) { +			c = colorsLen; +			if (colorsLen >= 16) { +				return 1; +			} +			colors[colorsLen++] = pixels[i]; +		} +		compressed[i] = c; +	} + +	printf("const char imagePixels%s[%d * %d] = {\n", index, desc.width, desc.height); +	for (int y = 0; y < (int) desc.height; y++) { +		for (int x = 0; x < (int) desc.width; x++) { +			printf("%d,", compressed[x + y * desc.width]); +		} +		printf("\n"); +	} +	printf("};\n"); + +	return 0; +} + +int image(char *index, char *filename) { +	qoi_desc desc; +	Color *pixels = qoi_read(filename, &desc, 4); + +	Color colors[16]; +	int colorsLen = 0; +	char *compressed = malloc(desc.width * desc.height); + +	for (int i = 0; i < (int) (desc.width * desc.height); i++) { +		char c = -1; +		for (int j = 0; j < colorsLen; j++) { +			if (*((int *) &colors[j]) == *((int *) pixels + i)) { +				c = j; +				break; +			} +		} +		if (c == -1) { +			c = colorsLen; +			if (colorsLen >= 16) { +				return 1; +			} +			colors[colorsLen++] = pixels[i]; +		} +		compressed[i] = c; +	} + +	printf("{\n.width = %d,\n.height = %d,\n.colors = {\n", desc.width, desc.height); +	for (int i = 0; i < colorsLen; i++) { +		printf("\t{%u, %u, %u, %u},\n", colors[i].r, colors[i].g, colors[i].b, colors[i].a); +	} +	printf("},\n.pixels = imagePixels%s,\n},\n", index); + +	return 0; +} + +int main(int argc, char **argv) { +	if (argc != 4) { +		return 1; +	} + +	if (!strcmp(argv[1], "pixels")) { +		return pixels(argv[3], argv[2]); +	} else if (!strcmp(argv[1], "image")) { +		return image(argv[3], argv[2]); +	} else { +		return 1; +	} +} diff --git a/src/index.html.in b/src/index.html.in index fcfb8ef..5414f09 100644 --- a/src/index.html.in +++ b/src/index.html.in @@ -11,9 +11,9 @@ html, body {      overflow: hidden;      text-align: center;      width: 100%; +		color: white;  }  h1 { -    color: white;      margin: 0;  }  button { @@ -22,21 +22,123 @@ button {      margin: 0.3em;      width: 25%;  } + +canvas { +	margin: auto; +}  </style> -<canvas></canvas> +<div id="tutorial" style="overflow-y: scroll; height: 90vh"> +	<h1>Depths of the Mind</h1> + +	<p> +		The mind is an intricate thing, with many tiny components working together +		in perfect harmony... Until something came in and messed everything up! +	</p> + +	<p> +		If you want to set all the pieces of the mind right, you'll need to know how +		it works! +	</p> + +	<img src= +#include "../build/tutorial_black_white.png.b64" +			> + +	<p> +		The structure of the brain consists of black blocks filling a white void. +	</p> + +	<img src= +#include "../build/tutorial_red_yellow.png.b64" +			> + +	<p> +		Black blocks will turn red when clicked (if you have red in your inventory on the left). +		While the brain is running, black blocks next to red blocks will turn red! +		Red blocks will eventually tire to yellow, and then back to black. They don't stay red very long! +		This is what allows the brain to send signals along a series of black blocks. +		Yellow blocks can be placed with a right click on a black block (if you have one in your inventory) +	</p> + +	<img src= +#include "../build/tutorial_direction_red.png.b64" +			 > + +	<p> +		Sometimes, only one side of a block turns red. +		When this happens, it will try to move in that direction (and leave a yellow behind it) +		but if it cannot it will turn clockwise and try again. +	</p> + +	<span> +		<img src= +#include "../build/tutorial_blue_1.png.b64" +				 > +		<img src= +#include "../build/tutorial_blue_2.png.b64" +				 > + +	<p> +		Blue blocks need to be moved around to keep things running smoothly! +		When a blue block is touched by a single red block, it will start rolling +		away from it. +		Each level has a goal in faint red which needs at least one blue block to be delivered to it. +		Some levels will also have faint blue areas that create new blue blocks for you! +	</p> + +		<img src= +#include "../build/tutorial_blue_clone.png.b64" +				 > + +	<p> +		Finally, if a blue block touches a red block <em>while in motion</em> it will be cloned! +		One will be left behind and the other will continue. +	</p> + +	<button id="play-button">Play!</button> +</div> + +<canvas style="display: none;"></canvas>  <script>  // Mirror these in src/all.c  const INPUT_NONE  = 0; -const INPUT_UP = 1; -const INPUT_DOWN  = 2; -const INPUT_LEFT = 3; -const INPUT_RIGHT  = 4; +const INPUT_CLICK = 1; +const INPUT_RCLICK = 2; +const INPUT_PAUSE_PLAY = 3; +const INPUT_MOVE = 4; +const INPUT_RESTART = 5;  const WASM =  #include "../build/main.wasm.b64" +const MUSIC = +#include "../build/music.mp3.b64" + +const IMAGES = [ +#include "../build/continue.png.b64" +, +#include "../build/exit.png.b64" +, +#include "../build/pause.png.b64" +, +#include "../build/play.png.b64" +, +#include "../build/restart.png.b64" +, +#include "../build/nelson.png.b64" +, +#include "../build/nelson_left.png.b64" +, +#include "../build/nelson_up.png.b64" +, +#include "../build/nelson_right.png.b64" +, +#include "../build/nelson_down.png.b64" +, +]; +  async function main() {      let bytes   = Uint8Array.from(atob(WASM), function(c) {          return c.charCodeAt(0); @@ -46,54 +148,126 @@ async function main() {      let exports = wasm.exports;      let html    = document.querySelector("html");      let canvas  = document.querySelector("canvas"); +	const tutorial = document.querySelector("#tutorial");      let ctx     = canvas.getContext("2d");      let memory  = exports.memory; +	let mousex = 0; +	let mousey = 0; + +	const audio = new Audio(); +	audio.src = MUSIC; +	audio.volume = 0.2; +	audio.loop = true; +	let musicPlaying = false; + +	let images = [null]; +	for (let i = 0; i < IMAGES.length; i++) { +		const image = new Image(); +		image.src = IMAGES[i]; +		images.push(image); +	} + +	const start = Date.now(); +	function now() { +		return Date.now() - start; +	} +      function min(a, b) {          return b<a ? b : a;      } -    function max_width() { -        return html.clientHeight * 0.7 | 0; -    } -      function render() { -        let width  = canvas.width  = min(html.clientWidth, max_width()); -        let height = canvas.height = width; -        let ptr    = exports.game_render(width, height); +			if (64 + html.clientHeight * (20 / 16) > html.clientWidth) { +				canvas.width = html.clientWidth; +				canvas.height = (html.clientWidth - 64) * (16 / 20); +			} else { +				canvas.width = 64 + html.clientHeight * (20 / 16); +				canvas.height = html.clientHeight; +			} +        let width  = canvas.width; +        let height = canvas.height; +        let ptr    = exports.game_render(width, height, mousex, mousey);          let dl     = new Int32Array(memory.buffer, ptr);          let len    = dl[0];          let ops    = dl.subarray(1); +					ctx.fillStyle = "#000000"; +					ctx.fillRect(0, 0, width, height);          for (let i = 0; i < len; i++) { -            let op    = ops.subarray(5*i, 5*i+5); -					const color = new Uint8Array(new Uint32Array(ops.subarray(4, 5)).buffer); -					let style = `#${color[0].toString(16).padStart(2, "0")}${color[1].toString(16).padStart(2, "0")}${color[2].toString(16).padStart(2, "0")}`; -						ctx.fillStyle = style; +            let op    = ops.subarray(7*i, 7*i+7); +					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]); +					const image = new Uint8Array(new Uint32Array(op.subarray(6, 7)).buffer); +					if (image[0] !== 0) { +						console.log(image); +						ctx.globalAlpha = image[1] / 255; +						ctx.drawImage(images[image[0]], op[0], op[1], op[2], op[3]); +					}          }      } -    function onresize() { html.style.maxWidth = `${max_width()}px`; } +    function onresize() { /*html.style.maxWidth = `${max_width()}px`; */}      window.addEventListener("resize", onresize);      onresize(); -    document.addEventListener("keydown", function(e) { -        if (e.key == "w") { -            exports.game_update(INPUT_UP); -        } else if (e.key == "a") { -            exports.game_update(INPUT_LEFT); -        } else if (e.key == "s") { -            exports.game_update(INPUT_DOWN); -        } else if (e.key == "d") { -            exports.game_update(INPUT_RIGHT); -        } -    }) +	function doUpdate(input) { +		if (exports.game_update(input, mousex, mousey, now()) !== 0) { +			canvas.style.display = "none"; +			tutorial.style.display = "block"; +		} +	} + +	canvas.addEventListener("mousemove", function(e) { +		const rect = e.target.getBoundingClientRect(); +		mousex = e.clientX - rect.left; +		mousey = e.clientY - rect.top; +		doUpdate(INPUT_MOVE); +	}); + +	canvas.addEventListener("mousedown", function(e) { +		const rect = e.target.getBoundingClientRect(); +		mousex = e.clientX - rect.left; +		mousey = e.clientY - rect.top; +		if (e.button == 0) { +			doUpdate(INPUT_CLICK); +		} else if (e.button == 2) { +			e.preventDefault(); +			doUpdate(INPUT_RCLICK); +		} +	}); + +	document.getElementById("play-button").addEventListener("click", function (e) { +		if (!musicPlaying) { +			musicPlaying = true; +			audio.play(); +		} + +		canvas.style.display = "block"; +		tutorial.style.display = "none"; +	}); + +	canvas.addEventListener("contextmenu", function (e) { +		e.preventDefault(); +	}); + +	document.addEventListener("keydown", function (e) { +		if (e.key === " ") { +			doUpdate(INPUT_PAUSE_PLAY); +		} else if (e.key == "r") { +			doUpdate(INPUT_RESTART); +		} +	});      function animate() {          // TODO: stop requesting frames when state is static          requestAnimationFrame(animate); -        exports.game_update(INPUT_NONE); +				doUpdate(INPUT_NONE);          render();      }      requestAnimationFrame(animate); diff --git a/src/levels.c b/src/levels.c new file mode 100644 index 0000000..a5f1d15 --- /dev/null +++ b/src/levels.c @@ -0,0 +1,488 @@ +#ifndef INCLUDE_LEVELS_C +#define INCLUDE_LEVELS_C + +#include "all.c" + +#define _ EMPTY, +#define B BLACK, +#define O BLUE, +#define R RED, +#define r YELLOW, +#define RL RED_LEFT, +#define RR RED_RIGHT, +#define RU RED_UP, +#define RD RED_DOWN, +static Level levels[] = { +	{ +		.grid = { +			// level 1 +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  RU _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  B  O  _  _  _  _  _  _  _  _  _  _  _  _  _  B +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +		}, +		.goalx = 18, +		.goaly = 7, +		.placeableCells = { +			RED, +		}, +	}, +	{ +		// level 2 +		.grid = { +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  B  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  O  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  B  _  _  _  _  _  _  _  _  _  _  _  _  _  _  B +			_  _  _  _  _  B  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +		}, +		.goalx = 18, +		.goaly = 10, +		.placeableCells = { +			RED, +			RED, +		}, +	}, +	{ +		// level 3 +		.grid = { +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  B  B  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  B  O  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  B  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  B  _  _  _  _  _  _  _  _  _  _  _  _  _  _  B +			_  _  _  _  B  B  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +		}, +		.goalx = 18, +		.goaly = 10, +		.placeableCells = { +			BLACK, +			RED, +		}, +	}, +	{ +		// level 4 +		.grid = { +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  B  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  B  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  B  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  B  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  B  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  B  B  B  B  B  B  B  B  B  B  B  _  _  _  _  _  _  _ +			_  _  B  _  B  O  _  _  _  _  _  O  B  _  _  _  _  _  _  _ +			_  _  _  _  B  _  _  _  _  _  B  B  B  _  _  _  _  _  _  _ +			_  _  _  _  B  _  _  _  _  _  B  _  _  _  _  _  _  _  _  _ +			_  _  _  _  B  _  _  _  _  _  _  B  _  _  _  _  _  _  _  _ +			_  _  _  _  _  B  B  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  B  _  _  _  _  O  B  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  B  _  _  _  _  _  _  _  _  _  _ +		}, +		.goalx = 10, +		.goaly = 13, +		.placeableCells = { +			RED, +		}, +	}, +	{ +		// level 5 +		.grid = { +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  R  B  _  _  _  _  _  _ +			_  _  _  _  _  _  B  _  _  _  _  _  r  B  _  _  _  _  _  _ +			_  _  _  _  r  RD _  _  _  _  _  _  _  _  B  _  _  _  _  _ +			_  _  _  _  B  B  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  RU _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  B  _  _  _  _  _  _  _  _  _  _  _ +			_  _  R  r  _  _  _  _  B  _  _  _  _  _  _  _  _  _  _  _ +			_  _  B  B  _  _  _  _  B  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  B  _  _  _  RU _  _  _  _  B  _  _ +			_  _  _  _  _  _  _  _  B  _  _  _  _  B  _  _  _  _  _  _ +			_  _  _  _  _  _  O  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  B  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +		}, +		.goalx = 16, +		.goaly = 10, +		.placeableCells = { +			RED, +		}, +	}, +	{ +		// level 6 +		.grid = { +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  RU _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  B  _  _  _  _  O  B  _  _  _  _  _  _  _  _ +			_  _  _  _  _  B  _  _  _  _  O  B  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  B  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +		}, +		.goalx = 6, +		.goaly = 11, +		.placeableCells = { +			RED, +			RED_UP, +			RED_DOWN, +			RED_LEFT, +			RED_RIGHT, +		}, +	}, +	{ +		// level 7 +		.grid = { +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  B  B  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  B  B  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  B  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +		}, +		.need9 = 1, +		.goalx = 6, +		.goaly = 11, +		.spawnerx = 10, +		.spawnery = 6, +		.spawnRate = 6, +		.placeableCells = { +			RED, +			YELLOW, +			RED_UP, +			BLACK, +		}, +	}, +	{ +		// level 8 +		.grid = { +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  B  B  B  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  B  B  _  _  _  _  _  _  B  _  _  _  _  _  _  _ +			_  _  _  _  B  B  B  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +		}, +		.goalx = 11, +		.goaly = 7, +		.spawnerx = 6, +		.spawnery = 7, +		.need9 = 1, +		.placeableCells = { +			RED, +			YELLOW, +		}, +	}, +	{ +		// level 9 +		.grid = { +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  RD _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  O  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  B  O  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  RU _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +		}, +		.goalx = 12, +		.goaly = 7, +		.placeableCells = { +			RED, +		}, +	}, +	{ +		// level 10 +		.grid = { +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  RU _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  B  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  B  _  _  _  _  B  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  B  B  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  B  RD _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +		}, +		.goalx = 7, +		.goaly = 9, +		.spawnerx = 4, +		.spawnery = 3, +		.need9 = 1, +		.spawnRate = 7, +		.placeableCells = { +			RED, +			YELLOW, +			BLACK, +			BLACK, +			BLACK, +			BLACK, +			BLACK, +			BLACK, +		}, +	}, +	{ +		// level 11 +		.grid = { +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			B  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			B  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			B  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			B  B  B  B  B  B  B  B  RL _  _  _  _  _  _  _  _  _  _  _ +		}, +		.goalx = 15, +		.goaly = 15, +		.spawnerx = 9, +		.spawnery = 15, +		.need9 = 1, +		.spawnRate = 7, +		.placeableCells = { +			RED, +			YELLOW, +			BLACK, +			BLACK, +		}, +	}, +	{ +		// level 12 +		.grid = { +			_  _  _  _  _  B  _  _  _  _  _  _  RU _  _  _  _  _  _  _ +			_  _  _  _  RU _  _  _  _  _  _  _  _  B  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  B  B  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  B  B  _  _  _  _  _  _  _  _  _  _  _ +			_  B  _  _  _  _  _  _  O  B  B  _  _  _  _  _  _  _  _  _ +			RU _  _  _  _  _  _  _  _  B  B  _  _  _  _  _  _  _  _  _ +			_  _  _  _  B  _  _  _  _  _  _  _  _  _  RU _  _  _  _  _ +			_  _  _  _  _  RU _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			B  _  _  _  _  _  _  _  _  RU _  _  _  _  _  _  _  _  _  _ +			_  RU _  _  _  _  _  _  B  _  _  _  _  _  _  _  _  _  _  _ +		}, +		.goalx = 8, +		.goaly = 7, +		.spawnerx = 13, +		.spawnery = 7, +		.need9 = 1, +		.spawnRate = 3, +		.placeableCells = { +			RED, +			RED, +			YELLOW, +			YELLOW, +		}, +	}, +	{ +		// level 14 +		// An attempt at a red herring +		// This one has some similarities to level 12 so I didn't want it to be level 13 +		.grid = { +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  B  B  _  R  B  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  B  B  _  r  B  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  B  O  _  _  _  _  _  _  _  _  _  _  _  RU _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +		}, +		.goalx = 10, +		.goaly = 7, +		.spawnerx = 15, +		.spawnery = 7, +		.need9 = 1, +		.spawnRate = 3, +		.placeableCells = { +			RED, +			RED_UP, +			YELLOW, +		}, +	}, +	{ +		// level 15 +		// This one is a bit tricky +		.grid = { +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  RU _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  B  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  B  _  _  _  _  B  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  B  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +		}, +		.goalx = 7, +		.goaly = 9, +		.spawnerx = 4, +		.spawnery = 3, +		.need9 = 1, +		.spawnRate = 2, +		.placeableCells = { +			RED, +			RED, +			YELLOW, +			YELLOW, +			BLACK, +			BLACK, +			BLACK, +			BLACK, +			BLACK, +			BLACK, +		}, +	}, +	{ +		// level ? +		// Must go after blues turning yellows into reds is introduced +		.grid = { +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  O  B  B  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  B  B  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  B  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +			_  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _  _ +		}, +		.need9 = 1, +		.goalx = 6, +		.goaly = 11, +		.placeableCells = { +			RED, +			RED_UP, +			RED_DOWN, +			YELLOW, +			BLACK, +			BLACK, +		}, +	}, +}; +#undef _ +#undef B +#undef O +#undef R +#undef r +#undef RR +#undef RL +#undef RU +#undef RD + +#endif // INCLUDE_LEVELS_C diff --git a/src/tick.c b/src/tick.c new file mode 100644 index 0000000..338017b --- /dev/null +++ b/src/tick.c @@ -0,0 +1,376 @@ +#ifndef INCLUDE_TICK_C +#define INCLUDE_TICK_C + +#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)); + +	if (lastState->spawnerCount > 0) { +		game->state.spawnerCount = lastState->spawnerCount - 1; +	} + +	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] = BLACK; // used to be RED +					} else { +						game->state.grid[x + y * GRIDWIDTH] = BLACK; +					} +					break; +				case BLUE: +					// Win condition +					if (game->state.goalx == x && game->state.goaly == y) { +						if (!game->state.need9) { +							game->state.levelComplete = 1; +						} else { +							if (game->state.blues < 9) { +								game->state.blues++; +								if (game->state.blues == 9) { +									game->state.levelComplete = 1; +								} +							} +						} +						game->state.grid[x + y * GRIDWIDTH] = EMPTY; +					} +					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: +					blues = 0; +					if ( +						x > 0 && +						lastState->grid[x - 1 + y * GRIDWIDTH] == BLUE_RIGHT +					) { +						blues++; +						blue = BLUE_RIGHT; +					} +					if ( +						x < GRIDWIDTH - 1 && +						lastState->grid[x + 1 + y * GRIDWIDTH] == BLUE_LEFT +					) { +						blues++; +						blue = BLUE_LEFT; +					} +					if ( +						y > 0 && +						lastState->grid[x + (y - 1) * GRIDWIDTH] == BLUE_DOWN +					) { +						blues++; +						blue = BLUE_DOWN; +					} +					if ( +						y < GRIDHEIGHT - 1 && +						lastState->grid[x + (y + 1) * GRIDWIDTH] == BLUE_UP +					) { +						blues++; +						blue = BLUE_UP; +					} +					if (blues == 1) { +						game->state.grid[x + y * GRIDWIDTH] = blue; +					} else if (blues >= 2) { +						game->state.grid[x + y * GRIDWIDTH] = BLUE; +					} + +					// Spawner +					if ( +						game->state.spawnerx != 0 && +						game->state.spawnery != 0 && +						game->state.grid[x + y * GRIDWIDTH] == EMPTY && +						x == game->state.spawnerx && +						y == game->state.spawnery && +						lastState->spawnerCount == 0 +					) { +						game->state.grid[x + y * GRIDWIDTH] = BLUE; +						game->state.spawnerCount = levels[game->state.currentLevel].spawnRate; +					} +					break; +			} +		} +	} +} + +#endif // INCLUDE_TICK_C diff --git a/src/types.c b/src/types.c new file mode 100644 index 0000000..c40d9c3 --- /dev/null +++ b/src/types.c @@ -0,0 +1,166 @@ +#ifndef INCLUDE_TYPES_C +#define INCLUDE_TYPES_C + +#include "all.c" + +#define MEM_SIZE (1<<24) +#define GRIDWIDTH 20 +#define GRIDHEIGHT 16 +#define TICK_LENGTH 200 +#define GRID_OFFSET_X 64 +#define MAX_PLACEABLE_CELLS 16 + +typedef struct { +	unsigned char r, g, b, a; +} Color; + +typedef struct { +	char index; +	unsigned char opacity; +	char padding[2]; +} ImageRef; + +typedef struct { +	int x, y, w, h; +	Color fill; +	Color border; +	ImageRef image; +} 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 { +	IMAGE_NULL, +	IMAGE_CONTINUE, +	IMAGE_EXIT, +	IMAGE_PAUSE, +	IMAGE_PLAY, +	IMAGE_RETRY, +	IMAGE_NELSON, +	IMAGE_NELSON_LEFT, +	IMAGE_NELSON_UP, +	IMAGE_NELSON_RIGHT, +	IMAGE_NELSON_DOWN, +	N_IMAGES, +}; + +static const char colorImages[N_COLORS] = { +	/*[RED] = IMAGE_NELSON, +	[RED_LEFT] = IMAGE_NELSON_LEFT, +	[RED_UP] = IMAGE_NELSON_UP, +	[RED_RIGHT] = IMAGE_NELSON_RIGHT, +	[RED_DOWN] = IMAGE_NELSON_DOWN,*/ +}; + +enum { +	BUTTON_BACK, +	BUTTON_RETRY, +	BUTTON_PLAY, +	BUTTON_CONTINUE, +	N_BUTTONS, +}; + +typedef struct { +	int width, height; +	int mousex, mousey; +} UI; + +typedef enum { +	BUTTON_STATE_IDLE, +	BUTTON_STATE_HOVERED, +	BUTTON_STATE_PRESSED +} ButtonState; + +typedef struct { +	int x, y, w, h; +} Button; + +typedef struct { +	// 0 if only 1 blue is needed, 1 if 9 are needed +	char need9; +	char spawnRate; +	char grid[GRIDWIDTH * GRIDHEIGHT]; +	int goalx, goaly; +	int spawnerx, spawnery; +	char placeableCells[MAX_PLACEABLE_CELLS]; // Color +} Level; + +typedef struct { +	uint64_t lastTick; +	char playing; +	char levelComplete; +	// How many blues have reached the end +	char blues; +	char need9; +	char grid[GRIDWIDTH * GRIDHEIGHT]; +	int goalx, goaly; +	int spawnerx, spawnery; +	int spawnerCount; +	ButtonState buttonStates[N_BUTTONS]; +	int currentLevel; +	char placeableCells[MAX_PLACEABLE_CELLS]; +} State; + +// Mirror these in src/index.html.in +enum { +	INPUT_NONE, +	INPUT_CLICK, +	INPUT_RCLICK, +	INPUT_PAUSE_PLAY, +	INPUT_MOVE, +	INPUT_RESTART, +}; + +typedef struct { +	State state; +	UI ui; +	int input; +} 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; +} + +#endif // INCLUDE_TYPES_C | 
