package main import ( "fmt" "strconv" "strings" ) const ( cellEmpty = " " cellUser = "X" cellBot = "O" ) func boardToString(board [3][3]string) string { rowStrings := make([]string, len(board)+1) for i, row := range board { rowStrings[i+1] = strconv.Itoa(i) + " " + strings.Join(row[:], "|") } columnIndices := make([]string, 3) for i := 0; i < 3; i++ { columnIndices[i] = strconv.Itoa(i) } rowStrings[0] = "/ " + strings.Join(columnIndices, " ") return strings.Join(rowStrings, "\n") } func changeCellUserInput(board *[3][3]string) { var row *[3]string for { var rowIndex int fmt.Print("row: ") fmt.Scanln(&rowIndex) if 0 <= rowIndex && rowIndex < len(board) { row = &board[rowIndex] break } else { fmt.Println("The value needs to be in the range of the board rows.") continue } } for { var colIndex int fmt.Print("col: ") fmt.Scanln(&colIndex) if 0 <= colIndex && colIndex < len(row) { if row[colIndex] != " " { fmt.Println("This cell is already occupied.") defer changeCellUserInput(board) break } row[colIndex] = cellUser break } else { fmt.Println("The value needs to be in the range of the board columns.") continue } } } func getWinningCombinations(board *[3][3]string) [][3]*string { // there are 8 winning combinations in total winningCombinations := make([][3]*string, 8) // 3 row wins | 3 col wins for i := 0; i < 3; i++ { row := [3]*string{} col := [3]*string{} for j := 0; j < 3; j++ { row[j] = &board[i][j] col[j] = &board[j][i] } winningCombinations[i*2] = row winningCombinations[i*2+1] = col } // 2 vertical wins for i := 0; i < 3; i++ { winningCombinations[6][i] = &board[i][i] winningCombinations[7][i] = &board[i][2-i] } return winningCombinations } type combinationStat struct { totalCells int emptyCells int userCells int botCells int emptyCellRefs []*string } func calculateCombinationStats(combination [3]*string) combinationStat { c := combinationStat{} for _, cell := range combination { c.totalCells++ switch *cell { case cellEmpty: c.emptyCells++ c.emptyCellRefs = append(c.emptyCellRefs, cell) case cellBot: c.botCells++ case cellUser: c.userCells++ } } return c } func changeCellBotInput(board *[3][3]string, winningCombinations [][3]*string) { combinationStats := make([]combinationStat, len(winningCombinations)) for i, combination := range winningCombinations { combinationStats[i] = calculateCombinationStats(combination) } // check if bot could win on the next move for _, c := range combinationStats { if c.emptyCells == 1 && c.botCells == 2 { *c.emptyCellRefs[0] = cellBot return } } // check if user could win on the next move and prevent it for _, c := range combinationStats { if c.emptyCells == 1 && c.userCells == 2 { *c.emptyCellRefs[0] = cellBot return } } // now count the still possible wins each pointer has cellScores := make(map[*string]int) for _, c := range combinationStats { if c.userCells <= 0 { score := 1 + (c.totalCells - c.emptyCells) for _, emptyCell := range c.emptyCellRefs { cellScores[emptyCell] += score } } else { // initialize empty cells by adding nothing in case there is not further win option for _, emptyCell := range c.emptyCellRefs { cellScores[emptyCell] += 0 } } } bestCellScore := 0 var bestCell *string for cell, cellScore := range cellScores { if cellScore > bestCellScore { bestCellScore = cellScore bestCell = cell } } *bestCell = cellBot } type boardData struct { isWin bool isPat bool winningUser string } func analyzeWin(winningCombinations [][3]*string) boardData { b := boardData{} winnable := false for _, combination := range winningCombinations { c := calculateCombinationStats(combination) if c.botCells == 3 { b.isWin = true b.winningUser = cellBot return b } if c.userCells == 3 { b.isWin = true b.winningUser = cellUser return b } if c.botCells == 0 || c.userCells == 0 { winnable = true } } if !winnable { b.isPat = true } return b } func main() { board := [3][3]string{ {cellEmpty, cellEmpty, cellEmpty}, {cellEmpty, cellEmpty, cellEmpty}, {cellEmpty, cellEmpty, cellEmpty}, } winningCombinations := getWinningCombinations(&board) currentUser := 0 for { fmt.Println() fmt.Println(boardToString(board)) bd := analyzeWin(winningCombinations) if bd.isWin { fmt.Println() switch bd.winningUser { case cellBot: fmt.Println("THE BOT WINS!!") case cellUser: fmt.Println("CONGRATULATION, YOU WIN!!") } break } if bd.isPat { fmt.Println() fmt.Println("GAME OVER. NEITHER ONE WON") break } if currentUser == 0 { fmt.Println() fmt.Println("it's your turn human") changeCellUserInput(&board, cellUser) } else { changeCellBotInput(&board, winningCombinations) } currentUser = (currentUser + 1) % 2 } }