Last active
April 8, 2021 04:00
-
-
Save justintout/736b51e6e5dd655c87d91cbab6773c5e to your computer and use it in GitHub Desktop.
2 player Tic-Tac-Toe in Go
This file contains 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 ( | |
"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) | |
} | |
} | |
} |
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
you can keep the board state in 18 bits:
i just thought that was kinda neat. got in my head here: https://lobste.rs/s/4w9ld6/google_interview_question