Last active 1750241564

tictactoe_bot.go Raw
1package main
2
3import (
4 "fmt"
5 "strconv"
6 "strings"
7)
8
9const (
10 cellEmpty = " "
11 cellUser = "X"
12 cellBot = "O"
13)
14
15func boardToString(board [3][3]string) string {
16 rowStrings := make([]string, len(board)+1)
17
18 for i, row := range board {
19 rowStrings[i+1] = strconv.Itoa(i) + " " + strings.Join(row[:], "|")
20 }
21
22 columnIndices := make([]string, 3)
23 for i := 0; i < 3; i++ {
24 columnIndices[i] = strconv.Itoa(i)
25 }
26 rowStrings[0] = "/ " + strings.Join(columnIndices, " ")
27
28 return strings.Join(rowStrings, "\n")
29}
30
31func changeCellUserInput(board *[3][3]string) {
32 var row *[3]string
33
34 for {
35 var rowIndex int
36 fmt.Print("row: ")
37 fmt.Scanln(&rowIndex)
38
39 if 0 <= rowIndex && rowIndex < len(board) {
40 row = &board[rowIndex]
41 break
42 } else {
43 fmt.Println("The value needs to be in the range of the board rows.")
44 continue
45 }
46 }
47
48 for {
49 var colIndex int
50 fmt.Print("col: ")
51 fmt.Scanln(&colIndex)
52
53 if 0 <= colIndex && colIndex < len(row) {
54 if row[colIndex] != " " {
55 fmt.Println("This cell is already occupied.")
56 defer changeCellUserInput(board)
57 break
58 }
59
60 row[colIndex] = cellUser
61 break
62 } else {
63 fmt.Println("The value needs to be in the range of the board columns.")
64 continue
65 }
66 }
67}
68
69func getWinningCombinations(board *[3][3]string) [][3]*string {
70 // there are 8 winning combinations in total
71 winningCombinations := make([][3]*string, 8)
72
73 // 3 row wins | 3 col wins
74 for i := 0; i < 3; i++ {
75 row := [3]*string{}
76 col := [3]*string{}
77
78 for j := 0; j < 3; j++ {
79 row[j] = &board[i][j]
80 col[j] = &board[j][i]
81 }
82
83 winningCombinations[i*2] = row
84 winningCombinations[i*2+1] = col
85 }
86
87 // 2 vertical wins
88 for i := 0; i < 3; i++ {
89 winningCombinations[6][i] = &board[i][i]
90 winningCombinations[7][i] = &board[i][2-i]
91 }
92
93 return winningCombinations
94}
95
96type combinationStat struct {
97 totalCells int
98 emptyCells int
99 userCells int
100 botCells int
101
102 emptyCellRefs []*string
103}
104
105func calculateCombinationStats(combination [3]*string) combinationStat {
106 c := combinationStat{}
107
108 for _, cell := range combination {
109 c.totalCells++
110 switch *cell {
111 case cellEmpty:
112 c.emptyCells++
113 c.emptyCellRefs = append(c.emptyCellRefs, cell)
114 case cellBot:
115 c.botCells++
116 case cellUser:
117 c.userCells++
118 }
119 }
120
121 return c
122}
123
124func changeCellBotInput(board *[3][3]string, winningCombinations [][3]*string) {
125 combinationStats := make([]combinationStat, len(winningCombinations))
126 for i, combination := range winningCombinations {
127 combinationStats[i] = calculateCombinationStats(combination)
128 }
129
130 // check if bot could win on the next move
131 for _, c := range combinationStats {
132 if c.emptyCells == 1 && c.botCells == 2 {
133 *c.emptyCellRefs[0] = cellBot
134 return
135 }
136 }
137
138 // check if user could win on the next move and prevent it
139 for _, c := range combinationStats {
140 if c.emptyCells == 1 && c.userCells == 2 {
141 *c.emptyCellRefs[0] = cellBot
142 return
143 }
144 }
145
146 // now count the still possible wins each pointer has
147 cellScores := make(map[*string]int)
148 for _, c := range combinationStats {
149 if c.userCells <= 0 {
150 score := 1 + (c.totalCells - c.emptyCells)
151 for _, emptyCell := range c.emptyCellRefs {
152 cellScores[emptyCell] += score
153 }
154 } else {
155 // initialize empty cells by adding nothing in case there is not further win option
156 for _, emptyCell := range c.emptyCellRefs {
157 cellScores[emptyCell] += 0
158 }
159 }
160 }
161
162 bestCellScore := 0
163 var bestCell *string
164 for cell, cellScore := range cellScores {
165 if cellScore > bestCellScore {
166 bestCellScore = cellScore
167 bestCell = cell
168 }
169 }
170
171 *bestCell = cellBot
172}
173
174type boardData struct {
175 isWin bool
176 isPat bool
177
178 winningUser string
179}
180
181func analyzeWin(winningCombinations [][3]*string) boardData {
182 b := boardData{}
183
184 winnable := false
185 for _, combination := range winningCombinations {
186 c := calculateCombinationStats(combination)
187
188 if c.botCells == 3 {
189 b.isWin = true
190 b.winningUser = cellBot
191 return b
192 }
193 if c.userCells == 3 {
194 b.isWin = true
195 b.winningUser = cellUser
196 return b
197 }
198
199 if c.botCells == 0 || c.userCells == 0 {
200 winnable = true
201 }
202 }
203
204 if !winnable {
205 b.isPat = true
206 }
207
208 return b
209}
210
211func main() {
212 board := [3][3]string{
213 {cellEmpty, cellEmpty, cellEmpty},
214 {cellEmpty, cellEmpty, cellEmpty},
215 {cellEmpty, cellEmpty, cellEmpty},
216 }
217 winningCombinations := getWinningCombinations(&board)
218
219 currentUser := 0
220 for {
221 fmt.Println()
222 fmt.Println(boardToString(board))
223
224 bd := analyzeWin(winningCombinations)
225 if bd.isWin {
226 fmt.Println()
227 switch bd.winningUser {
228 case cellBot:
229 fmt.Println("THE BOT WINS!!")
230 case cellUser:
231 fmt.Println("CONGRATULATION, YOU WIN!!")
232 }
233 break
234 }
235 if bd.isPat {
236 fmt.Println()
237 fmt.Println("GAME OVER. NEITHER ONE WON")
238 break
239 }
240
241 if currentUser == 0 {
242 fmt.Println()
243 fmt.Println("it's your turn human")
244 changeCellUserInput(&board, cellUser)
245 } else {
246 changeCellBotInput(&board, winningCombinations)
247 }
248
249 currentUser = (currentUser + 1) % 2
250 }
251}
252