If I formatted my question wrong, feel free to notify me or update, this is my first question on this subforum
This is literally my first ever Go project, decided to make a Tic Tac Toe (ironically I came up with that idea myself, turns out a lot of people use this as a beginner project heheh), requiring me to use User input and some slice/array/struc.
I would like some feedback :) Not perse the logic (like that there might be a more efficient way to check a match), but more the usage of struct, funcs, pointers, etc. Specific Go related feedback :)
If it matters I'm not new to programming, just expanding my skills from the web (Advanced PHP). I'm not really used to a program that keeps running until we hit an exit somewhere. I'm getting used to the super short var names, I'm not sure whether that's something I want to be more strict in, but time will tell.
I had some difficulty with returning matches in a proper/useful way.
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func getInputAsInt(r *bufio.Reader) int {
text, _ := r.ReadString('\n')
text = strings.Replace(text, "\n", "", -1)
textAsInt, _ := strconv.Atoi(text)
return textAsInt
}
func main() {
b := createNewBoard()
reader := bufio.NewReader(os.Stdin)
active := 1 // current player, 1=X, 2=O
for {
fmt.Print("\n\n")
b.print()
if active == 1 {
fmt.Print("X -> ")
} else {
fmt.Print("O -> ")
}
textAsInt := getInputAsInt(reader)
err := b.updateBoard(textAsInt, active)
if err != nil {
fmt.Println(err)
} else {
msg := hasAWinner(b)
if msg != "" {
b.print()
fmt.Println(msg)
os.Exit(0)
}
if active == 1 {
active = 2
} else {
active = 1
}
}
}
}
package main
import (
"errors"
"fmt"
"strings"
)
type board [3][3]int
func createNewBoard() board {
return board{}
}
// Convert the data to a human format
func (b board) print() {
var boardRows []string
for _, row := range b {
// convert slice of ints to a joined string
boardRows = append(boardRows, strings.Trim(strings.Join(strings.Fields(fmt.Sprint(row)), "|"), "[]"))
}
textBoard := strings.Join(boardRows, "\n---┼---┼---\n")
textBoard = strings.Replace(textBoard, "0", " · ", -1)
textBoard = strings.Replace(textBoard, "1", " X ", -1)
textBoard = strings.Replace(textBoard, "2", " O ", -1)
fmt.Println(textBoard)
}
// Takes the input and tries to update the board
func (b *board) updateBoard(pos int, char int) error {
keymap := [9][2]int{
{2, 0}, // 1
{2, 1}, // 2
{2, 2}, // 3
{1, 0}, // 4
{1, 1}, // 5
{1, 2}, // 6
{0, 0}, // 7
{0, 1}, // 8
{0, 2}, // 9
}
mp := keymap[pos-1] // Mapped Pos
square := b[mp[0]][mp[1]]
if square != 0 {
return errors.New("this square is already chosen dumdum, try again")
}
b[mp[0]][mp[1]] = char
return nil
}
package main
import "fmt"
// Tests whether there is a possible win
func hasAWinner(b board) string {
hasMatch, winner, pos := hasAnyHorizontalMatch(b)
if hasMatch {
return fmt.Sprintf("%v wins! (horizontal row %d)", winner, pos)
}
hasMatch, winner, pos = hasAnyVerticalMatch(b)
if hasMatch {
return fmt.Sprintf("%v wins! (vertical row %d)", winner, pos)
}
hasMatch, winner = hasAnyDiagonalMatch(b)
if hasMatch {
return fmt.Sprintf(" wins! (diagonal), winner")
}
return ""
}
func hasAnyHorizontalMatch(b board) (bool, int, int) {
for index, row := range b {
if hasHorizontalMatch(row, 1) {
return true, 1, index + 1
}
if hasHorizontalMatch(row, 2) {
return true, 2, index + 1
}
}
return false, -1, -1
}
func hasAnyVerticalMatch(b board) (bool, int, int) {
i := 0
for i < len(b[0]) {
if hasVerticalMatch(b, i, 1) {
return true, 1, i + 1
}
if hasVerticalMatch(b, i, 2) {
return true, 2, i + 1
}
i++
}
return false, -1, -1
}
func hasAnyDiagonalMatch(b board) (bool, int) {
if hasDiagonalMatch(b, 1) {
return true, 1
}
if hasDiagonalMatch(b, 2) {
return true, 2
}
return false, -1
}
func hasHorizontalMatch(line [3]int, char int) bool {
return line[0] == char && line[1] == char && line[2] == char
}
func hasVerticalMatch(b board, vline int, char int) bool {
return b[0][vline] == char && b[1][vline] == char && b[2][vline] == char
}
func hasDiagonalMatch(b board, char int) bool {
return (b[0][0] == char && b[1][1] == char && b[2][2] == char) || (b[0][2] == char && b[1][1] == char && b[2][0] == char)
}
Questions:
- I have
char int
in several places, short for the character, the currently active player. I wanted to make that more obvious by making atype player int
which enables me to usefunc(p player)
instead. Shorter var, more obvious code. However, then the board starts erroring because it is a grid of INT, not of the player. Is there something nice about that? I'm running into the type-strictness here. I could make it a grid of player, but Im not sure how to create a new player with value 1 or 2.
char int
, why not usetype Player int
, and change your maps to bemap[Player]T{}
? You can use user-defined types as map keys in this case \$\endgroup\$