Last active 1750245234

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