最后活跃于 1750241564

Hazel's Avatar Hazel 修订了这个 Gist 1750241564. 转到此修订

1 file changed, 251 insertions

tictactoe_bot.go(文件已创建)

@@ -0,0 +1,251 @@
1 + package main
2 +
3 + import (
4 + "fmt"
5 + "strconv"
6 + "strings"
7 + )
8 +
9 + const (
10 + cellEmpty = " "
11 + cellUser = "X"
12 + cellBot = "O"
13 + )
14 +
15 + func 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 +
31 + func 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 +
69 + func 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 +
96 + type combinationStat struct {
97 + totalCells int
98 + emptyCells int
99 + userCells int
100 + botCells int
101 +
102 + emptyCellRefs []*string
103 + }
104 +
105 + func 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 +
124 + func 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 +
174 + type boardData struct {
175 + isWin bool
176 + isPat bool
177 +
178 + winningUser string
179 + }
180 +
181 + func 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 +
211 + func 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 + }
上一页 下一页