PathrockNetwork Gno Explorer
HomeBlocksTransactionsTokensRealmsPackagesValidatorsAnalytics

PathrockNetwork Gno Explorer — an independent explorer for Gno.land Mainnet (gnoland-1), operated by PathrockNetwork. Not an official Gno.land service.

gnowebarchive RPC

gno.land/r/moul/x/daily/pixelcanvas/v0

Realm
Open in gnoweb ↗

Overview

Kind
Realm (renderable)
Name
v0
Namespace
moul / x / daily / pixelcanvas
Files
3 (README)(gnomod.toml)
Exported functions
6
Module
gno.land/r/moul/x/daily/pixelcanvas/v0
gno
0.9

Files (3)

  • README.mdmarkdown
  • gnomod.tomltoml
  • pixelcanvas.gnogno
pixelcanvas.gnogno
1// Package pixelcanvas is a shared 16x16 pixel canvas: anyone can paint one2// cell at a time from a fixed 9-colour palette, and the canvas renders in3// gnoweb as a grid of coloured blocks.4//5// The canvas is a flat [Size*Size]int of palette indices held in realm state —6// a fixed-size array rather than a tree, because every cell exists from the7// start and the grid is walked in full on every render. Painting is8// last-write-wins; the realm keeps who painted each cell and a per-address9// tally, so the board doubles as a contribution leaderboard.10//11// A stateful app with nothing reusable to extract, so it ships as a lone realm12// rather than a p/ library + demo pair.13package pixelcanvas1415import (16	"sort"17	"strconv"18	"strings"1920	"chain"21	"chain/runtime"2223	"gno.land/p/nt/avl/v0"24)2526// Size is the canvas edge length; the canvas is Size x Size cells.27const Size = 162829// palette maps a colour index to the block rendered for it. Index 0 is the30// blank canvas. Kept parallel to paletteNames.31var palette = []string{"⬜", "🟥", "🟧", "🟨", "🟩", "🟦", "🟪", "🟫", "⬛"}3233// paletteNames are the names Paint accepts, in palette order.34var paletteNames = []string{"white", "red", "orange", "yellow", "green", "blue", "purple", "brown", "black"}3536// Package-level persistent state.37var (38	cells    [Size * Size]int // palette index per cell, row-major39	painters [Size * Size]string40	tally    = avl.NewTree() // address string -> *int paint count41	strokes  int             // total paints ever42	lastAt   int64           // height of the most recent paint43)4445// Paint colours the cell at (x, y) and returns the total number of strokes.46//47// Coordinates are 0-based with (0,0) at the top-left. Colour is a palette name48// ("red", "blue", …) — see Palette. Painting over an existing cell is allowed:49// the canvas is last-write-wins, which is the whole point of a shared board.50func Paint(cur realm, x, y int, colour string) int {51	if !cur.IsCurrent() {52		panic("spoofed realm")53	}54	prev := cur.Previous()55	if !prev.IsUserCall() {56		panic("only an EOA via MsgCall can paint")57	}58	if x < 0 || x >= Size || y < 0 || y >= Size {59		panic("out of bounds: x and y must be in [0," + strconv.Itoa(Size-1) + "]")60	}61	idx := colourIndex(colour)62	if idx < 0 {63		panic("unknown colour " + strconv.Quote(colour) + "; see Palette()")64	}6566	addr := prev.Address()67	i := y*Size + x68	cells[i] = idx69	painters[i] = addr.String()70	strokes++71	lastAt = runtime.ChainHeight()72	bump(addr.String())7374	chain.Emit("Paint",75		"addr", addr.String(),76		"x", strconv.Itoa(x),77		"y", strconv.Itoa(y),78		"colour", paletteNames[idx],79	)80	return strokes81}8283// colourIndex resolves a palette name to its index, or -1.84func colourIndex(name string) int {85	n := strings.ToLower(strings.TrimSpace(name))86	for i, p := range paletteNames {87		if p == n {88			return i89		}90	}91	return -192}9394// bump increments an address's paint tally.95func bump(addr string) {96	// avl/v0's Get returns a single `any`; a miss is a nil interface.97	if v := tally.Get(addr); v != nil {98		c := v.(*int)99		*c++100		return101	}102	one := 1103	tally.Set(addr, &one)104}105106// At returns the palette name of the cell at (x, y), or "" when out of bounds.107func At(x, y int) string {108	if x < 0 || x >= Size || y < 0 || y >= Size {109		return ""110	}111	return paletteNames[cells[y*Size+x]]112}113114// PainterAt returns the address that last painted (x, y), or "" if untouched.115func PainterAt(x, y int) string {116	if x < 0 || x >= Size || y < 0 || y >= Size {117		return ""118	}119	return painters[y*Size+x]120}121122// Strokes returns how many paints the canvas has taken.123func Strokes() int { return strokes }124125// Palette returns the accepted colour names, comma-separated.126func Palette() string { return strings.Join(paletteNames, ", ") }127128// scorer is one row of the contribution leaderboard.129type scorer struct {130	addr  string131	count int132}133134// byCount ranks painters by strokes descending, ties broken on address so the135// board is deterministic.136type byCount []scorer137138func (s byCount) Len() int      { return len(s) }139func (s byCount) Swap(i, j int) { s[i], s[j] = s[j], s[i] }140func (s byCount) Less(i, j int) bool {141	if s[i].count != s[j].count {142		return s[i].count > s[j].count143	}144	return s[i].addr < s[j].addr145}146147// Render renders the canvas for gnoweb.148//149//	Render("")        / Render("/") -> the canvas, palette and leaderboard150//	Render("/<x>,<y>")              -> who painted that cell, and what colour151func Render(path string) string {152	var b strings.Builder153	b.WriteString("# Pixel Canvas\n\n")154	b.WriteString("A shared ")155	b.WriteString(strconv.Itoa(Size))156	b.WriteString("x")157	b.WriteString(strconv.Itoa(Size))158	b.WriteString(" canvas. Anyone can `Paint(x, y, colour)` — last write wins.\n\n")159160	if q := parseArg(path); q != "" {161		return b.String() + renderCell(q)162	}163164	b.WriteString(grid())165	b.WriteString("\n**")166	b.WriteString(strconv.Itoa(strokes))167	b.WriteString("** stroke")168	if strokes != 1 {169		b.WriteString("s")170	}171	b.WriteString(" · palette: ")172	b.WriteString(Palette())173	b.WriteString("\n")174175	if board := leaderboard(); board != "" {176		b.WriteString("\n## Top painters\n\n")177		b.WriteString(board)178	}179	return b.String()180}181182// grid renders the canvas as rows of coloured blocks.183func grid() string {184	var b strings.Builder185	for y := 0; y < Size; y++ {186		for x := 0; x < Size; x++ {187			b.WriteString(palette[cells[y*Size+x]])188		}189		b.WriteString("\n")190	}191	return b.String()192}193194// leaderboard renders the top painters, or "" when nobody has painted.195func leaderboard() string {196	scores := []scorer{}197	tally.Iterate("", "", func(k string, v any) bool {198		scores = append(scores, scorer{addr: k, count: *(v.(*int))})199		return false200	})201	if len(scores) == 0 {202		return ""203	}204	sort.Sort(byCount(scores))205	if len(scores) > 5 {206		scores = scores[:5]207	}208209	var b strings.Builder210	b.WriteString("| # | address | strokes |\n|---|---|---|\n")211	for i, s := range scores {212		b.WriteString("| ")213		b.WriteString(strconv.Itoa(i + 1))214		b.WriteString(" | `")215		b.WriteString(s.addr)216		b.WriteString("` | ")217		b.WriteString(strconv.Itoa(s.count))218		b.WriteString(" |\n")219	}220	return b.String()221}222223// renderCell renders a single "x,y" lookup.224func renderCell(q string) string {225	var b strings.Builder226	x, y, ok := parseCoord(q)227	if !ok {228		b.WriteString("_Expected `x,y` — e.g. `/3,7`._\n")229		return b.String()230	}231	b.WriteString("## Cell (")232	b.WriteString(strconv.Itoa(x))233	b.WriteString(", ")234	b.WriteString(strconv.Itoa(y))235	b.WriteString(")\n\n")236	b.WriteString(palette[cells[y*Size+x]])237	b.WriteString(" **")238	b.WriteString(At(x, y))239	b.WriteString("**\n\n")240	if p := PainterAt(x, y); p != "" {241		b.WriteString("Painted by `")242		b.WriteString(p)243		b.WriteString("`.\n")244	} else {245		b.WriteString("_Never painted._\n")246	}247	return b.String()248}249250// parseArg extracts the first path segment.251func parseArg(path string) string {252	s := strings.TrimSpace(path)253	s = strings.TrimPrefix(s, "/")254	if i := strings.IndexByte(s, '/'); i >= 0 {255		s = s[:i]256	}257	return s258}259260// parseCoord parses "x,y" and reports whether it is in bounds.261func parseCoord(s string) (int, int, bool) {262	i := strings.IndexByte(s, ',')263	if i < 0 {264		return 0, 0, false265	}266	x, err := strconv.Atoi(strings.TrimSpace(s[:i]))267	if err != nil {268		return 0, 0, false269	}270	y, err2 := strconv.Atoi(strings.TrimSpace(s[i+1:]))271	if err2 != nil {272		return 0, 0, false273	}274	if x < 0 || x >= Size || y < 0 || y >= Size {275		return 0, 0, false276	}277	return x, y, true278}279

Functions

  • At(x int, y int) string

  • Paint(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}, x int, y int, colour string) int

  • PainterAt(x int, y int) string

  • Palette() string

  • Render(path string) string

  • Strokes() int

Signatures reconstructed verbatim from vm/qfuncs — interface params keep their inline definitions.

Rendered

RenderedRawgnoweb ↗

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.