commit c2142436b958606e6f0fe0da160ae10d2b8ea321
Author: sam <multisniperism@gmail.com>
Date:   Fri Mar 7 18:58:47 2025 +1300

    first commit

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..f0fa0f5
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+converter*
+viewer*
diff --git a/build.bat b/build.bat
new file mode 100644
index 0000000..dfc00ef
--- /dev/null
+++ b/build.bat
@@ -0,0 +1,2 @@
+odin build . -out:converter
+odin build . -out:viewer -define:MAKE_CONVERTER=false
diff --git a/build.sh b/build.sh
new file mode 100755
index 0000000..dfc00ef
--- /dev/null
+++ b/build.sh
@@ -0,0 +1,2 @@
+odin build . -out:converter
+odin build . -out:viewer -define:MAKE_CONVERTER=false
diff --git a/kiva.sand b/kiva.sand
new file mode 100644
index 0000000..990ce8a
Binary files /dev/null and b/kiva.sand differ
diff --git a/main.odin b/main.odin
new file mode 100644
index 0000000..bf7fd26
--- /dev/null
+++ b/main.odin
@@ -0,0 +1,163 @@
+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 {
+                if pixel.pos.x >= DIVIDED - 1 do continue
+                pixel.pos = right
+            }
+        }
+        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()
+            }
+        }
+    }
+}
diff --git a/mathew.sand b/mathew.sand
new file mode 100644
index 0000000..5a755f5
Binary files /dev/null and b/mathew.sand differ
diff --git a/odinfmt.json b/odinfmt.json
new file mode 100644
index 0000000..4137bac
--- /dev/null
+++ b/odinfmt.json
@@ -0,0 +1,5 @@
+{
+	"character_width": 110,
+	"tabs": false,
+	"tabs_width": 4
+}
diff --git a/out.sand b/out.sand
new file mode 100644
index 0000000..5026824
Binary files /dev/null and b/out.sand differ
diff --git a/rose.sand b/rose.sand
new file mode 100644
index 0000000..7a72907
Binary files /dev/null and b/rose.sand differ
diff --git a/sam.sand b/sam.sand
new file mode 100644
index 0000000..d38e103
Binary files /dev/null and b/sam.sand differ