Created
June 2, 2019 05:22
-
-
Save akalin/bf7d117c0367d8459feca3c902c88c6a to your computer and use it in GitHub Desktop.
Simple Tic-Tac-Toe console game
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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