// by gera // public domain package hello_life import sdl "vendor:sdl3" ClearBoard :: proc(pixels : [][4]u8, color : [4]u8) { for i in 0..= 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() }