Search Apps Documentation Source Content File Folder Download Copy Actions Download State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

tictactoe.gno

3.55 Kb · 168 lines
  1// Package tictactoe is a two-player tic-tac-toe realm. The first caller to
  2// Join becomes X, the second becomes O; players then alternate Move calls by
  3// cell index (0-8, left-to-right top-to-bottom) until someone wins or the
  4// board fills. Reset starts a fresh game once the current one has ended.
  5package tictactoe
  6
  7import (
  8	"strconv"
  9
 10	"chain"
 11)
 12
 13var (
 14	zero address
 15
 16	board   [9]string // "" empty, "X", "O"
 17	playerX address
 18	playerO address
 19	turn    string // "X" or "O", whose move is next
 20	winner  string // "", "X", "O", or "Draw"
 21	moves   int
 22)
 23
 24func init() {
 25	turn = "X"
 26}
 27
 28// Join seats the caller as X (if the seat is open) or O (if X is taken).
 29// Panics if both seats are already filled.
 30func Join(cur realm) string {
 31	caller := cur.Previous().Address()
 32	switch {
 33	case playerX == zero:
 34		playerX = caller
 35		chain.Emit("Join", "player", caller.String(), "mark", "X")
 36		return "joined as X"
 37	case playerO == zero:
 38		if caller == playerX {
 39			panic("already joined as X")
 40		}
 41		playerO = caller
 42		chain.Emit("Join", "player", caller.String(), "mark", "O")
 43		return "joined as O"
 44	default:
 45		panic("game is full")
 46	}
 47}
 48
 49// Move places the caller's mark at the given cell (0-8) if it is their turn
 50// and the cell is empty, then checks for a win or a draw.
 51func Move(cur realm, pos int) {
 52	if winner != "" {
 53		panic("game already over, call Reset to start a new one")
 54	}
 55	if pos < 0 || pos > 8 {
 56		panic("position must be between 0 and 8")
 57	}
 58	if board[pos] != "" {
 59		panic("cell already occupied")
 60	}
 61
 62	var expected address
 63	if turn == "X" {
 64		expected = playerX
 65	} else {
 66		expected = playerO
 67	}
 68	if expected == zero {
 69		panic("waiting for both players to join")
 70	}
 71
 72	caller := cur.Previous().Address()
 73	if caller != expected {
 74		panic("not your turn")
 75	}
 76
 77	board[pos] = turn
 78	moves++
 79	chain.Emit("Move", "player", caller.String(), "mark", turn, "pos", strconv.Itoa(pos))
 80
 81	if checkWin(turn) {
 82		winner = turn
 83		chain.Emit("GameOver", "result", winner)
 84	} else if moves == 9 {
 85		winner = "Draw"
 86		chain.Emit("GameOver", "result", winner)
 87	} else if turn == "X" {
 88		turn = "O"
 89	} else {
 90		turn = "X"
 91	}
 92}
 93
 94// Reset clears the board for a new game. Only callable once the current
 95// game has ended (someone won or the board is full).
 96func Reset(cur realm) {
 97	if winner == "" {
 98		panic("game still in progress")
 99	}
100	board = [9]string{}
101	playerX = zero
102	playerO = zero
103	turn = "X"
104	winner = ""
105	moves = 0
106	chain.Emit("Reset")
107}
108
109var winLines = [8][3]int{
110	{0, 1, 2}, {3, 4, 5}, {6, 7, 8}, // rows
111	{0, 3, 6}, {1, 4, 7}, {2, 5, 8}, // columns
112	{0, 4, 8}, {2, 4, 6}, // diagonals
113}
114
115func checkWin(mark string) bool {
116	for _, line := range winLines {
117		if board[line[0]] == mark && board[line[1]] == mark && board[line[2]] == mark {
118			return true
119		}
120	}
121	return false
122}
123
124func cell(pos int) string {
125	if board[pos] == "" {
126		return strconv.Itoa(pos)
127	}
128	return board[pos]
129}
130
131// Render draws the current board and game status as markdown.
132func Render(path string) string {
133	out := "# Tic-Tac-Toe\n\n"
134	out += "|   |   |   |\n"
135	out += "|---|---|---|\n"
136	for row := 0; row < 3; row++ {
137		out += "| "
138		for col := 0; col < 3; col++ {
139			out += cell(row*3+col) + " | "
140		}
141		out += "\n"
142	}
143	out += "\n"
144
145	switch {
146	case winner == "Draw":
147		out += "**Game over: draw.**\n"
148	case winner != "":
149		out += "**Game over: " + winner + " wins!**\n"
150	default:
151		out += "Turn: **" + turn + "**\n"
152	}
153
154	out += "\nPlayer X: "
155	if playerX == zero {
156		out += "_(open)_"
157	} else {
158		out += playerX.String()
159	}
160	out += "\n\nPlayer O: "
161	if playerO == zero {
162		out += "_(open)_"
163	} else {
164		out += playerO.String()
165	}
166	out += "\n"
167	return out
168}