Skip to content

Instantly share code, notes, and snippets.

@Trey2k
Last active March 2, 2023 17:00
Show Gist options
  • Save Trey2k/da45b86e4b5c915004eb5d39b9951bb9 to your computer and use it in GitHub Desktop.
Save Trey2k/da45b86e4b5c915004eb5d39b9951bb9 to your computer and use it in GitHub Desktop.
Tic Tac Toe Golang
package main
import (
"fmt"
"strings"
)
// set a board type for get and set functions
type board uint16
// position struct
type position struct {
turn uint8
board [2]board
draw bool
}
// Masks used for checking if there is a winner
const (
vertMask = 0b111
horzMask = 0b100100100
diagMask1 = 0b100010001
diagMask2 = 0b001010100
)
func (b board) get(index uint16) bool {
return (1<<index)&b != 0
}
func (b *board) set(index uint16) {
// Dereferance the board and set the bit at the current index
*b |= 1 << index
}
func (b board) countBits() int {
var r int
for r = 0; b > 0; r++ {
b &= b - 1
}
return r
}
func main() {
playAgain := true
// Reset the board after every game until the user quits
for playAgain {
pos := &position{}
pos.gameLoop()
var answer string
fmt.Print("Play again? (y/N): ")
fmt.Scanln(&answer)
answer = strings.ToLower(answer)
if playAgain = answer == "y"; playAgain {
fmt.Print("\n")
}
}
}
func (pos *position) gameLoop() {
ended := false
for !ended {
pos.print()
var toMove string
if pos.turn == 0 {
toMove = "X"
} else {
toMove = "O"
}
fmt.Printf("It is %s's turn\n", toMove)
validMove := false
// keep trying to get a move while the move provided is invalid
for !validMove {
fmt.Print("Your Move: ")
var move string
fmt.Scanln(&move)
validMove = pos.makeMove(move)
}
ended = pos.gameEnded()
if !ended {
pos.turn ^= 1 // 0 == 1 && 1 == 0
}
}
if !pos.draw {
pos.print()
var winner string
if pos.turn == 0 {
winner = "X"
} else {
winner = "O"
}
fmt.Printf("%s wins! Conratz!\n", winner)
} else {
fmt.Println("The game is a draw. No one wins!")
}
}
func (pos *position) makeMove(move string) bool {
// Return false if the move is more than 2 chars
if len(move) != 2 {
return false
}
// Convert all letters to lowercase so we can calculate from a known state
move = strings.ToLower(move)
// get the value of the first char and subtract it by the ascii value of 'a' so a wil == 0 b will == 1 and so on
row := uint16(move[0] - 'a')
// do the same thing except for the number, 1 == 0, 2 == 1 and so on
col := uint16(move[1] - '1')
// Convert our 2D coords into a 1D index for the bitboard
index := (row * 3) + col
// Check that no square is already taken
if pos.board[0].get(index) || pos.board[1].get(index) {
return false
}
// Set the bit for the active player
pos.board[pos.turn].set(index)
return true
}
func (pos *position) gameEnded() bool {
// Check board for player who jsut moved since they are the only possible winner.
// Check diagnal masks first since they require no loop
if pos.board[pos.turn]&diagMask1 == diagMask1 || pos.board[pos.turn]&diagMask2 == diagMask2 {
return true
}
// Loop 3 times for each horizantal and vertical mask
for i := uint16(0); i < 3; i++ {
// Bit shift the board by i*3 to the right to allign the board with our vertical mask
if vertMask&(pos.board[pos.turn]>>(i*3)) == vertMask {
return true
}
// Bit shift the board by i to the left to allign the board with our horizantal mask
if horzMask&(pos.board[pos.turn]<<i) == horzMask {
return true
}
}
// check for a draw
if pos.board[0].countBits()+pos.board[1].countBits() >= 9 {
pos.draw = true
return true
}
return false
}
func (pos *position) print() {
fmt.Println(" 1 2 3\n -------------")
letter := 'A'
for i := uint16(0); i < 9; i++ {
if i%3 == 0 {
if i > 0 {
fmt.Println(" |")
}
fmt.Printf("%s| ", string(letter))
letter++
}
if pos.board[0].get(i) {
fmt.Print(" X ")
} else if pos.board[1].get(i) {
fmt.Print(" O ")
} else {
fmt.Print(" - ")
}
}
fmt.Println(" |\n -------------")
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment