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

Community realm. Read the code first. Community realm, deployed by its author. Read the code before you interact or send coins.

asciiart.gno

6.42 Kb · 223 lines
  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/nym-moultest123/kit/ui/v0"
 16	"gno.land/p/nym-moultest123/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}