Skip to content

Instantly share code, notes, and snippets.

@justintout
Last active April 8, 2021 04:00
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save justintout/736b51e6e5dd655c87d91cbab6773c5e to your computer and use it in GitHub Desktop.
Save justintout/736b51e6e5dd655c87d91cbab6773c5e to your computer and use it in GitHub Desktop.
2 player Tic-Tac-Toe in Go
package main
import (
"fmt"
"os"
"strconv"
)
type player int
const (
playerO player = iota
playerX
nobody
)
func (p player) String() string {
switch p {
case playerO:
return "Player O"
case playerX:
return "Player X"
}
return "Nobody"
}
var winConditions []int = []int{
0b000000000000000111,
0b000000000000111000,
0b000000000111000000,
0b000000000100100100,
0b000000000010010010,
0b000000000001001001,
0b000000000100010001,
0b000000000001010100,
}
type board struct {
state int
}
func newBoard(state int) *board {
return &board{
state: state,
}
}
func (b board) String() string {
return fmt.Sprintf("%s\u2502%s\u2502%s\n\u2500\u253c\u2500\u253c\u2500\n%s\u2502%s\u2502%s\n\u2500\u253c\u2500\u253c\u2500\n%s\u2502%s\u2502%s", b.label(0), b.label(1), b.label(2), b.label(3), b.label(4), b.label(5), b.label(6), b.label(7), b.label(8))
}
func (b board) occupied(s int) bool {
return (b.state>>s)&1 == 1
}
func (b board) label(s int) string {
if !b.occupied(s) {
return strconv.Itoa(s)
}
if (b.state >> (9 + s) & 1) == 1 {
return "X"
}
return "O"
}
func (b board) turn() int {
x := 0
for (b.state & 0b000000000111111111) > 0 {
b.state = b.state & (b.state - 1)
x++
}
return x
}
func (b board) playerTurn() player {
return player(b.turn() % 2)
}
func (b *board) mark(s int) error {
p := b.playerTurn()
if b.occupied(s) {
return fmt.Errorf("space %d is already occupied", s)
}
b.state = b.state | (1<<s + (int(p) << 9 << s))
return nil
}
func (b board) terminal() (terminal bool, won bool, winner player) {
for i := 0; i < len(winConditions); i++ {
mask := (winConditions[i] << 9) + winConditions[i]
masked := b.state & mask
switch {
case masked == winConditions[i]:
return true, true, playerO
case masked == mask:
return true, true, playerX
}
}
if b.turn() == 9 {
return true, false, nobody
}
return false, false, nobody
}
func play() {
b := newBoard(0)
finished := false
player := nobody
for !finished {
fmt.Println(b)
fmt.Printf("%s, choose a space: ", b.playerTurn())
var s int
if _, err := fmt.Scanf("%d", &s); err != nil {
continue
}
if err := b.mark(s); err != nil {
fmt.Printf("Space %d can't be marked. Choose a different space.\n", s)
}
fmt.Println()
finished, _, player = b.terminal()
}
fmt.Println(b)
fmt.Printf("%s wins!\n", player)
fmt.Println()
}
func main() {
for {
play()
fmt.Printf("Again? [y/N]: ")
var again string
fmt.Scanf("%s", &again)
if again != "y" && again != "Y" && again != "Yes" && again != "yes" {
os.Exit(0)
}
}
}
@justintout
Copy link
Author

you can keep the board state in 18 bits:

  • 9 to track whether a space is occupied
  • 9 to track who is occupying the space

i just thought that was kinda neat. got in my head here: https://lobste.rs/s/4w9ld6/google_interview_question

@justintout
Copy link
Author

turns out the mask is just the win condition shifted, since the "occupied" bits map to the "player" bits cleanly. neat 📸

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment