package main import "core:encoding/cbor" import "core:fmt" import "core:image/png" import "core:math/rand" import "core:os" import "core:strings" import "core:time" import rl "vendor:raylib" MAKE_CONVERTER :: #config(MAKE_CONVERTER, true) Pixel :: struct { id: int, pos: rl.Vector2, already_moved: bool, color: rl.Color, } State :: struct { seed: u64, colors: map[int]rl.Color, } state: State SIZE :: 800 SCALE :: 20 DIVIDED :: SIZE / SCALE pixels: [dynamic]Pixel get_pixel :: proc(pos: rl.Vector2) -> Maybe(^Pixel) { for &pixel in pixels { if pixel.pos == pos { return &pixel } } return nil } is_finished :: proc() -> bool { return len(pixels) >= DIVIDED * DIVIDED } main :: proc() { rl.SetTraceLogLevel(.ERROR) when MAKE_CONVERTER { if len(os.args) < 3 { fmt.eprintln("Please provide input and output filenames!") return } state.seed = u64(time.time_to_unix(time.now())) rand.reset(state.seed) rl.SetConfigFlags({.WINDOW_HIDDEN}) fmt.println("Beginning conversion...") } else { if len(os.args) < 2 { fmt.eprintln("Please provide a sand image!") return } bytes, err := os.read_entire_file_or_err(os.args[1]) if err != nil { fmt.eprintln("Failed to read sand image:", err) return } unmarshal_err := cbor.unmarshal(string(bytes), &state) if unmarshal_err != nil { fmt.eprintln("Failed to unmarshal sand image:", unmarshal_err) } rand.reset(state.seed) } rl.InitWindow(SIZE, SIZE, strings.clone_to_cstring(os.args[1])) when MAKE_CONVERTER { img := rl.LoadImage(strings.clone_to_cstring(os.args[1])) rl.ImageResize(&img, DIVIDED, DIVIDED) } else { rl.SetTargetFPS(120) } for !rl.WindowShouldClose() { for &pixel in pixels { pixel.already_moved = false } if !is_finished() { pos := rl.Vector2{f32(rand.int_max(DIVIDED)), 0} if _, found := get_pixel(pos).?; !found { color: rl.Color id := len(pixels) when !MAKE_CONVERTER { color = state.colors[id] } append(&pixels, Pixel{id = id, pos = pos, color = color}) } } when !MAKE_CONVERTER { rl.BeginDrawing() rl.ClearBackground(rl.BLACK) } for &pixel in pixels { when !MAKE_CONVERTER { rl.DrawRectangleV(pixel.pos * SCALE, {SCALE, SCALE}, pixel.color) } if pixel.already_moved do continue if pixel.pos.y >= DIVIDED - 1 do continue down := pixel.pos + {0, 1} left := pixel.pos + {-1, 1} right := pixel.pos + {1, 1} if _, found := get_pixel(down).?; !found { pixel.pos = down pixel.already_moved = true } else if _, found := get_pixel(left).?; !found { if pixel.pos.x > 0 { pixel.pos = left pixel.already_moved = true } } if _, found := get_pixel(right).?; !found && !pixel.already_moved { pixel.already_moved = true if pixel.pos.x >= DIVIDED - 1 do continue pixel.pos = right } pixel.already_moved = true } when !MAKE_CONVERTER { rl.EndDrawing() } when MAKE_CONVERTER { if is_finished() { for x in 0 ..< DIVIDED { for y in 0 ..< DIVIDED { pixel := get_pixel(rl.Vector2{f32(x), f32(y)}).? color := rl.GetImageColor(img, i32(x), i32(y)) pixel.color = color state.colors[pixel.id] = color } } bytes, err := cbor.marshal(state) if err != nil { fmt.eprintln("Failed to marshal sand image:", err) return } write_err := os.write_entire_file_or_err(os.args[2], bytes) if write_err != nil { fmt.eprintln("Failed to write sand image:", write_err) return } fmt.println("Conversion complete") rl.CloseWindow() } } } }