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/nt/avl/v0"16)1718// maxSize bounds every pattern's size parameter so a single Generate call19// can't blow the block gas limit on a pathologically large grid.20const maxSize = 322122// galleryPageSize caps how many pieces Render shows on the home page.23const galleryPageSize = 82425type piece struct {26 ID string27 Pattern string28 Size int29 Author address30 CreatedAt int6431 Art string32}3334var (35 gallery avl.Tree // zero-padded ID -> *piece36 nextID int37)3839// RenderDiamond draws a filled diamond of half-width n (2n-1 rows total),40// each row's stars centered and padded with spaces.41func RenderDiamond(n int) string {42 if n < 1 {43 panic("diamond size must be >= 1")44 }45 var b strings.Builder46 for i := 0; i < 2*n-1; i++ {47 d := i48 if i >= n {49 d = 2*n - 2 - i50 }51 b.WriteString(strings.Repeat(" ", n-1-d))52 b.WriteString(strings.Repeat("*", 2*d+1))53 if i < 2*n-2 {54 b.WriteString("\n")55 }56 }57 return b.String()58}5960// RenderPyramid draws a solid triangle n rows tall, widening by two stars61// per row and centered with leading spaces.62func RenderPyramid(n int) string {63 if n < 1 {64 panic("pyramid size must be >= 1")65 }66 var b strings.Builder67 for i := 0; i < n; i++ {68 b.WriteString(strings.Repeat(" ", n-1-i))69 b.WriteString(strings.Repeat("*", 2*i+1))70 if i < n-1 {71 b.WriteString("\n")72 }73 }74 return b.String()75}7677// RenderSierpinski draws a Sierpinski triangle in an n x n grid (n must be a78// power of two): cell (row, col) is filled exactly when row&col == 0, the79// classic bitwise identity for Pascal's-triangle-mod-2.80func RenderSierpinski(n int) string {81 if n < 1 || n&(n-1) != 0 {82 panic("sierpinski size must be a power of two (1, 2, 4, 8, 16, 32)")83 }84 var b strings.Builder85 for row := 0; row < n; row++ {86 for col := 0; col < n; col++ {87 if row&col == 0 {88 b.WriteString("#")89 } else {90 b.WriteString(" ")91 }92 }93 if row < n-1 {94 b.WriteString("\n")95 }96 }97 return b.String()98}99100// RenderChecker draws an n x n checkerboard of # and . cells.101func RenderChecker(n int) string {102 if n < 1 {103 panic("checker size must be >= 1")104 }105 var b strings.Builder106 for row := 0; row < n; row++ {107 for col := 0; col < n; col++ {108 if (row+col)%2 == 0 {109 b.WriteString("#")110 } else {111 b.WriteString(".")112 }113 }114 if row < n-1 {115 b.WriteString("\n")116 }117 }118 return b.String()119}120121func renderPattern(pattern string, size int) string {122 switch pattern {123 case "diamond":124 return RenderDiamond(size)125 case "pyramid":126 return RenderPyramid(size)127 case "sierpinski":128 return RenderSierpinski(size)129 case "checker":130 return RenderChecker(size)131 default:132 panic(`unknown pattern: use "diamond", "pyramid", "sierpinski", or "checker"`)133 }134}135136// padID zero-pads an ID so avl.Tree's key-ordered iteration matches numeric137// order.138func padID(n int) string {139 s := strconv.Itoa(n)140 for len(s) < 6 {141 s = "0" + s142 }143 return s144}145146// Generate renders a pattern, adds it to the on-chain gallery, and returns147// the rendered art.148func Generate(cur realm, pattern string, size int) string {149 if size < 1 || size > maxSize {150 panic("size must be between 1 and " + strconv.Itoa(maxSize))151 }152 art := renderPattern(pattern, size)153154 nextID++155 id := strconv.Itoa(nextID)156 author := cur.Previous().Address()157 p := &piece{158 ID: id,159 Pattern: pattern,160 Size: size,161 Author: author,162 CreatedAt: runtime.ChainHeight(),163 Art: art,164 }165 gallery.Set(padID(nextID), p)166167 chain.Emit("ArtGenerated", "id", id, "pattern", pattern, "author", author.String())168169 return art170}171172// escapeInline neutralizes markdown-active characters in untrusted text173// before it's embedded inline in Render output.174func escapeInline(s string) string {175 r := strings.NewReplacer(176 "\\", "\\\\",177 "`", "\\`",178 "*", "\\*",179 "_", "\\_",180 "[", "\\[",181 "]", "\\]",182 "|", "\\|",183 )184 return r.Replace(s)185}186187func renderHome() string {188 var b strings.Builder189 b.WriteString("# ASCII Art Generator\n\n")190 b.WriteString("Four deterministic patterns, no font table required -- each shape " +191 "falls out of a short formula over row/column indices. Pick a pattern and a " +192 "size (1-" + strconv.Itoa(maxSize) + ", sierpinski wants a power of two) and " +193 "`Generate` adds it to the gallery below.\n\n")194 b.WriteString("## Patterns\n\n")195 b.WriteString("- `diamond` -- filled diamond, half-width `size`\n")196 b.WriteString("- `pyramid` -- solid triangle, `size` rows tall\n")197 b.WriteString("- `sierpinski` -- Sierpinski gasket in an n×n grid " +198 "(`row & col == 0`), `size` a power of two\n")199 b.WriteString("- `checker` -- n×n checkerboard\n\n")200 b.WriteString("Call `Generate(pattern, size)`. View a piece at this realm's path " +201 "plus its ID (e.g. `.../asciiart:3`).\n\n")202203 b.WriteString("## Gallery (total: " + strconv.Itoa(nextID) + ")\n\n")204 if nextID == 0 {205 b.WriteString("_nothing generated yet -- be the first_\n")206 return b.String()207 }208209 shown := 0210 gallery.ReverseIterate("", "", func(key string, value interface{}) bool {211 if shown >= galleryPageSize {212 return true213 }214 p := value.(*piece)215 b.WriteString("### #" + p.ID + " -- " + p.Pattern + " (size " + strconv.Itoa(p.Size) + ")\n\n")216 b.WriteString("by `" + p.Author.String() + "` at block " + strconv.Itoa(int(p.CreatedAt)) + "\n\n")217 b.WriteString("```\n" + p.Art + "\n```\n\n")218 shown++219 return false220 })221 return b.String()222}223224func renderPiece(idStr string) string {225 safe := escapeInline(idStr)226 n, err := strconv.Atoi(idStr)227 if err != nil || n < 1 {228 return "# Piece " + safe + "\n\nNo such piece.\n"229 }230 v := gallery.Get(padID(n))231 if v == nil {232 return "# Piece #" + safe + "\n\nNo such piece.\n"233 }234 p := v.(*piece)235236 var b strings.Builder237 b.WriteString("# Piece #" + p.ID + "\n\n")238 b.WriteString("- Pattern: " + p.Pattern + "\n")239 b.WriteString("- Size: " + strconv.Itoa(p.Size) + "\n")240 b.WriteString("- Author: `" + p.Author.String() + "`\n")241 b.WriteString("- Block: " + strconv.Itoa(int(p.CreatedAt)) + "\n\n")242 b.WriteString("```\n" + p.Art + "\n```\n")243 return b.String()244}245246// Render shows the pattern guide + gallery at "", or one piece by numeric ID.247func Render(path string) string {248 path = strings.TrimPrefix(strings.TrimSpace(path), "/")249 if path == "" {250 return renderHome()251 }252 return renderPiece(path)253}254Generate(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.