← Back to Realms

Realm

gno.land/r/moul/x/daily/asciiart/v1

Overview

Realm Path
gno.land/r/moul/x/daily/asciiart/v1
Exported Functions
6
State Entries
4
Source Files
3
Total Package Entries
13

Exported Functions

6 exported functions

State

4 state entries

Source Code

FILES
asciiart.gno
go
1// Package asciiart is a small on-chain ASCII art generator. Instead of a
2// hand-authored font table, each pattern is produced by a short, pure,
3// deterministic algorithm -- e.g. the Sierpinski triangle falls out of a
4// single bitwise AND over row/column indices. Callers pick a pattern and a
5// size; the realm renders it and keeps a gallery of everyone's generations.
6package asciiart
7
8import (
9	"strconv"
10	"strings"
11
12	"chain"
13	"chain/runtime"
14
15	"gno.land/p/moul/kit/ui/v0"
16	"gno.land/p/moul/kit/store/v0"
17)
18
19// maxSize bounds every pattern's size parameter so a single Generate call
20// can't blow the block gas limit on a pathologically large grid.
21const maxSize = 32
22
23// galleryPageSize caps how many pieces Render shows on the home page.
24const galleryPageSize = 8
25
26// piece carries no ID field: the id belongs to the store, which hands it back
27// on lookup and iteration.
28type piece struct {
29	Pattern   string
30	Size      int
31	Author    address
32	CreatedAt int64
33	Art       string
34}
35
36// gallery assigns the piece ids. v1 kept its own nextID plus a padID() that
37// zero-padded to width 6, the narrowest ceiling in this repo: at the millionth
38// piece the padding stops and the gallery starts rendering out of order. The
39// store key is 8 fixed bytes with no width to outgrow.
40var gallery = store.Named("piece")
41
42// RenderDiamond draws a filled diamond of half-width n (2n-1 rows total),
43// each row's stars centered and padded with spaces.
44func RenderDiamond(n int) string {
45	if n < 1 {
46		panic("diamond size must be >= 1")
47	}
48	var b strings.Builder
49	for i := 0; i < 2*n-1; i++ {
50		d := i
51		if i >= n {
52			d = 2*n - 2 - i
53		}
54		b.WriteString(strings.Repeat(" ", n-1-d))
55		b.WriteString(strings.Repeat("*", 2*d+1))
56		if i < 2*n-2 {
57			b.WriteString("\n")
58		}
59	}
60	return b.String()
61}
62
63// RenderPyramid draws a solid triangle n rows tall, widening by two stars
64// per row and centered with leading spaces.
65func RenderPyramid(n int) string {
66	if n < 1 {
67		panic("pyramid size must be >= 1")
68	}
69	var b strings.Builder
70	for i := 0; i < n; i++ {
71		b.WriteString(strings.Repeat(" ", n-1-i))
72		b.WriteString(strings.Repeat("*", 2*i+1))
73		if i < n-1 {
74			b.WriteString("\n")
75		}
76	}
77	return b.String()
78}
79
80// RenderSierpinski draws a Sierpinski triangle in an n x n grid (n must be a
81// power of two): cell (row, col) is filled exactly when row&col == 0, the
82// classic bitwise identity for Pascal's-triangle-mod-2.
83func RenderSierpinski(n int) string {
84	if n < 1 || n&(n-1) != 0 {
85		panic("sierpinski size must be a power of two (1, 2, 4, 8, 16, 32)")
86	}
87	var b strings.Builder
88	for row := 0; row < n; row++ {
89		for col := 0; col < n; col++ {
90			if row&col == 0 {
91				b.WriteString("#")
92			} else {
93				b.WriteString(" ")
94			}
95		}
96		if row < n-1 {
97			b.WriteString("\n")
98		}
99	}
100	return b.String()
101}
102
103// RenderChecker draws an n x n checkerboard of # and . cells.
104func RenderChecker(n int) string {
105	if n < 1 {
106		panic("checker size must be >= 1")
107	}
108	var b strings.Builder
109	for row := 0; row < n; row++ {
110		for col := 0; col < n; col++ {
111			if (row+col)%2 == 0 {
112				b.WriteString("#")
113			} else {
114				b.WriteString(".")
115			}
116		}
117		if row < n-1 {
118			b.WriteString("\n")
119		}
120	}
121	return b.String()
122}
123
124func renderPattern(pattern string, size int) string {
125	switch pattern {
126	case "diamond":
127		return RenderDiamond(size)
128	case "pyramid":
129		return RenderPyramid(size)
130	case "sierpinski":
131		return RenderSierpinski(size)
132	case "checker":
133		return RenderChecker(size)
134	default:
135		panic(`unknown pattern: use "diamond", "pyramid", "sierpinski", or "checker"`)
136	}
137}
138
139// Generate renders a pattern, adds it to the on-chain gallery, and returns
140// the rendered art.
141func Generate(cur realm, pattern string, size int) string {
142	if size < 1 || size > maxSize {
143		panic("size must be between 1 and " + strconv.Itoa(maxSize))
144	}
145	art := renderPattern(pattern, size)
146
147	author := cur.Previous().Address()
148	id := gallery.Add(&piece{
149		Pattern:   pattern,
150		Size:      size,
151		Author:    author,
152		CreatedAt: runtime.ChainHeight(),
153		Art:       art,
154	})
155
156	chain.Emit("ArtGenerated", "id", id.String(), "pattern", pattern, "author", author.String())
157
158	return art
159}
160
161func renderHome() string {
162	var b strings.Builder
163	b.WriteString("# ASCII Art Generator\n\n")
164	b.WriteString("Four deterministic patterns, no font table required -- each shape " +
165		"falls out of a short formula over row/column indices. Pick a pattern and a " +
166		"size (1-" + strconv.Itoa(maxSize) + ", sierpinski wants a power of two) and " +
167		"`Generate` adds it to the gallery below.\n\n")
168	b.WriteString("## Patterns\n\n")
169	b.WriteString("- `diamond` -- filled diamond, half-width `size`\n")
170	b.WriteString("- `pyramid` -- solid triangle, `size` rows tall\n")
171	b.WriteString("- `sierpinski` -- Sierpinski gasket in an n×n grid " +
172		"(`row & col == 0`), `size` a power of two\n")
173	b.WriteString("- `checker` -- n×n checkerboard\n\n")
174	b.WriteString("Call `Generate(pattern, size)`. View a piece at this realm's path " +
175		"plus its ID (e.g. `.../asciiart:3`).\n\n")
176
177	b.WriteString("## Gallery (total: " + gallery.LastID().String() + ")\n\n")
178	if gallery.Len() == 0 {
179		b.WriteString("_nothing generated yet -- be the first_\n")
180		return b.String()
181	}
182
183	// The newest page, straight from the store: no counter, and only that
184	// window is walked.
185	for _, e := range gallery.PageReverse(1, galleryPageSize) {
186		p := e.Value.(*piece)
187		b.WriteString("### #" + e.ID.String() + " -- " + p.Pattern + " (size " + strconv.Itoa(p.Size) + ")\n\n")
188		b.WriteString("by `" + p.Author.String() + "` at block " + strconv.Itoa(int(p.CreatedAt)) + "\n\n")
189		b.WriteString("```\n" + p.Art + "\n```\n\n")
190	}
191	return b.String()
192}
193
194func renderPiece(idStr string) string {
195	safe := ui.Inline(idStr)
196	id, ok := store.ParseID(idStr)
197	if !ok {
198		return "# Piece " + safe + "\n\nNo such piece.\n"
199	}
200	v, ok := gallery.Get(id)
201	if !ok {
202		return "# Piece #" + safe + "\n\nNo such piece.\n"
203	}
204	p := v.(*piece)
205
206	var b strings.Builder
207	b.WriteString("# Piece #" + id.String() + "\n\n")
208	b.WriteString("- Pattern: " + p.Pattern + "\n")
209	b.WriteString("- Size: " + strconv.Itoa(p.Size) + "\n")
210	b.WriteString("- Author: `" + p.Author.String() + "`\n")
211	b.WriteString("- Block: " + strconv.Itoa(int(p.CreatedAt)) + "\n\n")
212	b.WriteString("```\n" + p.Art + "\n```\n")
213	return b.String()
214}
215
216// Render shows the pattern guide + gallery at "", or one piece by numeric ID.
217func Render(path string) string {
218	path = strings.TrimPrefix(strings.TrimSpace(path), "/")
219	if path == "" {
220		return renderHome()
221	}
222	return renderPiece(path)
223}
224

Raw Package Data

Raw JSON data