Skip to content

Commit

Permalink
✨ Added pattern placing controls
Browse files Browse the repository at this point in the history
  • Loading branch information
typio committed Mar 7, 2024
1 parent 11b74a2 commit 4d657e3
Show file tree
Hide file tree
Showing 5 changed files with 274 additions and 48 deletions.
91 changes: 88 additions & 3 deletions src/main.zig
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,23 @@ var scene_height: u32 = 0;
var paused: bool = false;
var step: u32 = 0;

var dragging: bool = false;
var mouseX: u32 = 0;
var mouseY: u32 = 0;

var SELECTED_PATTERN_BUFFER = [_]u8{ 'o', '!' } ++ [_]u8{0} ** 998;

// NOTE: Usage -
// const msg = std.fmt.allocPrint(allocator, "", .{ }) catch return;
// defer allocator.free(msg);
// debug_print(msg.ptr, @intCast(msg.len));
extern fn debug_print(message: [*]const u8, length: u8) void;

export fn alloc(length: u8) [*]const u8 {
const memory = allocator.alloc(u8, length) catch unreachable;
return memory.ptr;
}

export fn set_window_dimensions(w: u32, h: u32) void {
width = w;
height = h;
Expand All @@ -43,12 +54,30 @@ export fn get_output_buffer_pointer() *[OUTPUT_BUFFER_SIZE]u8 {
return &OUTPUT_BUFFER;
}

export fn pause() void {
export fn pause() bool {
paused = !paused;
return paused;
}

export fn clear() void {
@memset(&SCENE_BUFFER, 0);
}

export fn click() void {
place_cells();
}

export fn set_dragging(isDragging: bool) void {
dragging = isDragging;
}

export fn move_mouse(x: u32, y: u32) void {
mouseX = x;
mouseY = y;
}

export fn mouse_click(x: u32, y: u32) void {
SCENE_BUFFER[(y / CELL_SIZE * scene_width + x / CELL_SIZE)] = 1;
export fn select_pattern(rle: [*]const u8) void {
@memcpy(&SELECTED_PATTERN_BUFFER, rle);
}

export fn setup() void {
Expand All @@ -63,9 +92,61 @@ export fn setup() void {
SCENE_BUFFER[(y * scene_width + x)] = 1;
}

fn place_cells() void {
var dx: usize = 0;
var dy: usize = 0;
var run_count: usize = 0;
var cell_value: u8 = 0;
for (SELECTED_PATTERN_BUFFER) |code| {
switch (code) {
'0'...'9' => {
run_count *= 10;
run_count += code - '0';
continue;
},
'o' => {
cell_value = 1;
},
'b' => {
cell_value = 0;
},
'!' => {
break;
},
'$' => {
dy += 1;
dx = 0;
run_count = 0;
continue;
},
else => unreachable,
}

for (0..@max(1, run_count)) |_| {
SCENE_BUFFER[(mouseY / CELL_SIZE + dy) * scene_width + mouseX / CELL_SIZE + dx] = cell_value;
dx += 1;
}
run_count = 0;
}
}

export fn draw() void {
@memset(&OUTPUT_BUFFER, 0);

// Draw cursor
{
const sx = mouseX / CELL_SIZE;
const sy = mouseY / CELL_SIZE;
for (0..CELL_SIZE) |c_xi| {
for (0..CELL_SIZE) |c_yi| {
const x = sx * CELL_SIZE + c_xi;
const y = sy * CELL_SIZE + c_yi;
draw_pixel(x, y, .{ .r = 255, .g = 255, .b = 255, .a = 150 });
}
}
}

// Draw (live) cells
for (0..scene_width * scene_height) |c_i| {
if (SCENE_BUFFER[c_i] == 1) {
const sx = c_i % scene_width;
Expand Down Expand Up @@ -126,6 +207,10 @@ export fn draw() void {
SCENE_BUFFER = TEMP_SCENE_BUFFER;
step += 1;
}

if (dragging) {
place_cells();
}
}

fn draw_pixel(x: u32, y: u32, c: Color) void {
Expand Down
Binary file modified web/bin/zonzai.wasm
Binary file not shown.
47 changes: 32 additions & 15 deletions web/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,24 +7,41 @@
<script defer src="./main.js"></script>
<link rel="stylesheet" href="style.css" />
<link rel="shortcut icon" href="#" type="image/x-icon" />

<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Pixelify+Sans:[email protected]&display=swap"
rel="stylesheet"
/>
</head>

<body>
<p id="fps"></p>
<button id="pauseBtn">pause</button>
<canvas class="canvas"> </canvas>
<div class="gameContainer">
<div class="gameHeader">
<h1>Zonzai</h1>
</div>
<canvas class="canvas"> </canvas>
<div class="gameFooter">
<div class="gameControls">
<button id="pause-btn" class="btn">pause</button>
<button id="clear-btn" class="btn">clear</button>
</div>
<div id="pattern-palette"></div>
<p id="fps"></p>
</div>
</div>

<p style="color: white">
This is Conway's game of life, cells can be placed with the mouse. I'll
next add a menu of pre-made patterns that can be placed.
</p>
<p style="color: white">
There was a tree simulator here, but I removed it in the transition to a
cellular automata approach. Simulating plants is very difficult, yet I
just had the idea of a CA where cells store resources (like water) and
transfer them out to neighbors with osmosis. If a cell has an abundance of
nutrients it could grow a neighbor and if it has a derth it will die.
Still need to figure out how to model light and branching.
</p>
<div class="description">
<p>This is Conway's game of life. Cells can be placed with the mouse.</p>
<p>
There was a tree simulator here, but I removed it in the transition to a
cellular automata approach. Simulating plants is very difficult, yet I
just had the idea of a CA where cells store resources (like water) and
transfer them out to neighbors with osmosis. If a cell has an abundance
of nutrients it could grow a neighbor and if it has a derth it will die.
Still need to figure out how to model light and branching.
</p>
</div>
</body>
</html>
103 changes: 89 additions & 14 deletions web/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,30 @@ let canvas
let canvasContext
let canvasImageData

let dragging
let mousedown

const CELL_SIZE = 5

const EXAMPLE_PATTERNS = [
{
name: "Cell",
rle: "o!",
},
{
name: "Glider",
rle: "bob$2bo$3o!",
},
{
name: "70P23",
rle: "24bo$12bo9b3o$o11b3o6bo$3o5b2o5bo5b2o$3bo4b2o4b2o$2b2o$19bobo$19bobo$3b2o14b3o3b2o$3b2o3b3o14b2o$8bobo$8bobo$26b2o$14b2o4b2o4bo$7b2o5bo5b2o5b3o$8bo6b3o11bo$5b3o9bo$5bo!",
},
{
name: "p24 gliderless LWSS gun",
rle: "24b2o3b2o$2b2obo2bo15bobobo2bo$o2bob4o17bob4o$2o24bo2bo2b2o$4bob2obo9b2o3bobo3b2o2bo$2ob4ob2o9bo3b3o7b2o$o3bo12bobobob2o$b4o11bobo2bob2o$5b2o3b2o4bobob3o$3b4o3bobob2obobo$2bo3bo5bobo2bo12bo2bobo$bobo8bo3b2o10b2obob3o$bo2b2o3b3o8b2o7bo6bo$2b2obo4b2o8b2o6b2o5bo$5b3o4b3o5bo$6b2ob2o3b2o5bo7bo5b2o$b2ob2obob2o3b2o6b2o4bo6bo$obobo9b2o6b3o4b3obob2o$bo3b6o3bo14bobo2bo$7bo3bo9b2o$10bo10bo$10bo3bo6bo$6bo8bo$7b2o2bo3bo6bo2bo8bo2bo$5b2o9b2o8bo11bo$7bo5bo4bo3bo3bo7bo3bo$12bobo3bo4b4o8b4o$13bo4bo$7b2o3bo3b2o$6bobo3bo2bo$6bo4bo3bo$5b2o5bo2$8b2o8b2o$8bo2b6o2bo$9b2o6b2o$6b3o10b3o$6bo2bobo4bobo2bo$7b2o4b2o4b2o!",
},
]

const debug_print = (location, size) => {
var buffer = new Uint8Array(instance.exports.memory.buffer, location, size)
var decoder = new TextDecoder()
Expand All @@ -14,29 +38,55 @@ let frameTime = performance.now()
let lastFpsUpdateMS = new Date().getTime()
let prevFrameIntervals = []

const patternPaletteElement = document.getElementById("pattern-palette")
const fpsElement = document.getElementById("fps")
const pauseBtnElement = document.getElementById("pauseBtn")
const pauseBtnElement = document.getElementById("pause-btn")
const clearBtnElement = document.getElementById("clear-btn")

pauseBtnElement.addEventListener("click", () => {
instance.exports.pause()
const pauseResult = instance.exports.pause()
pauseBtnElement.innerText = pauseResult ? "unpause" : "pause"
})

clearBtnElement.addEventListener("click", () => {
instance.exports.clear()
})

const setCanvasDimensions = () => {
canvas.width = window.innerWidth
canvas.height = window.innerHeight
canvas.width = Math.floor((window.innerWidth * 0.96) / CELL_SIZE) * CELL_SIZE
canvas.height = Math.floor((window.innerHeight - 240) / CELL_SIZE) * CELL_SIZE
instance.exports.set_window_dimensions(canvas.width, canvas.height)
canvasImageData = canvasContext.createImageData(canvas.width, canvas.height)
}

window.addEventListener("resize", setCanvasDimensions)

const memory = new WebAssembly.Memory({
initial: 1000,
maximum: 65536,
EXAMPLE_PATTERNS.forEach((p) => {
const patternButtonElement = document.createElement("button")
patternButtonElement.classList.add("btn")
patternButtonElement.innerText = p.name
patternButtonElement.addEventListener("click", () => {
// TODO: There's definitely a better way to do this w/o allocating
// cause I just copy it into the same fixed buffer
const rleBuffer = Uint8Array.from(
Array.from(p.rle).map((letter) => letter.charCodeAt(0)),
)

var ptr = instance.exports.alloc(rleBuffer.length)
var mem = new Uint8Array(
instance.exports.memory.buffer,
ptr,
rleBuffer.length,
)
mem.set(new Uint8Array(rleBuffer))

instance.exports.select_pattern(ptr)
console.log(p.rle, p.rle.length)
})
patternPaletteElement.appendChild(patternButtonElement)
})

WebAssembly.instantiateStreaming(fetch("./bin/zonzai.wasm"), {
js: { mem: memory },
env: {
debug_print: debug_print,
},
Expand All @@ -47,18 +97,43 @@ WebAssembly.instantiateStreaming(fetch("./bin/zonzai.wasm"), {
canvasContext = canvas.getContext("2d")
setCanvasDimensions()

let mousedown = false
let mousePosition = { x: 0, y: 0 }

canvas.addEventListener("mouseup", (e) => {
mousedown = false
dragging = false
instance.exports.set_dragging(false)
})

canvas.addEventListener("mousedown", (e) => {
mousedown = true
instance.exports.mouse_click(e.x, canvas.height - e.y)
})
canvas.addEventListener("mouseup", (e) => {
mousedown = false

canvas.addEventListener("click", (e) => {
instance.exports.click()
})

canvas.addEventListener("mousemove", (e) => {
if (mousedown) {
instance.exports.mouse_click(e.x, canvas.height - e.y)
if (dragging) {
instance.exports.set_dragging(true)
}

let canvasBoundingRect = canvas.getBoundingClientRect()
const newMousePosition = {
x: e.x - canvasBoundingRect.left,
y: canvas.height + canvasBoundingRect.top - e.y,
}
if (
mousePosition.x !== newMousePosition.x ||
mousePosition.y !== newMousePosition.y
) {
if (mousedown) {
dragging = true
} else {
dragging = false
}
instance.exports.move_mouse(newMousePosition.x, newMousePosition.y)
mousePosition = newMousePosition
}
})

Expand Down
Loading

0 comments on commit 4d657e3

Please sign in to comment.