PathrockNetwork Gno Explorer
HomeBlocksTransactionsRealmsPackagesValidatorsAnalytics

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/connect4/v0

Realm
Open in gnoweb ↗

Overview

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

Files (3)

  • README.mdmarkdown
  • gnomod.tomltoml
  • connect4.gnogno
connect4.gnogno
1package connect423import (4	"chain"5	"chain/runtime/unsafe"6	"errors"7	"strconv"8	"strings"910	"gno.land/p/nt/avl/v0"11)1213const (14	cols = 715	rows = 616)1718// cell values19const (20	empty = 021	red   = 1 // 🔴 game creator (caller of NewGame)22	yel   = 2 // 🟡 opponent23)2425// Game holds the full state of one Connect Four match.26// board is indexed board[row][col]; row 0 is the BOTTOM row.27type Game struct {28	ID       int6429	Red      address // 🔴 creator, moves first30	Yellow   address // 🟡 opponent31	Board    [rows][cols]int32	Turn     int  // whose turn: red or yel33	Winner   int  // empty until decided; red/yel = winner34	Draw     bool // true when board full with no winner35	Finished bool36}3738var (39	games  = avl.NewTree() // id (zero-padded string) -> *Game40	nextID int6441)4243func idKey(id int64) string {44	// zero-pad so avl iteration order matches numeric order for Render lists45	s := strconv.FormatInt(id, 10)46	for len(s) < 12 {47		s = "0" + s48	}49	return s50}5152// NewGame creates a match between the caller (🔴) and opponent (🟡).53// Returns the new game id.54func NewGame(cur realm, opponent address) int64 {55	caller := unsafe.PreviousRealm().Address()56	if opponent == caller {57		panic("opponent must differ from caller")58	}59	if opponent.String() == "" {60		panic("opponent address is empty")61	}62	nextID++63	g := &Game{64		ID:     nextID,65		Red:    caller,66		Yellow: opponent,67		Turn:   red,68	}69	games.Set(idKey(g.ID), g)70	chain.Emit("GameCreated",71		"id", strconv.FormatInt(g.ID, 10),72		"red", caller.String(),73		"yellow", opponent.String(),74	)75	return g.ID76}7778func getGame(id int64) (*Game, error) {79	v := games.Get(idKey(id))80	if v == nil {81		return nil, errors.New("game not found: " + strconv.FormatInt(id, 10))82	}83	return v.(*Game), nil84}8586// Drop places the caller's disc into the given column (0-6).87// Enforces turn order by caller, rejects full columns, and detects88// a 4-in-a-row win or a draw.89func Drop(cur realm, gameID int64, column int) {90	if column < 0 || column >= cols {91		panic("column out of range (0-6)")92	}93	g, err := getGame(gameID)94	if err != nil {95		panic(err.Error())96	}97	if g.Finished {98		panic("game already finished")99	}100101	caller := unsafe.PreviousRealm().Address()102	var mover int103	switch caller {104	case g.Red:105		mover = red106	case g.Yellow:107		mover = yel108	default:109		panic("caller is not a player in this game")110	}111	if mover != g.Turn {112		panic("not your turn")113	}114115	// find lowest empty row in the column116	placed := -1117	for r := 0; r < rows; r++ {118		if g.Board[r][column] == empty {119			g.Board[r][column] = mover120			placed = r121			break122		}123	}124	if placed == -1 {125		panic("column is full")126	}127128	if wins(&g.Board, placed, column, mover) {129		g.Winner = mover130		g.Finished = true131		chain.Emit("GameWon",132			"id", strconv.FormatInt(g.ID, 10),133			"winner", caller.String(),134		)135	} else if full(&g.Board) {136		g.Draw = true137		g.Finished = true138		chain.Emit("GameDraw", "id", strconv.FormatInt(g.ID, 10))139	} else {140		if g.Turn == red {141			g.Turn = yel142		} else {143			g.Turn = red144		}145		chain.Emit("DiscDropped",146			"id", strconv.FormatInt(g.ID, 10),147			"col", strconv.Itoa(column),148			"player", caller.String(),149		)150	}151152	games.Set(idKey(g.ID), g)153}154155func full(b *[rows][cols]int) bool {156	for c := 0; c < cols; c++ {157		if b[rows-1][c] == empty {158			return false159		}160	}161	return true162}163164// wins checks whether the disc just placed at (r,c) for player p completes165// a run of 4 in any of the four directions.166func wins(b *[rows][cols]int, r, c, p int) bool {167	// direction pairs: horizontal, vertical, diag /, diag \168	dirs := [4][2]int{{0, 1}, {1, 0}, {1, 1}, {1, -1}}169	for _, d := range dirs {170		count := 1171		count += run(b, r, c, d[0], d[1], p)172		count += run(b, r, c, -d[0], -d[1], p)173		if count >= 4 {174			return true175		}176	}177	return false178}179180func run(b *[rows][cols]int, r, c, dr, dc, p int) int {181	n := 0182	for i := 1; i < 4; i++ {183		rr := r + dr*i184		cc := c + dc*i185		if rr < 0 || rr >= rows || cc < 0 || cc >= cols {186			break187		}188		if b[rr][cc] != p {189			break190		}191		n++192	}193	return n194}195196func glyph(v int) string {197	switch v {198	case red:199		return "🔴"200	case yel:201		return "🟡"202	default:203		return "·"204	}205}206207// Render draws the board with 🔴🟡· and shows whose turn / the winner.208// path "" lists all games; path "<id>" shows a single board.209func Render(path string) string {210	path = strings.TrimSpace(strings.Trim(path, "/"))211	if path == "" {212		return renderList()213	}214	id, err := strconv.ParseInt(path, 10, 64)215	if err != nil {216		return "# Connect Four\n\nInvalid game id: `" + path + "`\n"217	}218	g, err := getGame(id)219	if err != nil {220		return "# Connect Four\n\n" + err.Error() + "\n"221	}222	return renderGame(g)223}224225func renderList() string {226	var sb strings.Builder227	sb.WriteString("# Connect Four\n\n")228	sb.WriteString("Two-player Connect Four on a 7×6 board. 🔴 is the game creator, 🟡 the opponent.\n\n")229	if games.Size() == 0 {230		sb.WriteString("_No games yet. Call `NewGame(opponent)` to start one._\n")231		return sb.String()232	}233	sb.WriteString("## Games\n\n")234	sb.WriteString("| ID | 🔴 Red | 🟡 Yellow | Status |\n")235	sb.WriteString("|----|--------|-----------|--------|\n")236	games.Iterate("", "", func(_ string, v any) bool {237		g := v.(*Game)238		status := ""239		switch {240		case g.Winner == red:241			status = "🔴 won"242		case g.Winner == yel:243			status = "🟡 won"244		case g.Draw:245			status = "draw"246		case g.Turn == red:247			status = "🔴 to move"248		default:249			status = "🟡 to move"250		}251		sb.WriteString("| [" + strconv.FormatInt(g.ID, 10) + "](/r:" + strconv.FormatInt(g.ID, 10) + ") | " +252			short(g.Red) + " | " + short(g.Yellow) + " | " + status + " |\n")253		return false254	})255	sb.WriteString("\nOpen a game by its id (e.g. path `1`).\n")256	return sb.String()257}258259func short(a address) string {260	s := a.String()261	if len(s) <= 12 {262		return "`" + s + "`"263	}264	return "`" + s[:8] + "…" + s[len(s)-4:] + "`"265}266267func renderGame(g *Game) string {268	var sb strings.Builder269	sb.WriteString("# Connect Four — Game " + strconv.FormatInt(g.ID, 10) + "\n\n")270	sb.WriteString("- 🔴 Red: `" + g.Red.String() + "`\n")271	sb.WriteString("- 🟡 Yellow: `" + g.Yellow.String() + "`\n\n")272273	// status line274	switch {275	case g.Winner == red:276		sb.WriteString("**🔴 Red wins!**\n\n")277	case g.Winner == yel:278		sb.WriteString("**🟡 Yellow wins!**\n\n")279	case g.Draw:280		sb.WriteString("**Draw — board full.**\n\n")281	case g.Turn == red:282		sb.WriteString("**Turn: 🔴 Red**\n\n")283	default:284		sb.WriteString("**Turn: 🟡 Yellow**\n\n")285	}286287	// board top-down (row rows-1 at top, row 0 at bottom)288	sb.WriteString("```\n")289	for r := rows - 1; r >= 0; r-- {290		for c := 0; c < cols; c++ {291			sb.WriteString(glyph(g.Board[r][c]))292		}293		sb.WriteString("\n")294	}295	// column indices296	for c := 0; c < cols; c++ {297		sb.WriteString(strconv.Itoa(c))298	}299	sb.WriteString("\n```\n\n")300301	if !g.Finished {302		sb.WriteString("Drop a disc: `Drop(" + strconv.FormatInt(g.ID, 10) + ", <col 0-6>)`\n")303	}304	return sb.String()305}306

Functions

  • Drop(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 int64, column 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) int64

  • Render(path string) string

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.