Skip to content

Instantly share code, notes, and snippets.

@akalin
Created June 2, 2019 05:22
Show Gist options
  • Save akalin/bf7d117c0367d8459feca3c902c88c6a to your computer and use it in GitHub Desktop.
Save akalin/bf7d117c0367d8459feca3c902c88c6a to your computer and use it in GitHub Desktop.
Simple Tic-Tac-Toe console game
package main
import (
"bufio"
"fmt"
"os"
"strings"
)
type player int
const (
noPlayer player = 0
playerOne player = 1
playerTwo player = 2
)
// A board contains the cells of the board (by row and column) and
// which player moved in that cell.
type board [3][3]player
const boardSize = 3
func (b board) String() string {
var s string
s += " "
for i := 0; i < boardSize; i++ {
s += fmt.Sprintf(" %d", (i + 1))
}
s += "\n\n"
for i := 0; i < boardSize; i++ {
s += string('A'+i) + " "
for j := 0; j < boardSize; j++ {
switch b[i][j] {
case noPlayer:
s += " "
case playerOne:
s += "X"
case playerTwo:
s += "O"
}
if j < boardSize-1 {
s += "|"
}
}
if i < boardSize-1 {
s += "\n " + strings.Repeat("-", 2*boardSize-1) + "\n"
}
}
return s
}
// getWinner returns the winner given the current moves on the board,
// or noPlayer if there is no winner.
func (b board) getWinner() player {
// Check each row.
outerRow:
for i := 0; i < boardSize; i++ {
p := b[i][0]
if p == noPlayer {
continue
}
for j := 1; j < boardSize; j++ {
if b[i][j] != p {
continue outerRow
}
}
return p
}
// Check each column.
outerCol:
for i := 0; i < boardSize; i++ {
p := b[0][i]
if p == noPlayer {
continue
}
for j := 1; j < boardSize; j++ {
if b[j][i] != p {
continue outerCol
}
}
return p
}
// Check the diagonal from the upper left.
p := b[0][0]
if p != noPlayer {
diagonalFilled := true
for i := 1; i < boardSize; i++ {
if b[i][i] != p {
diagonalFilled = false
break
}
}
if diagonalFilled {
return p
}
}
// Check the diagonal from the upper right.
p = b[0][boardSize-1]
if p != noPlayer {
diagonalFilled := true
for i := 1; i < boardSize; i++ {
if b[i][boardSize-1-i] != p {
diagonalFilled = false
break
}
}
if diagonalFilled {
return p
}
}
return noPlayer
}
// promptMove prompts currentPlayer for a move and checks it against
// the board. If a nil error is returned, that means that
// board[row][col] is an unfilled cell.
func promptMove(input *bufio.Reader, output *os.File, currentPlayer player, board board) (row, col int, err error) {
for {
fmt.Fprintf(output, "Player %d, what's your next move? (e.g., \"b2\") ", currentPlayer)
s, err := input.ReadString('\n')
if err != nil {
return 0, 0, err
}
s = strings.TrimSpace(strings.ToLower(s))
if len(s) != 2 {
fmt.Fprintf(output, "Unrecognized move: %q\n", s)
continue
}
row := int(s[0] - 'a')
if row < 0 || row >= boardSize {
fmt.Fprintf(output, "Invalid row: %q\n", s)
continue
}
col := int(s[1] - '1')
if col < 0 || col >= boardSize {
fmt.Fprintf(output, "Invalid column: %q\n", s)
continue
}
if board[row][col] != noPlayer {
fmt.Fprintf(output, "Cell already filled: %q\n", s)
continue
}
return row, col, nil
}
}
// playGame plays a single game of Tic-Tac-Toe.
func playGame(input *bufio.Reader, output *os.File) error {
fmt.Fprintln(output, "Starting new game")
currentPlayer := playerOne
var board board
for {
fmt.Fprintf(output, "\n%s\n\n", board)
row, col, err := promptMove(input, output, currentPlayer, board)
if err != nil {
return err
}
board[row][col] = currentPlayer
winner := board.getWinner()
if winner != noPlayer {
fmt.Fprintf(output, "\n%s\n\nPlayer %d wins!\n", board, currentPlayer)
return nil
}
currentPlayer = (3 - currentPlayer)
}
}
func promptYesNo(input *bufio.Reader, output *os.File, prompt string) (result bool, err error) {
for {
fmt.Fprintf(output, "%s (Y/N) ", prompt)
s, err := input.ReadString('\n')
if err != nil {
return false, err
}
s = strings.TrimSpace(strings.ToLower(s))
if s == "y" || s == "yes" {
return true, nil
}
if s == "n" || s == "no" {
return false, nil
}
fmt.Fprintf(output, "Unrecognized answer: %q\n", s)
}
}
func main() {
input := bufio.NewReader(os.Stdin)
output := os.Stdout
fmt.Fprintln(output, "Welcome to Tic-Tac-Toe!")
for {
err := playGame(input, output)
if err != nil {
panic(err)
}
result, err := promptYesNo(input, output, "New game?")
if err != nil {
panic(err)
}
if !result {
fmt.Fprintln(output, "Goodbye!")
break
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment