1package tictactoe23import (4 "chain"5 "chain/runtime/unsafe"6 "errors"7 "strconv"8 "strings"910 "gno.land/p/nt/avl/v0"11)1213// Cell states.14const (15 empty = 016 markX = 117 markO = 218)1920// Game holds a single tic-tac-toe match.21type Game struct {22 id int23 playerX address // caller of NewGame24 playerO address // opponent25 board [9]int // 0 empty, 1 X, 2 O26 moves int // number of moves played27 turn int // markX or markO — whose turn it is28 winner int // 0 none, 1 X, 2 O29 draw bool30 finished bool31}3233var (34 games = avl.NewTree() // id (padded string) -> *Game35 nextID = 136)3738func pad(id int) string {39 s := strconv.Itoa(id)40 for len(s) < 12 {41 s = "0" + s42 }43 return s44}4546// NewGame creates a game. Caller is X, opponent is O. Returns the game id.47func NewGame(cur realm, opponent address) int {48 caller := unsafe.PreviousRealm().Address()49 if opponent == caller {50 panic("opponent must differ from caller")51 }52 if !opponent.IsValid() {53 panic("invalid opponent address")54 }55 id := nextID56 nextID++57 g := &Game{58 id: id,59 playerX: caller,60 playerO: opponent,61 turn: markX,62 }63 games.Set(pad(id), g)64 chain.Emit("GameCreated", "id", strconv.Itoa(id), "x", caller.String(), "o", opponent.String())65 return id66}6768// Move plays cell (0-8) for the calling player. Enforces turn order and rejects taken cells.69func Move(cur realm, gameID int, cell int) {70 v := games.Get(pad(gameID))71 if v == nil {72 panic("game not found")73 }74 g := v.(*Game)75 if g.finished {76 panic("game already finished")77 }78 if cell < 0 || cell > 8 {79 panic("cell out of range (0-8)")80 }81 if g.board[cell] != empty {82 panic("cell already taken")83 }84 caller := unsafe.PreviousRealm().Address()8586 var mark int87 switch caller {88 case g.playerX:89 mark = markX90 case g.playerO:91 mark = markO92 default:93 panic("caller is not a player in this game")94 }95 if mark != g.turn {96 panic("not your turn")97 }9899 g.board[cell] = mark100 g.moves++101102 if won(g.board, mark) {103 g.winner = mark104 g.finished = true105 chain.Emit("GameWon", "id", strconv.Itoa(gameID), "winner", markName(mark))106 } else if g.moves == 9 {107 g.draw = true108 g.finished = true109 chain.Emit("GameDraw", "id", strconv.Itoa(gameID))110 } else {111 if g.turn == markX {112 g.turn = markO113 } else {114 g.turn = markX115 }116 chain.Emit("Move", "id", strconv.Itoa(gameID), "cell", strconv.Itoa(cell), "mark", markName(mark))117 }118}119120var lines = [8][3]int{121 {0, 1, 2}, {3, 4, 5}, {6, 7, 8}, // rows122 {0, 3, 6}, {1, 4, 7}, {2, 5, 8}, // cols123 {0, 4, 8}, {2, 4, 6}, // diagonals124}125126func won(b [9]int, mark int) bool {127 for _, l := range lines {128 if b[l[0]] == mark && b[l[1]] == mark && b[l[2]] == mark {129 return true130 }131 }132 return false133}134135func markName(m int) string {136 switch m {137 case markX:138 return "X"139 case markO:140 return "O"141 default:142 return "-"143 }144}145146func cellGlyph(m int) string {147 switch m {148 case markX:149 return "X"150 case markO:151 return "O"152 default:153 return "·"154 }155}156157// GetGame returns a game by id (read helper for tests / callers).158func GetGame(id int) (*Game, error) {159 v := games.Get(pad(id))160 if v == nil {161 return nil, errors.New("game not found")162 }163 return v.(*Game), nil164}165166func renderBoard(b [9]int) string {167 var sb strings.Builder168 for r := 0; r < 3; r++ {169 sb.WriteString("| ")170 for c := 0; c < 3; c++ {171 sb.WriteString(cellGlyph(b[r*3+c]))172 sb.WriteString(" | ")173 }174 sb.WriteString("\n")175 }176 return sb.String()177}178179func gameStatus(g *Game) string {180 if g.finished {181 if g.draw {182 return "Draw."183 }184 return "Winner: " + markName(g.winner)185 }186 return "Turn: " + markName(g.turn)187}188189// Render draws the list of games, or a single game board at path "/<id>".190func Render(path string) string {191 p := strings.TrimPrefix(path, "/")192 if p == "" {193 return renderList()194 }195 id, err := strconv.Atoi(p)196 if err != nil {197 return "# Tic-Tac-Toe\n\nInvalid game id: " + p + "\n"198 }199 g, gerr := GetGame(id)200 if gerr != nil {201 return "# Tic-Tac-Toe\n\nGame " + p + " not found.\n"202 }203 var sb strings.Builder204 sb.WriteString("# Tic-Tac-Toe — Game #")205 sb.WriteString(strconv.Itoa(g.id))206 sb.WriteString("\n\n")207 sb.WriteString("- X: `")208 sb.WriteString(g.playerX.String())209 sb.WriteString("`\n- O: `")210 sb.WriteString(g.playerO.String())211 sb.WriteString("`\n\n")212 sb.WriteString(renderBoard(g.board))213 sb.WriteString("\n**")214 sb.WriteString(gameStatus(g))215 sb.WriteString("**\n\nCell indices:\n\n")216 sb.WriteString("| 0 | 1 | 2 |\n| 3 | 4 | 5 |\n| 6 | 7 | 8 |\n")217 return sb.String()218}219220func renderList() string {221 var sb strings.Builder222 sb.WriteString("# Tic-Tac-Toe\n\n")223 sb.WriteString("2-player tic-tac-toe. Call `NewGame(opponent)` to start; view a game at `/<id>`.\n\n")224 if games.Size() == 0 {225 sb.WriteString("_No games yet._\n")226 return sb.String()227 }228 sb.WriteString("| # | X | O | Status |\n|---|---|---|--------|\n")229 games.Iterate("", "", func(key string, value interface{}) bool {230 g := value.(*Game)231 sb.WriteString("| ")232 sb.WriteString(strconv.Itoa(g.id))233 sb.WriteString(" | `")234 sb.WriteString(g.playerX.String())235 sb.WriteString("` | `")236 sb.WriteString(g.playerO.String())237 sb.WriteString("` | ")238 sb.WriteString(gameStatus(g))239 sb.WriteString(" |\n")240 return false241 })242 return sb.String()243}244GetGame(id int) (*gno.land/r/moul/x/daily/tictactoe/v0.Game, interface {Error func() string})
Move(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}, gameID int, cell int)
NewGame(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}, opponent string) int
Render(path string) 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.