Extremely simple sand cellular automata written in Go

Initialize repo

Signed-off-by: Amy <amy+git@amogus.cloud>

Changed files
+210
+29
go.mod
··· 1 + module github.com/amycatgirl/sand-go 2 + 3 + go 1.25.3 4 + 5 + require ( 6 + github.com/charmbracelet/bubbletea v1.3.10 7 + golang.org/x/term v0.36.0 8 + ) 9 + 10 + require ( 11 + github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect 12 + github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect 13 + github.com/charmbracelet/lipgloss v1.1.0 // indirect 14 + github.com/charmbracelet/x/ansi v0.10.1 // indirect 15 + github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd // indirect 16 + github.com/charmbracelet/x/term v0.2.1 // indirect 17 + github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect 18 + github.com/lucasb-eyer/go-colorful v1.2.0 // indirect 19 + github.com/mattn/go-isatty v0.0.20 // indirect 20 + github.com/mattn/go-localereader v0.0.1 // indirect 21 + github.com/mattn/go-runewidth v0.0.16 // indirect 22 + github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect 23 + github.com/muesli/cancelreader v0.2.2 // indirect 24 + github.com/muesli/termenv v0.16.0 // indirect 25 + github.com/rivo/uniseg v0.4.7 // indirect 26 + github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect 27 + golang.org/x/sys v0.37.0 // indirect 28 + golang.org/x/text v0.30.0 // indirect 29 + )
+45
go.sum
··· 1 + github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= 2 + github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= 3 + github.com/charmbracelet/bubbletea v1.3.10 h1:otUDHWMMzQSB0Pkc87rm691KZ3SWa4KUlvF9nRvCICw= 4 + github.com/charmbracelet/bubbletea v1.3.10/go.mod h1:ORQfo0fk8U+po9VaNvnV95UPWA1BitP1E0N6xJPlHr4= 5 + github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc h1:4pZI35227imm7yK2bGPcfpFEmuY1gc2YSTShr4iJBfs= 6 + github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc/go.mod h1:X4/0JoqgTIPSFcRA/P6INZzIuyqdFY5rm8tb41s9okk= 7 + github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY= 8 + github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30= 9 + github.com/charmbracelet/x/ansi v0.10.1 h1:rL3Koar5XvX0pHGfovN03f5cxLbCF2YvLeyz7D2jVDQ= 10 + github.com/charmbracelet/x/ansi v0.10.1/go.mod h1:3RQDQ6lDnROptfpWuUVIUG64bD2g2BgntdxH0Ya5TeE= 11 + github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd h1:vy0GVL4jeHEwG5YOXDmi86oYw2yuYUGqz6a8sLwg0X8= 12 + github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs= 13 + github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ= 14 + github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg= 15 + github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4= 16 + github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM= 17 + github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= 18 + github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= 19 + github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= 20 + github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 21 + github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4= 22 + github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88= 23 + github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= 24 + github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= 25 + github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI= 26 + github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo= 27 + github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= 28 + github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= 29 + github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc= 30 + github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk= 31 + github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= 32 + github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= 33 + github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= 34 + github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= 35 + github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= 36 + golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561 h1:MDc5xs78ZrZr3HMQugiXOAkSZtfTpbJLDr/lwfgO53E= 37 + golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE= 38 + golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 39 + golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 40 + golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ= 41 + golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= 42 + golang.org/x/term v0.36.0 h1:zMPR+aF8gfksFprF/Nc/rd1wRS1EI6nDBGyWAvDzx2Q= 43 + golang.org/x/term v0.36.0/go.mod h1:Qu394IJq6V6dCBRgwqshf3mPF85AqzYEzofzRdZkWss= 44 + golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k= 45 + golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM=
+136
main.go
··· 1 + package main 2 + 3 + import ( 4 + "fmt" 5 + "math/rand/v2" 6 + "os" 7 + "time" 8 + 9 + tea "github.com/charmbracelet/bubbletea" 10 + "golang.org/x/term" 11 + ) 12 + 13 + type state struct { 14 + board [][]CellType 15 + rows int 16 + cols int 17 + } 18 + 19 + type tickMsg time.Time 20 + 21 + type CellType int 22 + 23 + const ( 24 + CellAir CellType = iota 25 + CellSolid 26 + ) 27 + 28 + func NewState(rows int, cols int) state { 29 + matrix := make([][]CellType, rows) 30 + 31 + for y := range matrix { 32 + matrix[y] = make([]CellType, cols) 33 + } 34 + 35 + for y := range matrix { 36 + for x := range matrix { 37 + if rand.IntN(2) == 0 { 38 + matrix[y][x] = CellAir 39 + } else { 40 + matrix[y][x] = CellSolid 41 + } 42 + } 43 + } 44 + 45 + return state{ 46 + board: matrix, 47 + rows: rows, 48 + cols: cols, 49 + } 50 + } 51 + 52 + func (s state) Init() tea.Cmd { 53 + return tea.Batch( 54 + tick(), 55 + tea.EnterAltScreen, 56 + ) 57 + } 58 + 59 + func tick() tea.Cmd { 60 + return tea.Tick(35*time.Millisecond, func(t time.Time) tea.Msg { 61 + return tickMsg(t) 62 + }) 63 + } 64 + 65 + func (s *state) updateBoard() { 66 + for y := range s.board { 67 + for x := range s.board[y] { 68 + if s.board[y][x] == CellSolid { 69 + if s.inBoard(x, y+1) && s.board[y+1][x] == CellAir { 70 + s.board[y][x] = CellAir 71 + s.board[y+1][x] = CellSolid 72 + } else if s.inBoard(x+1, y+1) && s.board[y+1][x+1] == CellAir { 73 + s.board[y+1][x+1] = 1 74 + s.board[y][x] = 0 75 + } 76 + } 77 + } 78 + } 79 + } 80 + 81 + func (s state) Update(msg tea.Msg) (tea.Model, tea.Cmd) { 82 + switch msg := msg.(type) { 83 + case tea.KeyMsg: 84 + switch msg.String() { 85 + case "ctrl+c", "q": 86 + return s, tea.Quit 87 + } 88 + case tickMsg: 89 + s.updateBoard() 90 + return s, tick() 91 + } 92 + 93 + return s, nil 94 + } 95 + 96 + func (s state) inBoard(x int, y int) bool { 97 + if (x >= 0 && x < s.cols) && (y >= 0 && y < s.rows) { 98 + return true 99 + } 100 + 101 + return false 102 + } 103 + 104 + func (s state) View() string { 105 + board := "" 106 + for y := range s.board { 107 + row := "" 108 + 109 + for x := range s.board[y] { 110 + if s.board[y][x] == CellAir { 111 + row += "\x1b[0;30m█" 112 + } else { 113 + row += "\x1b[0;32m█" 114 + } 115 + } 116 + 117 + row += "\n" 118 + board += row 119 + } 120 + 121 + return board 122 + } 123 + 124 + func main() { 125 + cols, rows, err := term.GetSize(int(os.Stdin.Fd())) 126 + if err != nil { 127 + panic(fmt.Errorf("Error whilst getting terminal size: %w", err)) 128 + } 129 + 130 + p := tea.NewProgram(NewState(rows, cols)) 131 + 132 + if _, err := p.Run(); err != nil { 133 + fmt.Printf("Error while running sim: %v", err) 134 + os.Exit(1) 135 + } 136 + }