// 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()
}