Transaction

6E07D13417C809…AB22395A0481

Block 26,096 · index 0 · indexed

Summary

Hash
6E07D13417C809F209C31F5A90795F08ADA6083E905F3A07743EAB22395A0481
Block
26,096
Size
10636 bytes
Gas used
15,369,135 / 18,443,022
Fee
1000000 ugnot
Memo
gnopublish
Status
success

Messages

Arguments · 9

  1. #1pixelcanvas
  2. #2README.md
  3. #3# Pixel Canvas > ⚠️ **Experimental — generated with no human supervision** by the daily MCP pipeline to exercise gno tooling. Not audited. See [r/moul/x/daily](https://github.com/moul/gno-contracts/blob/main/r/moul/x/daily/README.md). --- A shared 16x16 canvas. Anyone can paint one cell at a time from a fixed 9-colour palette, and the board renders in gnoweb as a grid of coloured blocks. Painting is **last-write-wins** — that's the point of a shared canvas. Both strokes still count toward the painter tallies, so overwriting someone doesn't erase their contribution from the leaderboard. ``` Paint(x, y, colour) → colours a cell, returns the total stroke count At(x, y) → the colour name of a cell ("white" when untouched) PainterAt(x, y) → who last painted it ("" when untouched) Strokes() → total paints ever Palette() → the accepted colour names /r/moul/x/daily/pixelcanvas/v0 → the canvas, palette and top painters /r/moul/x/daily/pixelcanvas/v0:4,9 → who painted that cell, and what colour ``` Coordinates are 0-based with `(0,0)` at the **top-left**, and are `x` (column) then `y` (row) — so `(3,7)` is column 3 of row 7. Colour names are matched case- and space-insensitively. Palette: `white`, `red`, `orange`, `yellow`, `green`, `blue`, `purple`, `brown`, `black`. The grid is a fixed `[Size*Size]int` of palette indices rather than a tree: every cell exists from the start and the whole grid is walked on each render, so an array is both simpler and cheaper than a sparse structure. Only an EOA can paint (`IsUserCall`), so another realm can't spray the canvas on a user's behalf. Built for gno 0.9. Live on both testnets (agent address): - sapphire — https://sapphire.testnets.gno.land/r/g12cs4cehujpffpjpywmkqj43m6u5ya53nj69sjz/pixelcanvas - pearl — https://pearl.testnets.gno.land/r/g12cs4cehujpffpjpywmkqj43m6u5ya53nj69sjz/pixelcanvas <!-- BEGIN GNOCONTRACTS FOOTER (generated by `make readmes`; do not edit below) --> --- Part of **[moul/gno-contracts](https://github.com/moul/gno-contracts)** — moul's versioned gno.land contracts. See the repository for the full catalog, build/test tooling, and usage. **Dependency graph:** ![gno.land/r/moul/x/daily/pixelcanvas/v0 dependency graph](https://raw.githubusercontent.com/moul/gno-contracts/main/_assets/gno.land/r/moul/x/daily/pixelcanvas/v0/deps.png) > 🧪 **Highly experimental — potentially vibe-coded.** Not audited; may break, change, or be removed at any time. Do not use with anything of value. Full disclaimer: [DISCLAIMER](https://github.com/moul/gno-contracts/blob/main/DISCLAIMER.md). <!-- END GNOCONTRACTS FOOTER -->
  4. #4gnomod.toml
  5. #5module = "gno.land/r/moul/x/daily/pixelcanvas/v0" gno = "0.9"
  6. #6pixelcanvas.gno
  7. #7// Package pixelcanvas is a shared 16x16 pixel canvas: anyone can paint one // cell at a time from a fixed 9-colour palette, and the canvas renders in // gnoweb as a grid of coloured blocks. // // The canvas is a flat [Size*Size]int of palette indices held in realm state — // a fixed-size array rather than a tree, because every cell exists from the // start and the grid is walked in full on every render. Painting is // last-write-wins; the realm keeps who painted each cell and a per-address // tally, so the board doubles as a contribution leaderboard. // // A stateful app with nothing reusable to extract, so it ships as a lone realm // rather than a p/ library + demo pair. package pixelcanvas import ( "sort" "strconv" "strings" "chain" "chain/runtime" "gno.land/p/nt/avl/v0" ) // Size is the canvas edge length; the canvas is Size x Size cells. const Size = 16 // palette maps a colour index to the block rendered for it. Index 0 is the // blank canvas. Kept parallel to paletteNames. var palette = []string{"⬜", "🟥", "🟧", "🟨", "🟩", "🟦", "🟪", "🟫", "⬛"} // paletteNames are the names Paint accepts, in palette order. var paletteNames = []string{"white", "red", "orange", "yellow", "green", "blue", "purple", "brown", "black"} // Package-level persistent state. var ( cells [Size * Size]int // palette index per cell, row-major painters [Size * Size]string tally = avl.NewTree() // address string -> *int paint count strokes int // total paints ever lastAt int64 // height of the most recent paint ) // Paint colours the cell at (x, y) and returns the total number of strokes. // // Coordinates are 0-based with (0,0) at the top-left. Colour is a palette name // ("red", "blue", …) — see Palette. Painting over an existing cell is allowed: // the canvas is last-write-wins, which is the whole point of a shared board. func Paint(cur realm, x, y int, colour string) int { if !cur.IsCurrent() { panic("spoofed realm") } prev := cur.Previous() if !prev.IsUserCall() { panic("only an EOA via MsgCall can paint") } if x < 0 || x >= Size || y < 0 || y >= Size { panic("out of bounds: x and y must be in [0," + strconv.Itoa(Size-1) + "]") } idx := colourIndex(colour) if idx < 0 { panic("unknown colour " + strconv.Quote(colour) + "; see Palette()") } addr := prev.Address() i := y*Size + x cells[i] = idx painters[i] = addr.String() strokes++ lastAt = runtime.ChainHeight() bump(addr.String()) chain.Emit("Paint", "addr", addr.String(), "x", strconv.Itoa(x), "y", strconv.Itoa(y), "colour", paletteNames[idx], ) return strokes } // colourIndex resolves a palette name to its index, or -1. func colourIndex(name string) int { n := strings.ToLower(strings.TrimSpace(name)) for i, p := range paletteNames { if p == n { return i } } return -1 } // bump increments an address's paint tally. func bump(addr string) { // avl/v0's Get returns a single `any`; a miss is a nil interface. if v := tally.Get(addr); v != nil { c := v.(*int) *c++ return } one := 1 tally.Set(addr, &one) } // At returns the palette name of the cell at (x, y), or "" when out of bounds. func At(x, y int) string { if x < 0 || x >= Size || y < 0 || y >= Size { return "" } return paletteNames[cells[y*Size+x]] } // PainterAt returns the address that last painted (x, y), or "" if untouched. func PainterAt(x, y int) string { if x < 0 || x >= Size || y < 0 || y >= Size { return "" } return painters[y*Size+x] } // Strokes returns how many paints the canvas has taken. func Strokes() int { return strokes } // Palette returns the accepted colour names, comma-separated. func Palette() string { return strings.Join(paletteNames, ", ") } // scorer is one row of the contribution leaderboard. type scorer struct { addr string count int } // byCount ranks painters by strokes descending, ties broken on address so the // board is deterministic. type byCount []scorer func (s byCount) Len() int { return len(s) } func (s byCount) Swap(i, j int) { s[i], s[j] = s[j], s[i] } func (s byCount) Less(i, j int) bool { if s[i].count != s[j].count { return s[i].count > s[j].count } return s[i].addr < s[j].addr } // Render renders the canvas for gnoweb. // // Render("") / Render("/") -> the canvas, palette and leaderboard // Render("/<x>,<y>") -> who painted that cell, and what colour func Render(path string) string { var b strings.Builder b.WriteString("# Pixel Canvas\n\n") b.WriteString("A shared ") b.WriteString(strconv.Itoa(Size)) b.WriteString("x") b.WriteString(strconv.Itoa(Size)) b.WriteString(" canvas. Anyone can `Paint(x, y, colour)` — last write wins.\n\n") if q := parseArg(path); q != "" { return b.String() + renderCell(q) } b.WriteString(grid()) b.WriteString("\n**") b.WriteString(strconv.Itoa(strokes)) b.WriteString("** stroke") if strokes != 1 { b.WriteString("s") } b.WriteString(" · palette: ") b.WriteString(Palette()) b.WriteString("\n") if board := leaderboard(); board != "" { b.WriteString("\n## Top painters\n\n") b.WriteString(board) } return b.String() } // grid renders the canvas as rows of coloured blocks. func grid() string { var b strings.Builder for y := 0; y < Size; y++ { for x := 0; x < Size; x++ { b.WriteString(palette[cells[y*Size+x]]) } b.WriteString("\n") } return b.String() } // leaderboard renders the top painters, or "" when nobody has painted. func leaderboard() string { scores := []scorer{} tally.Iterate("", "", func(k string, v any) bool { scores = append(scores, scorer{addr: k, count: *(v.(*int))}) return false }) if len(scores) == 0 { return "" } sort.Sort(byCount(scores)) if len(scores) > 5 { scores = scores[:5] } var b strings.Builder b.WriteString("| # | address | strokes |\n|---|---|---|\n") for i, s := range scores { b.WriteString("| ") b.WriteString(strconv.Itoa(i + 1)) b.WriteString(" | `") b.WriteString(s.addr) b.WriteString("` | ") b.WriteString(strconv.Itoa(s.count)) b.WriteString(" |\n") } return b.String() } // renderCell renders a single "x,y" lookup. func renderCell(q string) string { var b strings.Builder x, y, ok := parseCoord(q) if !ok { b.WriteString("_Expected `x,y` — e.g. `/3,7`._\n") return b.String() } b.WriteString("## Cell (") b.WriteString(strconv.Itoa(x)) b.WriteString(", ") b.WriteString(strconv.Itoa(y)) b.WriteString(")\n\n") b.WriteString(palette[cells[y*Size+x]]) b.WriteString(" **") b.WriteString(At(x, y)) b.WriteString("**\n\n") if p := PainterAt(x, y); p != "" { b.WriteString("Painted by `") b.WriteString(p) b.WriteString("`.\n") } else { b.WriteString("_Never painted._\n") } return b.String() } // parseArg extracts the first path segment. func parseArg(path string) string { s := strings.TrimSpace(path) s = strings.TrimPrefix(s, "/") if i := strings.IndexByte(s, '/'); i >= 0 { s = s[:i] } return s } // parseCoord parses "x,y" and reports whether it is in bounds. func parseCoord(s string) (int, int, bool) { i := strings.IndexByte(s, ',') if i < 0 { return 0, 0, false } x, err := strconv.Atoi(strings.TrimSpace(s[:i])) if err != nil { return 0, 0, false } y, err2 := strconv.Atoi(strings.TrimSpace(s[i+1:])) if err2 != nil { return 0, 0, false } if x < 0 || x >= Size || y < 0 || y >= Size { return 0, 0, false } return x, y, true }
  8. #8/gno.MemPackageType
  9. #9 MPUserProd

Result log

msg:0,success:true,log:,events:[]