Last active 1750260662

tictactoe_bot_dynamic.go Raw
1package main
2
3import (
4 "fmt"
5 "maps"
6 "slices"
7 "strconv"
8 "strings"
9)
10
11const emptyCellName = " "
12
13type User struct {
14 name string
15 isBot bool
16}
17
18type Board struct {
19 board [][]*User
20 winningUser *User
21
22 combinations [][]**User
23
24 users []User
25}
26
27func (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
62func 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
86func 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
117type combinationStats struct {
118 user *User
119
120 totalCount int
121 userCount int
122
123 emptyCells []**User
124}
125
126func (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
164func 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
192func (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
229func 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
237func 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
246func main() {
247 board := NewBoard(3, []User{
248 {name: "X", isBot: true},
249 {name: "O", isBot: false},
250 })
251 board.Play()
252}
253