Last active 1750260662

Hazel's Avatar Hazel revised this gist 1750260662. Go to revision

1 file changed, 252 insertions

tictactoe_bot_dynamic.go(file created)

@@ -0,0 +1,252 @@
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 + }
Newer Older