1// Package asciiart is a small on-chain ASCII art generator. Instead of a2// hand-authored font table, each pattern is produced by a short, pure,3// deterministic algorithm -- e.g. the Sierpinski triangle falls out of a4// single bitwise AND over row/column indices. Callers pick a pattern and a5// size; the realm renders it and keeps a gallery of everyone's generations.6package asciiart78import (9 "strconv"10 "strings"1112 "chain"13 "chain/runtime"1415 "gno.land/p/moul/kit/ui/v0"16 "gno.land/p/moul/kit/store/v0"17)1819// maxSize bounds every pattern's size parameter so a single Generate call20// can't blow the block gas limit on a pathologically large grid.21const maxSize = 322223// galleryPageSize caps how many pieces Render shows on the home page.24const galleryPageSize = 82526// piece carries no ID field: the id belongs to the store, which hands it back27// on lookup and iteration.28type piece struct {29 Pattern string30 Size int31 Author address32 CreatedAt int6433 Art string34}3536// gallery assigns the piece ids. v1 kept its own nextID plus a padID() that37// zero-padded to width 6, the narrowest ceiling in this repo: at the millionth38// piece the padding stops and the gallery starts rendering out of order. The39// store key is 8 fixed bytes with no width to outgrow.40var gallery = store.Named("piece")4142// 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.Builder49 for i := 0; i < 2*n-1; i++ {50 d := i51 if i >= n {52 d = 2*n - 2 - i53 }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}6263// RenderPyramid draws a solid triangle n rows tall, widening by two stars64// 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.Builder70 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}7980// RenderSierpinski draws a Sierpinski triangle in an n x n grid (n must be a81// power of two): cell (row, col) is filled exactly when row&col == 0, the82// 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.Builder88 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}102103// 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.Builder109 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}123124func 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}138139// Generate renders a pattern, adds it to the on-chain gallery, and returns140// 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)146147 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 })155156 chain.Emit("ArtGenerated", "id", id.String(), "pattern", pattern, "author", author.String())157158 return art159}160161func renderHome() string {162 var b strings.Builder163 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")176177 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 }182183 // The newest page, straight from the store: no counter, and only that184 // 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}193194func 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)205206 var b strings.Builder207 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}215216// 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}224Generate(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, pattern string, size int) string
Render(path string) string
RenderChecker(n int) string
RenderDiamond(n int) string
RenderPyramid(n int) string
RenderSierpinski(n int) string
Signatures reconstructed verbatim from vm/qfuncs — interface params keep their inline definitions.
vm/qrender output, sanitized (docs/render-security.md) and displayed in an empty-sandbox iframe — scripts, forms and popups cannot run. Links stay inert in-preview; right-click to open.