Created
March 23, 2022 22:27
-
-
Save friedenberg/0e9f4cf0bfbeb782c7604bdb430a8590 to your computer and use it in GitHub Desktop.
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 ( | |
"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 | |
} |
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 | |
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 | |
} |
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" | |
) | |
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("-------------") | |
} | |
} |
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
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 | |
) |
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
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= |
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 ( | |
"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