Skip to content Search Apps Documentation Source Content File Folder Download Copy Actions State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

life.gno

4.90 Kb · 246 lines
  1// Conway's Game of Life on Gno — the entire board lives on-chain.
  2// Anyone can call Step() to tick the simulation forward, load classic
  3// patterns, or sculpt cells one by one.
  4package life
  5
  6import (
  7	"strconv"
  8	"strings"
  9)
 10
 11const (
 12	MaxWidth  = 50
 13	MaxHeight = 40
 14)
 15
 16var (
 17	width      = 20
 18	height     = 15
 19	cells      []bool
 20	generation int
 21	population int
 22)
 23
 24func init() {
 25	initBoard(width, height)
 26	LoadPreset("glider")
 27}
 28
 29func initBoard(w, h int) {
 30	if w < 1 {
 31		w = 1
 32	}
 33	if w > MaxWidth {
 34		w = MaxWidth
 35	}
 36	if h < 1 {
 37		h = 1
 38	}
 39	if h > MaxHeight {
 40		h = MaxHeight
 41	}
 42	width = w
 43	height = h
 44	cells = make([]bool, width*height)
 45	generation = 0
 46	population = 0
 47}
 48
 49func cellIdx(x, y int) int {
 50	return y*width + x
 51}
 52
 53func alive(x, y int) bool {
 54	if x < 0 || x >= width || y < 0 || y >= height {
 55		return false
 56	}
 57	return cells[cellIdx(x, y)]
 58}
 59
 60func countNeighbors(x, y int) int {
 61	n := 0
 62	for dy := -1; dy <= 1; dy++ {
 63		for dx := -1; dx <= 1; dx++ {
 64			if dx == 0 && dy == 0 {
 65				continue
 66			}
 67			if alive(x+dx, y+dy) {
 68				n++
 69			}
 70		}
 71	}
 72	return n
 73}
 74
 75// Step advances the simulation by exactly one generation.
 76func Step() {
 77	next := make([]bool, width*height)
 78	newPop := 0
 79	for y := 0; y < height; y++ {
 80		for x := 0; x < width; x++ {
 81			n := countNeighbors(x, y)
 82			var live bool
 83			if alive(x, y) {
 84				live = n == 2 || n == 3
 85			} else {
 86				live = n == 3
 87			}
 88			next[cellIdx(x, y)] = live
 89			if live {
 90				newPop++
 91			}
 92		}
 93	}
 94	cells = next
 95	population = newPop
 96	generation++
 97}
 98
 99// MultiStep advances the simulation by n generations (capped at 100).
100func MultiStep(n int) {
101	if n < 1 {
102		n = 1
103	}
104	if n > 100 {
105		n = 100
106	}
107	for i := 0; i < n; i++ {
108		Step()
109	}
110}
111
112// SetCell sets or clears a specific cell.
113func SetCell(x, y int, live bool) {
114	if x < 0 || x >= width || y < 0 || y >= height {
115		return
116	}
117	i := cellIdx(x, y)
118	was := cells[i]
119	cells[i] = live
120	if live && !was {
121		population++
122	} else if !live && was {
123		population--
124	}
125}
126
127// Reset clears the board and resizes it to w×h.
128func Reset(w, h int) {
129	initBoard(w, h)
130}
131
132// LoadPreset loads a classic Game of Life pattern onto the current board.
133// Names: glider, blinker, toad, beacon, block, pulsar.
134func LoadPreset(name string) {
135	for i := range cells {
136		cells[i] = false
137	}
138	generation = 0
139	population = 0
140
141	cx := width / 2
142	cy := height / 2
143
144	switch name {
145	case "glider":
146		//  .O.
147		//  ..O
148		//  OOO
149		SetCell(1, 0, true)
150		SetCell(2, 1, true)
151		SetCell(0, 2, true)
152		SetCell(1, 2, true)
153		SetCell(2, 2, true)
154
155	case "blinker":
156		// OOO  (period-2 oscillator)
157		SetCell(cx-1, cy, true)
158		SetCell(cx, cy, true)
159		SetCell(cx+1, cy, true)
160
161	case "toad":
162		// period-2 oscillator
163		SetCell(cx, cy-1, true)
164		SetCell(cx+1, cy-1, true)
165		SetCell(cx+2, cy-1, true)
166		SetCell(cx-1, cy, true)
167		SetCell(cx, cy, true)
168		SetCell(cx+1, cy, true)
169
170	case "beacon":
171		// period-2 oscillator
172		SetCell(cx-2, cy-2, true)
173		SetCell(cx-1, cy-2, true)
174		SetCell(cx-2, cy-1, true)
175		SetCell(cx, cy, true)
176		SetCell(cx+1, cy, true)
177		SetCell(cx, cy+1, true)
178		SetCell(cx+1, cy+1, true)
179
180	case "block":
181		// 2×2 still life
182		SetCell(cx, cy, true)
183		SetCell(cx+1, cy, true)
184		SetCell(cx, cy+1, true)
185		SetCell(cx+1, cy+1, true)
186
187	case "pulsar":
188		// period-3 oscillator (needs at least 17×17 board)
189		for _, d := range []int{-4, -3, -2, 2, 3, 4} {
190			SetCell(cx+d, cy-6, true)
191			SetCell(cx+d, cy-1, true)
192			SetCell(cx+d, cy+1, true)
193			SetCell(cx+d, cy+6, true)
194			SetCell(cx-6, cy+d, true)
195			SetCell(cx-1, cy+d, true)
196			SetCell(cx+1, cy+d, true)
197			SetCell(cx+6, cy+d, true)
198		}
199
200	default:
201		LoadPreset("glider")
202	}
203}
204
205// Render returns a Markdown snapshot of the current board.
206func Render(path string) string {
207	out := "# Conway's Game of Life\n\n"
208	out += "> A classic cellular automaton, ported to Gno — board state lives on-chain.\n\n"
209	out += "**Generation:** " + strconv.Itoa(generation)
210	out += " | **Population:** " + strconv.Itoa(population)
211	out += " | **Board:** " + strconv.Itoa(width) + "×" + strconv.Itoa(height) + "\n\n"
212
213	border := "+" + strings.Repeat("-", width) + "+\n"
214	out += "```\n"
215	out += border
216	for y := 0; y < height; y++ {
217		row := "|"
218		for x := 0; x < width; x++ {
219			if alive(x, y) {
220				row += "O"
221			} else {
222				row += "."
223			}
224		}
225		row += "|\n"
226		out += row
227	}
228	out += border
229	out += "```\n\n"
230
231	out += "## Transactions\n\n"
232	out += "| Call | Effect |\n"
233	out += "|---|---|\n"
234	out += "| `Step()` | Advance one generation |\n"
235	out += "| `MultiStep(n)` | Advance *n* generations (max 100) |\n"
236	out += "| `SetCell(x, y, alive)` | Flip a single cell |\n"
237	out += "| `Reset(w, h)` | Clear and resize (max " + strconv.Itoa(MaxWidth) + "×" + strconv.Itoa(MaxHeight) + ") |\n"
238	out += "| `LoadPreset(name)` | `glider` · `blinker` · `toad` · `beacon` · `block` · `pulsar` |\n\n"
239
240	out += "## Rules\n\n"
241	out += "1. A live cell with 2 or 3 live neighbours survives.\n"
242	out += "2. A dead cell with exactly 3 live neighbours becomes alive.\n"
243	out += "3. All other cells die or remain dead.\n"
244
245	return out
246}