Skip to content

Instantly share code, notes, and snippets.

@friedenberg
Created March 23, 2022 22:27
Show Gist options
  • Save friedenberg/0e9f4cf0bfbeb782c7604bdb430a8590 to your computer and use it in GitHub Desktop.
Save friedenberg/0e9f4cf0bfbeb782c7604bdb430a8590 to your computer and use it in GitHub Desktop.
package main
import (
"io"
"os"
"strings"
)
type ControlCharacter int
const (
ControlCharacterUnknown = ControlCharacter(iota)
ControlCharacterUp
ControlCharacterDown
ControlCharacterRight
ControlCharacterLeft
ControlCharacterSpace
ControlCharacterEnter
)
var ControlCharacterByteMap = map[string]ControlCharacter{
"\x1b[A": ControlCharacterUp,
"\x1b[B": ControlCharacterDown,
"\x1b[C": ControlCharacterRight,
"\x1b[D": ControlCharacterLeft,
"\n": ControlCharacterEnter,
" ": ControlCharacterEnter,
}
func (k ControlCharacter) IsMovement() bool {
return !(k == ControlCharacterSpace || k == ControlCharacterEnter)
}
type ControlCharacterReader struct {
io.Reader
}
func (r ControlCharacterReader) ReadControlCharacter() (out ControlCharacter) {
sb := &strings.Builder{}
for {
b := make([]byte, 1)
_, err := os.Stdin.Read(b)
if err == io.EOF {
break
}
if b[0] == '\x1b' || b[0] == ' ' || b[0] == '\n' {
sb.Reset()
}
for _, b1 := range b {
sb.WriteByte(b1)
}
if k, ok := ControlCharacterByteMap[sb.String()]; ok {
out = k
break
}
}
return
}
package main
type Coordinate struct {
X, Y int
}
func (a Coordinate) NextCoordinate(chars ...ControlCharacter) (b Coordinate) {
b = a
for _, char := range chars {
switch char {
case ControlCharacterUp:
b.Y -= 1
case ControlCharacterDown:
b.Y += 1
case ControlCharacterLeft:
b.X -= 1
case ControlCharacterRight:
b.X += 1
}
if b.X > 2 {
b.X = 2
} else if b.X < 0 {
b.X = 0
}
if b.Y > 2 {
b.Y = 2
} else if b.Y < 0 {
b.Y = 0
}
}
return
}
func (a Coordinate) GetWinningSets() (wins [][3]Coordinate) {
wins = make([][3]Coordinate, 0)
if a.Y == 0 {
wins = append(
wins,
[3]Coordinate{
a,
a.NextCoordinate(ControlCharacterDown),
a.NextCoordinate(ControlCharacterDown, ControlCharacterDown),
},
)
}
if a.X == 0 {
wins = append(
wins,
[3]Coordinate{
a,
a.NextCoordinate(ControlCharacterRight),
a.NextCoordinate(ControlCharacterRight, ControlCharacterRight),
},
)
}
if a.X == 0 && a.Y == 0 {
wins = append(
wins,
[3]Coordinate{
a,
a.NextCoordinate(ControlCharacterRight, ControlCharacterDown),
a.NextCoordinate(
ControlCharacterRight,
ControlCharacterDown,
ControlCharacterRight,
ControlCharacterDown,
),
},
)
}
if a.X == 2 && a.Y == 0 {
wins = append(
wins,
[3]Coordinate{
a,
a.NextCoordinate(ControlCharacterLeft, ControlCharacterDown),
a.NextCoordinate(
ControlCharacterLeft,
ControlCharacterDown,
ControlCharacterLeft,
ControlCharacterDown,
),
},
)
}
return
}
package main
import (
"fmt"
)
type State int
const (
StateEmpty = State(iota)
StateX
StateO
StateGameOver
)
type Space struct {
State
winningSpace bool
}
type Board struct {
currentState State
spaces [9]Space
selectedSpace *Coordinate
usedSpaceCount int
winner State
}
func (s Space) String() string {
switch s.State {
case StateX:
return "X"
case StateO:
return "O"
}
return " "
}
func (g Board) Winner() (s State, winningCoordinates [3]Coordinate) {
OUTER:
for y := 0; y < 3; y++ {
for x := 0; x < 3; x++ {
c := Coordinate{X: x, Y: y}
winningSets := c.GetWinningSets()
for _, cw := range winningSets {
s = g.checkWinner(cw)
if s != StateEmpty {
winningCoordinates = cw
break OUTER
}
}
}
}
return
}
func (g Board) checkWinner(set [3]Coordinate) (winner State) {
spaces := [3]State{
g.Space(set[0]).State,
g.Space(set[1]).State,
g.Space(set[2]).State,
}
winner = spaces[0]
for _, s := range spaces {
if winner != s {
winner = StateEmpty
break
}
}
return
}
func (g *Board) NextState() State {
winner, winningCoordinates := g.Winner()
g.winner = winner
if g.usedSpaceCount == 9 || g.winner != StateEmpty {
if g.winner != StateEmpty {
for _, c := range winningCoordinates {
s := g.Space(c)
s.winningSpace = true
g.setSpace(c, s)
}
}
return StateGameOver
}
switch g.currentState {
case StateX:
return StateO
default:
return StateX
}
}
func (g Board) Space(c Coordinate) Space {
return g.spaces[(c.Y*3)+c.X]
}
func (g *Board) SetSpace(c Coordinate, s State) {
space := g.Space(c)
space.State = s
g.setSpace(c, space)
}
func (g *Board) setSpace(c Coordinate, s Space) {
g.spaces[(c.Y*3)+c.X] = s
if s.State != StateEmpty {
g.usedSpaceCount += 1
}
}
func (g Board) Print() {
//clear screen
fmt.Printf("\033[2J")
switch g.currentState {
case StateX:
fmt.Println("player 1:")
fmt.Println("place an X using the arrow keys")
fmt.Println("then hit space or enter to end your turn")
case StateO:
fmt.Println("player 2:")
fmt.Println("place an O using the arrow keys")
fmt.Println("then hit space or enter to end your turn")
case StateGameOver:
fmt.Println("game over!")
switch g.winner {
case StateX:
fmt.Println("player 1 wins")
case StateO:
fmt.Println("player 2 wins")
default:
fmt.Println("nobody wins")
}
}
fmt.Println("-------------")
for i := 0; i < 3; i++ {
for j := 0; j < 3; j++ {
c := Coordinate{X: j, Y: i}
space := g.Space(c)
if space.winningSpace {
//white text green background
fmt.Printf("| \033[37m\033[42m%s\033[0m ", space)
} else if g.selectedSpace != nil && c == *g.selectedSpace {
//red background
fmt.Printf("| \033[101m%s\033[0m ", space)
} else {
fmt.Printf("| %s ", space)
}
}
fmt.Print("|")
fmt.Println()
fmt.Println("-------------")
}
}
module github.com/friedenberg/TicTacWhoa
go 1.16
require (
github.com/pkg/term v1.1.0 // indirect
golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8 // indirect
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect
)
github.com/pkg/term v1.1.0 h1:xIAAdCMh3QIAy+5FrE8Ad8XoDhEU4ufwbaSozViP9kk=
github.com/pkg/term v1.1.0/go.mod h1:E25nymQcrSllhX42Ok8MRm1+hyBdHY0dCeiKZ9jpNGw=
golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8 h1:OH54vjqzRWmbJ62fjuhxy7AxFFgoHN0/DPc/UrL8cAs=
golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
package main
import (
"os"
"github.com/pkg/term"
)
func main() {
t, err := term.Open(os.Stdin.Name(), term.CBreakMode)
if err != nil {
panic(err)
}
defer t.Restore()
board := Board{
currentState: StateX,
}
board.Print()
keyReader := ControlCharacterReader{Reader: os.Stdin}
c := Coordinate{}
for {
if board.currentState == StateGameOver {
break
}
key := keyReader.ReadControlCharacter()
if key.IsMovement() {
c = c.NextCoordinate(key)
board.selectedSpace = &c
} else {
board.selectedSpace = nil
board.SetSpace(c, board.currentState)
board.currentState = board.NextState()
}
board.Print()
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment