#include #include #if SDL #include #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 idle_colour; int hover_colour; int pressed_colour; } ButtonStyle; typedef struct { int x, y, w, h; ButtonState state; } Button; typedef struct { int x, y, w, h; Color fill; Color border; } DrawElement; typedef struct { DrawElement bg; } SidePanel; typedef struct { int len; DrawElement els[512]; SidePanel panel; } 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; typedef struct { uint64_t lastTick; char playing; int grid[GRIDWIDTH * GRIDHEIGHT]; } 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} }; static const ButtonStyle button_style = { .idle_colour = BLACK, .hover_colour = RED, .pressed_colour = YELLOW }; enum { BUTTON_RETRY, BUTTON_BACK, N_BUTTONS, }; static Button buttons[N_BUTTONS] = { {.x = 64, .y = 64, .w = 128, .h = 32, .state = BUTTON_STATE_IDLE}, {.x = 64, .y = 114, .w = 128, .h = 32, .state = BUTTON_STATE_IDLE}, }; static DrawList *render(State *state, UI *ui, Arena *a) { (void) ui; DrawList *drawList = new(a, 1, DrawList); int cellWidth = (ui->width - GRID_OFFSET_X) / GRIDWIDTH; int cellHeight = ui->height / GRIDHEIGHT; for (int x = 0; x < GRIDWIDTH; x++) { for (int y = 0; y < GRIDHEIGHT; y++) { drawList->els[drawList->len++] = (DrawElement) { .x = cellWidth * x + GRID_OFFSET_X, .y = cellHeight * y, .w = cellWidth, .h = cellHeight, .fill = colors[state->grid[x + GRIDWIDTH * y]], .border = {0, 0, 0, 255}, }; } } drawList->panel = (SidePanel) { .bg = (DrawElement) { .x = 0, .y = 0, .w = GRID_OFFSET_X, .h = ui->height, .fill = colors[YELLOW], .border = {0, 0, 0, 255} } }; return drawList; } static void update_buttons(const int mx, const int my, const int mstate) { for (int i = 0; i < N_BUTTONS; i++) { if ( mx > buttons[i].x && mx < buttons[i].x + buttons[i].w && my > buttons[i].y && my < buttons[i].y + buttons[i].h ) { if (mstate == 1) { buttons[i].state = BUTTON_STATE_PRESSED; } else buttons[i].state = BUTTON_STATE_HOVERED; } else { buttons[i].state = BUTTON_STATE_IDLE; } } } static void update(Game *game, uint64_t now, Arena a) { const int offset_width = game->ui.width - GRID_OFFSET_X; switch (game->input) { int x, y; case INPUT_CLICK: x = game->mousex * GRIDWIDTH / offset_width; y = game->mousey * GRIDHEIGHT / game->ui.height; game->state.grid[x + y * GRIDWIDTH] = (game->state.grid[x + y * GRIDWIDTH] + 1) % (sizeof(colors) / sizeof(colors[0])); break; case INPUT_PAUSE_PLAY: game->state.playing = !game->state.playing; break; default: break; } 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; } } } } } #if SDL #include void render_button(SDL_Renderer* r, const Button* b) { Color colour = colors[BLACK]; switch (b->state) { case BUTTON_STATE_IDLE: colour = colors[button_style.idle_colour]; break; case BUTTON_STATE_HOVERED: colour = colors[button_style.hover_colour]; break; case BUTTON_STATE_PRESSED: colour = colors[button_style.pressed_colour]; break; } SDL_SetRenderDrawColor(r, colour.r, colour.g, colour.b, colour.a); SDL_FRect rect = { .x = (float) b->x, .y = (float) b->y, .w = (float) b->w, .h = (float) b->h }; SDL_RenderFillRect(r, &rect); } void render_side_panel(SDL_Renderer* r, SidePanel* panel) { Color* bg_colour = &panel->bg.fill; SDL_FRect rect = { .x = (float) panel->bg.x, .y = (float) panel->bg.y, .w = (float) panel->bg.w, .h = (float) panel->bg.h, }; SDL_SetRenderDrawColor(r, bg_colour->r, bg_colour->g, bg_colour->b, bg_colour->a); SDL_RenderFillRect(r, &rect); for (int i = 0; i < N_BUTTONS; i++) { render_button(r, &buttons[i]); } } int main(int argc, char **argv) { (void) argc; (void) argv; char *mem = SDL_malloc(MEM_SIZE); Arena a = { .start = mem, .end = mem + MEM_SIZE, }; Game *game = new(&a, 1, Game); game->state.grid[0] = 0; game->state.playing = 0; game->ui = (UI) { .width = 640, .height = 480, }; SDL_Init(SDL_INIT_VIDEO); SDL_Window *w = SDL_CreateWindow( "LDJam 57", game->ui.width, game->ui.height, 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); int mousex = 0, mousey = 0, mousestate = 0; 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->input = INPUT_CLICK; game->mousex = (int) e.button.x - GRID_OFFSET_X; game->mousey = (int) e.button.y; mousestate = SDL_BUTTON_MASK(e.motion.state); break; case SDL_EVENT_MOUSE_BUTTON_UP: mousestate = 0; break; case SDL_EVENT_MOUSE_MOTION: mousex = (int) e.motion.x; mousey = (int) e.motion.y; break; case SDL_EVENT_KEY_DOWN: switch (e.key.key) { case SDLK_SPACE: game->input = INPUT_PAUSE_PLAY; break; } break; case SDL_EVENT_WINDOW_RESIZED: SDL_GetWindowSize(w, &game->ui.width, &game->ui.height); break; } update_buttons(mousex, mousey, mousestate); } Arena scratch = a; 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_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); } render_side_panel(r, &drawList->panel); update(game, now, a); SDL_RenderPresent(r); } return 0; } #elif WASM static Game *game; static Arena perm; __attribute((export_name("game_init"))) void game_init(void) { static char heap[MEM_SIZE]; perm.start = heap; perm.end = heap + MEM_SIZE; game = new(&perm, 1, Game); game->state.grid[0] = 1; } __attribute((export_name("game_render"))) DrawList *game_render(int width, int height) { game->ui.width = width; game->ui.height = height; Arena scratch = perm; return render(&game->state, &game->ui, &scratch); } __attribute((export_name("game_update"))) void game_update(int input, int mousex, int mousey) { game->input = input; game->mousex = mousex; game->mousey = mousey; update(game, perm); } #endif