import rl "vendor:raylib" import agent "vendor:agent" import edn "vendor:edn" const screen-width = 900 const screen-height = 600 const cell-size = 5 const rows = screen-height / cell-size const cols = screen-width / cell-size const brush-size = 10 fn dyn->f64(v: f64) -> f64 = v fn dyn->u32(v: i64) -> u32 = u32(v) let gravity = 0.05 let colors = let v = vec-new(dyn) push(v, 0xFFF00FFF) push(v, 0x3B6E8CFF) push(v, 0xA83232FF) push(v, 0xCC6B1FFF) v once grid: [rows [cols u32]] once velocity: [rows [cols f32]] once current-color = 0 fn clear-grid() -> () grid = zeroed() velocity = zeroed() fn next-color() -> () current-color = (current-color + 1) % length(colors) fn paint-at(row: i32, col: i32) -> () let half = brush-size / 2 for x in range(brush-size) for y in range(brush-size) let r = y + (row - half) c = x + (col - half) if r >= 0 and r < rows - 1 and c >= 0 and c < cols - 1 and 0 == grid[r, c] and f32(rand()) < 0.5 grid[r, c] = dyn->u32(colors[current-color]) velocity[r, c] = 1.0 fn settle(row: i32, col: i32) -> () let vel = f32(dyn->f64(gravity)) + velocity[row, col] let y = min(rows - 1, row + i32(vel)) while y > row if 0 == grid[y, col] grid[y, col] = grid[row, col] grid[row, col] = 0 velocity[y, col] = vel velocity[row, col] = 0.0 return let is-left = col > 0 and 0 == grid[y, col - 1] is-right = col < cols - 1 and 0 == grid[y, col + 1] if is-left or is-right let side = if not is-left 1 elif not is-right -1 elif f32(rand()) < 0.5 1 else -1 grid[y, col + side] = grid[row, col] grid[row, col] = 0 velocity[y, col + side] = vel velocity[row, col] = 0.0 return y -= 1 velocity[row, col] = 0.0 fn step() -> () let row = rows - 2 while row >= 0 for col in range(cols) unless(0 == grid[row, col]): settle(row, col) row -= 1 const fnv-offset: u64 = 0xcbf29ce484222325 const fnv-prime: u64 = 1099511628211 fn hash-grid() -> u64 let h = fnv-offset for row in range(rows) for col in range(cols) let c = grid[row, col] for b in range(4) h = h ^^ u64(c >> u32(b * 8) && 255) h *= fnv-prime h fn game-update() -> () if rl/is-key-pressed(:key-r) clear-grid() if rl/is-mouse-button-down(:mouse-left) let m = rl/get-mouse-position() paint-at(i32(m.y) / cell-size, i32(m.x) / cell-size) if rl/is-mouse-button-released(:mouse-left) next-color() step() fn game-draw() -> () rl/clear-background(rl/black) for row in range(rows) for col in range(cols) let c = grid[row, col] unless(0 == c): rl/draw-rectangle(i32(col * cell-size), i32(row * cell-size), cell-size, cell-size, rl/get-color(c)) rl/draw-fps(20, 20) once frame: Allocator = arena-new(262144) let game-data = handler-case edn/read-file("game-data.edn") on FileError(c) nil fn main() -> () rl/set-trace-log-level(:log-warning) rl/init-window(screen-width, screen-height, "SAND") defer rl/close-window() rl/set-target-fps(120) agent/start("/tmp/flan-sand.sock") until rl/window-should-close() restart-case agent/poll() game-update() restart continue() () rl/with-drawing: game-draw()