package main import ( "fmt" "maps" "slices" "strconv" "strings" ) const emptyCellName = " " type User struct { name string isBot bool } type Board struct { board [][]*User winningUser *User combinations [][]**User users []User } func (b Board) String() string { size := len(b.board) rowStrings := make([]string, size+1) columnIndices := make([]string, size) for i := 0; i < size; i++ { rowIndex := strconv.Itoa(i) if i < 10 { rowIndex = " " + rowIndex } columnIndices[i] = rowIndex } rowStrings[0] = "/" + strings.Join(columnIndices, "") for i, row := range b.board { rowIndex := strconv.Itoa(i) if i < 10 { rowIndex += " " } columnStrings := make([]string, size) for j, c := range row { if c == nil { columnStrings[j] = emptyCellName } else { columnStrings[j] = c.name } } rowStrings[i+1] = rowIndex + strings.Join(columnStrings, "|") } return strings.Join(rowStrings, "\n") } func changeCellUserInput(board *[][]*User, user *User) { var row int for { fmt.Print("row: ") fmt.Scanln(&row) if 0 <= row && row < len(*board) { break } } var col int for { fmt.Print("col: ") fmt.Scanln(&col) if 0 <= col && col < len(*board) { break } } (*board)[row][col] = user } func getWinningCombinations(board *[][]*User) [][]**User { // there are 2 * size + 2 winning combinations in total winningCombinations := make([][]**User, len(*board)*2+2) // size row wins | size col wins for i, _ := range *board { row := make([]**User, len(*board)) col := make([]**User, len(*board)) for j, _ := range *board { row[j] = &(*board)[i][j] col[j] = &(*board)[j][i] } winningCombinations[i*2] = row winningCombinations[i*2+1] = col } // 2 vertical wins a := make([]**User, len(*board)) b := make([]**User, len(*board)) for i, _ := range *board { a[i] = &(*board)[i][i] b[i] = &(*board)[i][len(*board)-i-1] } winningCombinations[len(*board)*2] = a winningCombinations[len(*board)*2+1] = b return winningCombinations } type combinationStats struct { user *User totalCount int userCount int emptyCells []**User } func (b *Board) computeCombinationStats(user *User) []combinationStats { newCombinations := [][]**User{} cs := []combinationStats{} for _, combination := range b.combinations { c := combinationStats{} allUsersMap := make(map[*User]int) for _, cell := range combination { c.totalCount++ if *cell == nil { c.emptyCells = append(c.emptyCells, cell) } else { allUsersMap[*cell]++ } } allUsers := slices.Collect(maps.Keys(allUsersMap)) if len(allUsers) == 1 { c.user = allUsers[0] c.userCount = allUsersMap[allUsers[0]] cs = append(cs, c) newCombinations = append(newCombinations, combination) if c.userCount == c.totalCount { b.winningUser = slices.Collect(maps.Keys(allUsersMap))[0] } } else if len(allUsers) == 0 { cs = append(cs, c) newCombinations = append(newCombinations, combination) } } b.combinations = newCombinations return cs } func changeCellBotInput(combinations []combinationStats, user *User) { // now count the still possible wins each pointer has cellScores := make(map[**User]int) for _, c := range combinations { score := 0 if c.user == nil || c.user == user { score = 1 + c.userCount } else { score = 1 + c.userCount } for _, emptyCell := range c.emptyCells { cellScores[emptyCell] += score } } bestCellScore := 0 var bestCell **User for cell, cellScore := range cellScores { if cellScore > bestCellScore { bestCellScore = cellScore bestCell = cell } } *bestCell = user } func (b Board) Play() { turn := 0 for { currentUser := &b.users[turn%len(b.users)] fmt.Println() fmt.Println("it's the turn of player " + currentUser.name) fmt.Println(b.String()) cs := b.computeCombinationStats(currentUser) if b.winningUser != nil { fmt.Println() if b.winningUser.isBot { fmt.Println("THE BOT " + b.winningUser.name + " WON!!!") } else { fmt.Println("THE USER " + b.winningUser.name + " WON!!!") } break } if len(b.combinations) == 0 { fmt.Println() fmt.Println("GAME OVER. NEITHER ONE WON") break } if currentUser.isBot { changeCellBotInput(cs, currentUser) } else { fmt.Println() changeCellUserInput(&b.board, currentUser) } turn++ } } func createBoardGrid(size int) [][]*User { board := make([][]*User, size) for r := 0; r < size; r++ { board[r] = make([]*User, size) } return board } func NewBoard(size int, users []User) Board { board := createBoardGrid(size) return Board{ board: board, combinations: getWinningCombinations(&board), users: users, } } func main() { board := NewBoard(3, []User{ {name: "X", isBot: true}, {name: "O", isBot: false}, }) board.Play() }