Back to shtanton's homepage
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--Makefile21
-rw-r--r--src/all.c184
-rw-r--r--src/index.html.in52
-rw-r--r--src/tick.c19
-rw-r--r--src/types.c3
6 files changed, 226 insertions, 54 deletions
diff --git a/.gitignore b/.gitignore
index a0be67f..fa7cb8e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -8,3 +8,4 @@ x64
*.vcxproj.filters
*.vcxproj.user
*.sln.DotSettings.user
+.ccls-cache
diff --git a/Makefile b/Makefile
index d48073d..8b6c2e4 100644
--- a/Makefile
+++ b/Makefile
@@ -1,8 +1,8 @@
-build/main: src/all.c src/all.h build/images.c src/types.c src/levels.c src/tick.c
+build/main: src/all.c build/images.c build/music.c src/types.c src/levels.c src/tick.c
mkdir -p build
gcc -Wall -Wextra -Wpedantic -DSDL $$(pkg-config --libs sdl3) -o build/main src/all.c
-build/main.wasm: src/all.c src/all.h
+build/main.wasm: src/all.c src/types.c src/levels.c src/tick.c
mkdir -p build
clang --target=wasm32 -nostdlib -DWASM -Wall -Wextra -Wpedantic \
-Wl,--no-entry -fno-builtin -o build/main.wasm src/all.c
@@ -11,11 +11,16 @@ build/music.mp3.b64: res/music.mp3
mkdir -p build
(printf '"data:audio/mpeg;base64,'; base64 < res/music.mp3 | tr -d '\n'; printf '"') > build/music.mp3.b64
+build/%.png.b64: res/%.png
+ mkdir -p build
+ (printf '"data:image/png;base64,'; base64 < $< | tr -d '\n'; printf '"') > $@
+
build/main.wasm.b64: build/main.wasm
mkdir -p build
(printf '"'; base64 < build/main.wasm | tr -d '\n'; printf '"') > build/main.wasm.b64
-build/index.html: src/index.html.in build/main.wasm.b64 build/music.mp3.b64
+build/index.html: src/index.html.in build/main.wasm.b64 build/music.mp3.b64 \
+ build/continue.png.b64 build/exit.png.b64 build/pause.png.b64 build/play.png.b64 build/restart.png.b64
mkdir -p build
clang -E -P -undef -nostdinc -x c -o build/index.html src/index.html.in
@@ -27,6 +32,16 @@ build/%.qoi: res/%.png
mkdir -p build
magick $< $@
+build/music.pcm: res/music.mp3
+ ffmpeg -i res/music.mp3 -acodec pcm_s16le -f s16le build/music.pcm
+
+build/music.c: build/music.pcm
+ ( \
+ echo 'unsigned char musicBytes[] = {' && \
+ xxd -i < build/music.pcm && \
+ echo '};' \
+ ) > build/music.c
+
build/images.c: build/continue.qoi build/exit.qoi build/pause.qoi build/play.qoi build/restart.qoi build/img
mkdir -p build
(\
diff --git a/src/all.c b/src/all.c
index 3e62732..6e1525a 100644
--- a/src/all.c
+++ b/src/all.c
@@ -101,40 +101,76 @@ static const Button buttons[N_BUTTONS] = {
[BUTTON_CONTINUE] = {.x = BUTTON_SPACING, .y = BUTTON_SPACER(BUTTON_CONTINUE), .w = BUTTON_SIZE, .h = BUTTON_SIZE},
};
-static DrawList *render(State *state, UI *ui, Arena *a) {
- (void) ui;
+static int getPlaceAction(int currentColor, int cellx, int celly, float cellWidth, float cellHeight) {
+ int hoverColor = EMPTY;
+
+ 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 hoverColor;
+}
+
+static DrawList *render(State *state, UI *ui, Arena *a) {
DrawList *drawList = new(a, 1, DrawList);
- int cellWidth = (ui->width - GRID_OFFSET_X) / GRIDWIDTH;
- int cellHeight = ui->height / GRIDHEIGHT;
+ float cellWidth = (float) (ui->width - GRID_OFFSET_X) / GRIDWIDTH;
+ float cellHeight = (float) 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 = {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 + GRID_OFFSET_X + subx * cellWidth / 2,
.y = cellHeight * y + suby * cellHeight / 2,
- .w = cellWidth / 2,
- .h = cellHeight / 2,
+ .w = cellWidth / 2 + (1 - subx),
+ .h = cellHeight / 2 + (1 - suby),
.fill = colors[state->grid[x + GRIDWIDTH * y]][subx + 2 * suby],
- .border = {0, 0, 0, 0},
};
}
}
}
}
- // render end cell
+ // 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},
+ };
+ }
+
+ // Goal
drawList->els[drawList->len++] = (DrawElement) {
.x = cellWidth * state->goalx + GRID_OFFSET_X,
.y = cellHeight * state->goaly,
@@ -142,10 +178,44 @@ static DrawList *render(State *state, UI *ui, Arena *a) {
.h = cellHeight,
.fill = {255, 0, 0, 63},
.border = {255, 0, 0, 255},
- .image = 5,
};
- // render side panel
+ // 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) {
+ 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,
+ };
+ }
+ }
+ }
+ }
+
+ // Side panel
drawList->els[drawList->len++] = (DrawElement) {
.x = 0,
.y = 0,
@@ -243,19 +313,32 @@ static void update(Game *game, uint64_t now, Arena a) {
switch (game->input) {
int x, y;
case INPUT_CLICK:
- x = (game->mousex - GRID_OFFSET_X) * GRIDWIDTH / offset_width;
- y = game->mousey * GRIDHEIGHT / game->ui.height;
- if (x >= 0) { // TODO - 0 isn't far left of grid for some reason, it's slightly more
- game->state.grid[x + y * GRIDWIDTH] = (game->state.grid[x + y * GRIDWIDTH] + 1) % (sizeof(colors) / sizeof(colors[0]));
- // TODO - some ignore list for which cells we have left to place
- game->state.placedCells[0] = BLACK;
+ 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]));
+ game->state.grid[x + y * GRIDWIDTH] = getPlaceAction(
+ game->state.grid[x + y * GRIDWIDTH],
+ cellx,
+ celly,
+ cellWidth,
+ cellHeight
+ );
}
+ // TODO - some ignore list for which cells we have left to place
+ game->state.placedCells[0] = BLACK;
+
for (int i = 0; i < N_BUTTONS; i++) {
if (
- game->mousex > buttons[i].x && game->mousex < buttons[i].x + buttons[i].w &&
- game->mousey > game->ui.height - buttons[i].y &&
- game->mousey < game->ui.height - buttons[i].y + buttons[i].h
+ 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;
@@ -280,8 +363,8 @@ static void update(Game *game, uint64_t now, Arena a) {
}
break;
case INPUT_RCLICK:
- x = (game->mousex - GRID_OFFSET_X) * GRIDWIDTH / offset_width;
- y = game->mousey * GRIDHEIGHT / game->ui.height;
+ x = (game->ui.mousex - GRID_OFFSET_X) * GRIDWIDTH / (game->ui.width - GRID_OFFSET_X);
+ y = game->ui.mousey * GRIDHEIGHT / game->ui.height;
game->state.grid[x + y * GRIDWIDTH] = EMPTY;
break;
case INPUT_PAUSE_PLAY:
@@ -315,6 +398,10 @@ typedef struct {
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;
@@ -326,20 +413,19 @@ int main(int argc, char **argv) {
};
Game *game = new(&a, 1, Game);
- 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.currentLevel = 0;
- game->state.playing = 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,
@@ -369,6 +455,15 @@ int main(int argc, char **argv) {
}
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();
@@ -379,14 +474,19 @@ int main(int argc, char **argv) {
case SDL_EVENT_QUIT:
return 0;
case SDL_EVENT_MOUSE_BUTTON_DOWN:
- game->mousex = (int) e.button.x;
- game->mousey = (int) e.button.y;
+ 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 SDLK_SPACE:
@@ -441,6 +541,10 @@ int main(int argc, char **argv) {
update(game, now, a);
+ if (SDL_GetAudioStreamQueued(stream) < (int) sizeof(musicBytes) / 8) {
+ SDL_PutAudioStreamData(stream, musicBytes, sizeof(musicBytes));
+ }
+
SDL_RenderPresent(r);
}
@@ -466,9 +570,11 @@ void game_init(void) {
}
__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);
}
@@ -476,8 +582,8 @@ DrawList *game_render(int width, int height) {
__attribute((export_name("game_update")))
void game_update(int input, int mousex, int mousey, int now) {
game->input = input;
- game->mousex = mousex;
- game->mousey = mousey;
+ game->ui.mousex = mousex;
+ game->ui.mousey = mousey;
update(game, now, perm);
}
diff --git a/src/index.html.in b/src/index.html.in
index aca8b6d..14731f6 100644
--- a/src/index.html.in
+++ b/src/index.html.in
@@ -32,6 +32,8 @@ const INPUT_NONE = 0;
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"
@@ -39,6 +41,19 @@ const WASM =
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"
+,
+];
+
async function main() {
let bytes = Uint8Array.from(atob(WASM), function(c) {
return c.charCodeAt(0);
@@ -51,12 +66,22 @@ async function main() {
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;
@@ -73,13 +98,16 @@ async function main() {
function render() {
let width = canvas.width = min(html.clientWidth, max_width());
let height = canvas.height = width;
- let ptr = exports.game_render(width, 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);
+ console.log("frame");
for (let i = 0; i < len; i++) {
- let op = ops.subarray(6*i, 6*i+6);
+ 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;
@@ -87,6 +115,10 @@ async function main() {
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]);
+ if (op[6] !== 0) {
+ ctx.globalAlpha = 1;
+ ctx.drawImage(images[op[6]], op[0], op[1], op[2], op[3]);
+ }
}
}
@@ -94,9 +126,15 @@ async function main() {
window.addEventListener("resize", onresize);
onresize();
+ canvas.addEventListener("mousemove", function(e) {
+ mousex = e.clientX;
+ mousey = e.clientY;
+ exports.game_update(INPUT_MOVE, mousex, mousey, now());
+ });
+
canvas.addEventListener("mousedown", function(e) {
- const mousex = e.clientX;
- const mousey = e.clientY;
+ mousex = e.clientX;
+ mousey = e.clientY;
if (e.button == 0) {
exports.game_update(INPUT_CLICK, mousex, mousey, now());
} else if (e.button == 2) {
@@ -115,14 +153,16 @@ async function main() {
document.addEventListener("keydown", function (e) {
if (e.key === " ") {
- exports.game_update(INPUT_PAUSE_PLAY, 0, 0, now());
+ exports.game_update(INPUT_PAUSE_PLAY, mousex, mousey, now());
+ } else if (e.key == "r") {
+ exports.game_update(INPUT_RESTART, mousex, mousey, now());
}
});
function animate() {
// TODO: stop requesting frames when state is static
requestAnimationFrame(animate);
- exports.game_update(INPUT_NONE, 0, 0, now());
+ exports.game_update(INPUT_NONE, mousex, mousey, now());
render();
}
requestAnimationFrame(animate);
diff --git a/src/tick.c b/src/tick.c
index 4b255df..598087a 100644
--- a/src/tick.c
+++ b/src/tick.c
@@ -302,30 +302,39 @@ static void tick(Game *game, Arena a) {
}
break;
case EMPTY:
- // TODO: same as multiple reds
+ blues = 0;
if (
x > 0 &&
lastState->grid[x - 1 + y * GRIDWIDTH] == BLUE_RIGHT
) {
- game->state.grid[x + y * GRIDWIDTH] = BLUE_RIGHT;
+ blues++;
+ blue = BLUE_RIGHT;
}
if (
x < GRIDWIDTH - 1 &&
lastState->grid[x + 1 + y * GRIDWIDTH] == BLUE_LEFT
) {
- game->state.grid[x + y * GRIDWIDTH] = BLUE_LEFT;
+ blues++;
+ blue = BLUE_LEFT;
}
if (
y > 0 &&
lastState->grid[x + (y - 1) * GRIDWIDTH] == BLUE_DOWN
) {
- game->state.grid[x + y * GRIDWIDTH] = BLUE_DOWN;
+ blues++;
+ blue = BLUE_DOWN;
}
if (
y < GRIDHEIGHT - 1 &&
lastState->grid[x + (y + 1) * GRIDWIDTH] == BLUE_UP
) {
- game->state.grid[x + y * 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;
}
break;
}
diff --git a/src/types.c b/src/types.c
index bd6b858..bee6b05 100644
--- a/src/types.c
+++ b/src/types.c
@@ -68,6 +68,7 @@ enum {
typedef struct {
int width, height;
+ int mousex, mousey;
} UI;
typedef enum {
@@ -102,6 +103,7 @@ enum {
INPUT_CLICK,
INPUT_RCLICK,
INPUT_PAUSE_PLAY,
+ INPUT_MOVE,
INPUT_RESTART,
};
@@ -109,7 +111,6 @@ 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)))