Extremely simple sand cellular automata written in Go
1package main
2
3import (
4 "fmt"
5 "math/rand/v2"
6 "os"
7 "time"
8
9 tea "github.com/charmbracelet/bubbletea"
10 "golang.org/x/term"
11)
12
13type state struct {
14 board [][]CellType
15 rows int
16 cols int
17}
18
19type tickMsg time.Time
20
21type CellType int
22
23const (
24 CellAir CellType = iota
25 CellSolid
26)
27
28func 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
52func (s state) Init() tea.Cmd {
53 return tea.Batch(
54 tick(),
55 tea.EnterAltScreen,
56 )
57}
58
59func tick() tea.Cmd {
60 return tea.Tick(35*time.Millisecond, func(t time.Time) tea.Msg {
61 return tickMsg(t)
62 })
63}
64
65func (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
81func (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
96func (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
104func (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
124func 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}