add: new tictactoe game

add: testing to tictactoe

rename: tictactoe

add: tictactoe steps

refactor: tictactoe const names

refactor: tictactoe loop

add: tictactoe bad switch example (fallthrough)

refactor: tictactoe loop skin

remove: tictactoe changable skin

refactor: tictactoe all

remove: tictactoe unnecessary base dir

add: tictactoe slices

add: tictactoe slices

remove: tictactoe fallthrough

rename: tictactoe slices 10 -> 09

update: loops skin tictactoe

add: tictactoe randomization

add: tictactoe infinite loop and labeled break

refactor: tictactoe rand and infinite loop

add: tictactoe buggy winning algo

add: tictactoe more tests

rename: tictactoe wrongPlay to wrongMove

add: tictactoe even more tests

fix: tictactoe

rename: tictactoe waitForInput to wait

add: tictactoe os.args gameSpeed

remove: tictactoe unnecessary files

rename: tictactoe game messages

refactor: tictactoe main loop

add: types and arrays
This commit is contained in:
Inanc Gumus
2019-07-27 18:16:17 +03:00
parent e191567e1f
commit eb8f9987a8
97 changed files with 3457 additions and 3 deletions

View File

@@ -0,0 +1,152 @@
package main
import (
"strings"
)
type state int
const (
maxTurn = 9
wrongPosition = -2
statePlaying state = iota
stateWon
stateTie
stateAlreadyPlayed
stateWrongPosition
)
type game struct {
board [][]string
turn int
player string
skin // embed the skin
logger // embed the logger
}
func newGame(s skin, l logger) *game {
return &game{
player: s.mark1,
board: [][]string{
{s.empty, s.empty, s.empty},
{s.empty, s.empty, s.empty},
{s.empty, s.empty, s.empty},
},
skin: s,
logger: l,
}
}
func (g *game) play(pos int) state {
if st := g.move(pos); st != statePlaying {
return st
}
g.turn++ // increment the turn
// first check the winner then check the tie
// or the last mover won't win
switch {
case g.won():
return stateWon
case g.turn == maxTurn:
return stateTie
}
g.changePlayer()
return statePlaying
}
func (g *game) move(pos int) state {
row, col := position(pos)
if row+col == wrongPosition {
return stateWrongPosition
}
if g.board[row][col] != g.empty {
return stateAlreadyPlayed
}
// put the player's mark on the board
g.board[row][col] = g.player
return statePlaying
}
// we can detect the winning state just by comparing the strings
// because, the game board is a bunch of strings
func (g *game) won() (won bool) {
for _, m := range [2]string{g.mark1, g.mark2} {
b, mmm := g.board, strings.Repeat(m, 3)
won = /* horizontals */
strings.Join(b[0], "") == mmm ||
strings.Join(b[1], "") == mmm ||
strings.Join(b[2], "") == mmm ||
/* verticals */
b[0][0]+b[1][0]+b[2][0] == mmm ||
b[0][1]+b[1][1]+b[2][1] == mmm ||
b[0][2]+b[1][2]+b[2][2] == mmm ||
/* diagonals */
b[0][0]+b[1][1]+b[2][2] == mmm ||
b[0][2]+b[1][1]+b[2][0] == mmm
if won {
return true
}
}
return false
}
// this method should have a pointer receiver
// because, it changes the game value
func (g *game) changePlayer() {
if g.player == g.mark1 {
g.player = g.mark2
} else {
g.player = g.mark1
}
}
func (g *game) print() {
g.Println()
g.Println(g.header)
for i, line := range g.board {
g.Print(g.separator)
for _, m := range line {
g.Printf("%2s%s", m, g.separator)
}
if i+1 != len(g.board) {
g.Printf("\n%s\n", g.middle)
}
}
g.Printf("\n%s\n", g.footer)
}
// this function doesn't depend on the game state
// so, make it a function instead of a method
func position(pos int) (row, col int) {
switch {
case pos >= 1 && pos <= 3:
row = 0
case pos >= 4 && pos <= 6:
row = 1
case pos >= 7 && pos <= 9:
row = 2
default:
return -1, -1
}
return row, pos - row*3 - 1
}

View File

@@ -0,0 +1,19 @@
package main
type logger struct {
print func(...interface{}) (int, error)
printf func(fmt string, args ...interface{}) (int, error)
println func(...interface{}) (int, error)
}
func (l logger) Print(args ...interface{}) {
l.print(args...)
}
func (l logger) Printf(fmt string, args ...interface{}) {
l.printf(fmt, args...)
}
func (l logger) Println(args ...interface{}) {
l.println(args...)
}

View File

@@ -0,0 +1,93 @@
package main
import (
"bufio"
"fmt"
"os"
"strconv"
c "github.com/fatih/color"
rainbow "github.com/guineveresaenger/golang-rainbow"
)
// TODO: move the main logic into a helper
func main() {
const banner = `
~~~~~~~~~~~~~~~~
TIC~TAC~TOE
~~~~~~~~~~~~~~~~`
in := bufio.NewScanner(os.Stdin)
sk := selectSkin(in)
lg := logger{
print: fmt.Print,
printf: fmt.Printf,
println: fmt.Println,
}
for {
g := newGame(sk, lg)
game:
for {
rainbow.Rainbow(banner, 3)
g.print()
fmt.Printf(c.CyanString("\n%s [1-9]: ", g.player))
if !in.Scan() {
break
}
pos, _ := strconv.Atoi(in.Text())
switch st := g.play(pos); st {
case stateAlreadyPlayed, stateWrongPosition:
announce(g, st)
continue
case stateWon, stateTie:
announce(g, st)
break game
}
g.Print("\033[2J")
}
fmt.Print(c.MagentaString("One more game? [y/n]: "))
if in.Scan(); in.Text() != "y" {
fmt.Println("OK, bye!")
break
}
}
}
func announce(g *game, st state) {
red := c.New(c.FgRed, c.Bold)
green := c.New(c.BgBlack, c.FgGreen, c.Bold)
switch st {
case stateAlreadyPlayed, stateWrongPosition:
red.Printf("\n>>> You can't play there!\n")
case stateWon:
g.print()
green.Printf("\nWINNER: %s\n", g.player)
case stateTie:
g.print()
green.Printf("\nTIE!\n")
}
}
func selectSkin(in *bufio.Scanner) skin {
fmt.Println(c.MagentaString("Our finest selection of skins:"))
for name := range skins {
fmt.Printf("- %s\n", name)
}
fmt.Print(c.GreenString("\nEnter the name of the skin: "))
in.Scan()
if sk, ok := skins[in.Text()]; ok {
return sk
}
return defaultSkin
}

View File

@@ -0,0 +1,5 @@
https://github.com/mattn/go-runewidth
https://github.com/olekukonko/tablewriter
https://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c

View File

@@ -0,0 +1,91 @@
package main
import "github.com/fatih/color"
// skin options :-)
type skin struct {
empty, mark1, mark2 string
header, middle, footer, separator string
unicode bool
}
var skins = map[string]skin{
"colorful": colorfulSkin,
"poseidon": poseidonSkin,
"statues": statuesSkin,
"aliens": aliensSkin,
"snow": snowSkin,
"monkeys": monkeysSkin,
}
var defaultSkin = skin{
empty: " ",
mark1: " X ",
mark2: " O ",
header: "┌───┬───┬───┐",
middle: "├───┼───┼───┤",
footer: "└───┴───┴───┘",
separator: "│",
}
var colorfulSkin = skin{
empty: " ",
mark1: color.CyanString(" X "),
mark2: color.HiMagentaString(" O "),
header: color.HiBlueString("┌───┬───┬───┐"),
middle: color.HiBlueString("├───┼───┼───┤"),
footer: color.HiBlueString("└───┴───┴───┘"),
separator: color.BlueString("│"),
}
var poseidonSkin = skin{
empty: "❓ ",
mark1: "🔱 ",
mark2: "⚓️ ",
header: "●————●————●————●",
middle: "●————●————●————●",
footer: "●————●————●————●",
separator: "⎮ ",
}
var statuesSkin = skin{
empty: "❓ ",
mark1: "🗿 ",
mark2: "🗽 ",
header: "┌────┬────┬────┐",
middle: "├────┼────┼────┤",
footer: "└────┴────┴────┘",
separator: "│ ",
}
var aliensSkin = skin{
empty: "❓ ",
mark1: "👽 ",
mark2: "👾 ",
header: "┌────┬────┬────┐",
middle: "├────┼────┼────┤",
footer: "└────┴────┴────┘",
separator: "│ ",
}
var snowSkin = skin{
empty: "❓ ",
mark1: "⛄ ",
mark2: "❄️ ",
header: "╔════╦════╦════╗",
middle: "╠════╬════╬════╣",
footer: "╚════╩════╩════╝",
separator: "║ ",
}
var monkeysSkin = skin{
empty: "🍌 ",
mark1: "🙈 ",
mark2: "🙉 ",
header: "┌────┬────┬────┐",
middle: "├────┼────┼────┤",
footer: "└────┴────┴────┘",
separator: "│ ",
}