rfrxsm (go, doesn't expire)
// by gera
// public domain
package hello_life

import sdl "vendor:sdl3"

ClearBoard :: proc(pixels : [][4]u8, color : [4]u8) {
	for i in 0..<len(pixels) {
		pixels[i] = color
	}
}

DrawCells :: proc(cells : ^map[[2]i32]bool, pixels : [][4]u8, color : [4]u8, w, h : i32) {
	for cell in cells {
		if cell.x >= 0 && cell.y >= 0 && cell.x < w && cell.y < h {
			pixels[cell.x + cell.y * w] = color
		}
	}
}

CountNeighbors :: proc(cells : ^map[[2]i32]bool, pos : [2]i32) -> i32
{
	count : i32
	count += i32(cells[{pos.x + 1, pos.y}])
	count += i32(cells[{pos.x + 1, pos.y + 1}])
	count += i32(cells[{pos.x, pos.y + 1}])
	count += i32(cells[{pos.x - 1, pos.y + 1}])
	count += i32(cells[{pos.x - 1, pos.y}])
	count += i32(cells[{pos.x - 1, pos.y - 1}])
	count += i32(cells[{pos.x, pos.y - 1}])
	count += i32(cells[{pos.x + 1, pos.y - 1}])
	return count
}

ApplyRulesOnCell :: proc(cellscur, cellsnext : ^map[[2]i32]bool, pos : [2]i32)
{
	neighbors := CountNeighbors(cellscur, pos)
	alive := cellscur[pos]
	if (alive && (neighbors < 2 || neighbors > 3)) do delete_key(cellsnext, pos)
	else if (!alive && neighbors == 3) do cellsnext[pos] = true
	else if (alive && (neighbors == 2 || neighbors == 3)) do cellsnext[pos] = true
}

ComputeLife :: proc(cellscur, cellsnext : ^map[[2]i32]bool)
{
	clear(cellsnext)
	for cell in cellscur {
		x := cell[0]
		y := cell[1]
		ApplyRulesOnCell(cellscur, cellsnext, {x, y})
		ApplyRulesOnCell(cellscur, cellsnext, {x + 1, y})
		ApplyRulesOnCell(cellscur, cellsnext, {x + 1, y + 1})
		ApplyRulesOnCell(cellscur, cellsnext, {x, y + 1})
		ApplyRulesOnCell(cellscur, cellsnext, {x - 1, y + 1})
		ApplyRulesOnCell(cellscur, cellsnext, {x - 1, y})
		ApplyRulesOnCell(cellscur, cellsnext, {x - 1, y - 1})
		ApplyRulesOnCell(cellscur, cellsnext, {x, y - 1})
		ApplyRulesOnCell(cellscur, cellsnext, {x + 1, y - 1})
	}
}

main :: proc() {
	result := sdl.Init({.VIDEO})
	window : ^sdl.Window
	renderer : ^sdl.Renderer
	result = sdl.CreateWindowAndRenderer("Game of Life", width = 640, height = 480,
										 window_flags = {.RESIZABLE,},
										 window = &window, renderer = &renderer)
	texture := sdl.CreateTexture(renderer, sdl.PixelFormat.ABGR8888, sdl.TextureAccess.STREAMING, 320, 240)
	sdl.SetRenderLogicalPresentation(renderer, texture.w, texture.h, sdl.RendererLogicalPresentation.LETTERBOX)
	sdl.SetTextureScaleMode(texture, sdl.ScaleMode.PIXELART)

	cells1 : map[[2]i32]bool
	cells2 : map[[2]i32]bool
	cellsfront := &cells1
	cellsback := &cells2

/* initial state:
	****
	 *
	  *
	  *
*/
	cellsfront[{0+texture.w/2, 0+texture.h/2}] = true
	cellsfront[{1+texture.w/2, 0+texture.h/2}] = true
	cellsfront[{2+texture.w/2, 0+texture.h/2}] = true
	cellsfront[{3+texture.w/2, 0+texture.h/2}] = true
	cellsfront[{1+texture.w/2, 1+texture.h/2}] = true
	cellsfront[{2+texture.w/2, 2+texture.h/2}] = true
	cellsfront[{2+texture.w/2, 3+texture.h/2}] = true

	running : bool = true
	time_start : u64
	loop: for running {
		time_start = sdl.GetTicksNS()
		event : sdl.Event
		result = sdl.PollEvent(&event)
		#partial switch event.type {
			case .QUIT:
			running = false
			break loop
		}

		pixels_rawptr : rawptr
		pitch : i32
		sdl.LockTexture(texture, nil, &pixels_rawptr, &pitch)
		pixels := ([^][4]u8)(pixels_rawptr)[:texture.w * texture.h]
		ClearBoard(pixels, {200, 200, 200, 255})
		DrawCells(cellsfront, pixels, {0, 0, 255, 255}, texture.w, texture.h)
		sdl.UnlockTexture(texture)
		sdl.RenderClear(renderer)
		sdl.RenderTexture(renderer, texture, nil, nil)
		sdl.RenderPresent(renderer)

		ComputeLife(cellsfront, cellsback)
		cellsfront, cellsback = cellsback, cellsfront

		time_end := sdl.GetTicksNS()
		// do more generations but maintain the target fps
		for time_end - time_start < 1e9/30 {
			ComputeLife(cellsfront, cellsback)
			cellsfront, cellsback = cellsback, cellsfront
			// also handle events so the window doesn't lag
			result = sdl.PollEvent(&event)
			#partial switch event.type {
				case .QUIT:
				running = false
				break loop
			}
			time_end = sdl.GetTicksNS()
		}
	}

	sdl.DestroyRenderer(renderer)
	sdl.DestroyWindow(window)
	sdl.Quit()
}