tictactoe_bot_dynamic.go
                        
                             · 4.8 KiB · Go
                        
                    
                    
                      
                        Orginalformat
                      
                      
                        
                          
                        
                    
                    
                
                
            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()
}
                | 1 | package main | 
| 2 | |
| 3 | import ( | 
| 4 | "fmt" | 
| 5 | "maps" | 
| 6 | "slices" | 
| 7 | "strconv" | 
| 8 | "strings" | 
| 9 | ) | 
| 10 | |
| 11 | const emptyCellName = " " | 
| 12 | |
| 13 | type User struct { | 
| 14 | name string | 
| 15 | isBot bool | 
| 16 | } | 
| 17 | |
| 18 | type Board struct { | 
| 19 | board [][]*User | 
| 20 | winningUser *User | 
| 21 | |
| 22 | combinations [][]**User | 
| 23 | |
| 24 | users []User | 
| 25 | } | 
| 26 | |
| 27 | func (b Board) String() string { | 
| 28 | size := len(b.board) | 
| 29 | rowStrings := make([]string, size+1) | 
| 30 | |
| 31 | columnIndices := make([]string, size) | 
| 32 | for i := 0; i < size; i++ { | 
| 33 | rowIndex := strconv.Itoa(i) | 
| 34 | if i < 10 { | 
| 35 | rowIndex = " " + rowIndex | 
| 36 | } | 
| 37 | columnIndices[i] = rowIndex | 
| 38 | } | 
| 39 | rowStrings[0] = "/" + strings.Join(columnIndices, "") | 
| 40 | |
| 41 | for i, row := range b.board { | 
| 42 | rowIndex := strconv.Itoa(i) | 
| 43 | if i < 10 { | 
| 44 | rowIndex += " " | 
| 45 | } | 
| 46 | |
| 47 | columnStrings := make([]string, size) | 
| 48 | for j, c := range row { | 
| 49 | if c == nil { | 
| 50 | columnStrings[j] = emptyCellName | 
| 51 | } else { | 
| 52 | columnStrings[j] = c.name | 
| 53 | } | 
| 54 | } | 
| 55 | |
| 56 | rowStrings[i+1] = rowIndex + strings.Join(columnStrings, "|") | 
| 57 | } | 
| 58 | |
| 59 | return strings.Join(rowStrings, "\n") | 
| 60 | } | 
| 61 | |
| 62 | func changeCellUserInput(board *[][]*User, user *User) { | 
| 63 | var row int | 
| 64 | for { | 
| 65 | fmt.Print("row: ") | 
| 66 | fmt.Scanln(&row) | 
| 67 | |
| 68 | if 0 <= row && row < len(*board) { | 
| 69 | break | 
| 70 | } | 
| 71 | } | 
| 72 | |
| 73 | var col int | 
| 74 | for { | 
| 75 | fmt.Print("col: ") | 
| 76 | fmt.Scanln(&col) | 
| 77 | |
| 78 | if 0 <= col && col < len(*board) { | 
| 79 | break | 
| 80 | } | 
| 81 | } | 
| 82 | |
| 83 | (*board)[row][col] = user | 
| 84 | } | 
| 85 | |
| 86 | func getWinningCombinations(board *[][]*User) [][]**User { | 
| 87 | // there are 2 * size + 2 winning combinations in total | 
| 88 | winningCombinations := make([][]**User, len(*board)*2+2) | 
| 89 | |
| 90 | // size row wins | size col wins | 
| 91 | for i, _ := range *board { | 
| 92 | row := make([]**User, len(*board)) | 
| 93 | col := make([]**User, len(*board)) | 
| 94 | |
| 95 | for j, _ := range *board { | 
| 96 | row[j] = &(*board)[i][j] | 
| 97 | col[j] = &(*board)[j][i] | 
| 98 | } | 
| 99 | |
| 100 | winningCombinations[i*2] = row | 
| 101 | winningCombinations[i*2+1] = col | 
| 102 | } | 
| 103 | |
| 104 | // 2 vertical wins | 
| 105 | a := make([]**User, len(*board)) | 
| 106 | b := make([]**User, len(*board)) | 
| 107 | for i, _ := range *board { | 
| 108 | a[i] = &(*board)[i][i] | 
| 109 | b[i] = &(*board)[i][len(*board)-i-1] | 
| 110 | } | 
| 111 | winningCombinations[len(*board)*2] = a | 
| 112 | winningCombinations[len(*board)*2+1] = b | 
| 113 | |
| 114 | return winningCombinations | 
| 115 | } | 
| 116 | |
| 117 | type combinationStats struct { | 
| 118 | user *User | 
| 119 | |
| 120 | totalCount int | 
| 121 | userCount int | 
| 122 | |
| 123 | emptyCells []**User | 
| 124 | } | 
| 125 | |
| 126 | func (b *Board) computeCombinationStats(user *User) []combinationStats { | 
| 127 | newCombinations := [][]**User{} | 
| 128 | cs := []combinationStats{} | 
| 129 | |
| 130 | for _, combination := range b.combinations { | 
| 131 | c := combinationStats{} | 
| 132 | allUsersMap := make(map[*User]int) | 
| 133 | |
| 134 | for _, cell := range combination { | 
| 135 | c.totalCount++ | 
| 136 | |
| 137 | if *cell == nil { | 
| 138 | c.emptyCells = append(c.emptyCells, cell) | 
| 139 | } else { | 
| 140 | allUsersMap[*cell]++ | 
| 141 | } | 
| 142 | } | 
| 143 | |
| 144 | allUsers := slices.Collect(maps.Keys(allUsersMap)) | 
| 145 | if len(allUsers) == 1 { | 
| 146 | c.user = allUsers[0] | 
| 147 | c.userCount = allUsersMap[allUsers[0]] | 
| 148 | cs = append(cs, c) | 
| 149 | newCombinations = append(newCombinations, combination) | 
| 150 | |
| 151 | if c.userCount == c.totalCount { | 
| 152 | b.winningUser = slices.Collect(maps.Keys(allUsersMap))[0] | 
| 153 | } | 
| 154 | } else if len(allUsers) == 0 { | 
| 155 | cs = append(cs, c) | 
| 156 | newCombinations = append(newCombinations, combination) | 
| 157 | } | 
| 158 | } | 
| 159 | |
| 160 | b.combinations = newCombinations | 
| 161 | return cs | 
| 162 | } | 
| 163 | |
| 164 | func changeCellBotInput(combinations []combinationStats, user *User) { | 
| 165 | // now count the still possible wins each pointer has | 
| 166 | cellScores := make(map[**User]int) | 
| 167 | for _, c := range combinations { | 
| 168 | score := 0 | 
| 169 | if c.user == nil || c.user == user { | 
| 170 | score = 1 + c.userCount | 
| 171 | } else { | 
| 172 | score = 1 + c.userCount | 
| 173 | } | 
| 174 | |
| 175 | for _, emptyCell := range c.emptyCells { | 
| 176 | cellScores[emptyCell] += score | 
| 177 | } | 
| 178 | } | 
| 179 | |
| 180 | bestCellScore := 0 | 
| 181 | var bestCell **User | 
| 182 | for cell, cellScore := range cellScores { | 
| 183 | if cellScore > bestCellScore { | 
| 184 | bestCellScore = cellScore | 
| 185 | bestCell = cell | 
| 186 | } | 
| 187 | } | 
| 188 | |
| 189 | *bestCell = user | 
| 190 | } | 
| 191 | |
| 192 | func (b Board) Play() { | 
| 193 | turn := 0 | 
| 194 | for { | 
| 195 | currentUser := &b.users[turn%len(b.users)] | 
| 196 | fmt.Println() | 
| 197 | fmt.Println("it's the turn of player " + currentUser.name) | 
| 198 | fmt.Println(b.String()) | 
| 199 | |
| 200 | cs := b.computeCombinationStats(currentUser) | 
| 201 | |
| 202 | if b.winningUser != nil { | 
| 203 | fmt.Println() | 
| 204 | if b.winningUser.isBot { | 
| 205 | fmt.Println("THE BOT " + b.winningUser.name + " WON!!!") | 
| 206 | } else { | 
| 207 | fmt.Println("THE USER " + b.winningUser.name + " WON!!!") | 
| 208 | } | 
| 209 | break | 
| 210 | } | 
| 211 | |
| 212 | if len(b.combinations) == 0 { | 
| 213 | fmt.Println() | 
| 214 | fmt.Println("GAME OVER. NEITHER ONE WON") | 
| 215 | break | 
| 216 | } | 
| 217 | |
| 218 | if currentUser.isBot { | 
| 219 | changeCellBotInput(cs, currentUser) | 
| 220 | } else { | 
| 221 | fmt.Println() | 
| 222 | changeCellUserInput(&b.board, currentUser) | 
| 223 | } | 
| 224 | |
| 225 | turn++ | 
| 226 | } | 
| 227 | } | 
| 228 | |
| 229 | func createBoardGrid(size int) [][]*User { | 
| 230 | board := make([][]*User, size) | 
| 231 | for r := 0; r < size; r++ { | 
| 232 | board[r] = make([]*User, size) | 
| 233 | } | 
| 234 | return board | 
| 235 | } | 
| 236 | |
| 237 | func NewBoard(size int, users []User) Board { | 
| 238 | board := createBoardGrid(size) | 
| 239 | return Board{ | 
| 240 | board: board, | 
| 241 | combinations: getWinningCombinations(&board), | 
| 242 | users: users, | 
| 243 | } | 
| 244 | } | 
| 245 | |
| 246 | func main() { | 
| 247 | board := NewBoard(3, []User{ | 
| 248 | {name: "X", isBot: true}, | 
| 249 | {name: "O", isBot: false}, | 
| 250 | }) | 
| 251 | board.Play() | 
| 252 | } | 
| 253 |