File tree

1 file changed

+48
-30
lines changed
  • Classic Computer Science Problems in Swift.playground/Pages/Chapter 8.xcplaygroundpage

1 file changed

+48
-30
lines changed
Original file line numberDiff line numberDiff line change
@@ -192,19 +192,14 @@ enum Piece: String {
192192
}
193193
}
194194

195-
// a move is an integer 0-9 indicating a place to put a piece
195+
// a move is an integer 0-8 indicating a place to put a piece
196196
typealias Move = Int
197197

198198
struct Board {
199199
let position: [Piece]
200200
let turn: Piece
201201
let lastMove: Move
202202

203-
// the legal moves in a position are all of the empty squares
204-
var legalMoves: [Move] {
205-
return position.indices.filter { position[$0] == .E }
206-
}
207-
208203
// by default the board is empty and X goes first
209204
// lastMove being -1 is a marker of a start position
210205
init(position: [Piece] = [.E, .E, .E, .E, .E, .E, .E, .E, .E], turn: Piece = .X, lastMove: Int = -1) {
@@ -221,16 +216,21 @@ struct Board {
221216
return Board(position: tempPosition, turn: turn.opposite, lastMove: location)
222217
}
223218

219+
// the legal moves in a position are all of the empty squares
220+
var legalMoves: [Move] {
221+
return position.indices.filter { position[$0] == .E }
222+
}
223+
224224
var isWin: Bool {
225225
return
226-
position[0] == position[1] && position [0] == position[2] && position[0] != .E || // row 0
227-
position[3] == position[4] && position[3] == position [5] && position[3] != .E || // row 1
228-
position[6] == position[7] && position[6] == position[8] && position[6] != .E || // row 2
229-
position[0] == position[3] && position[0] == position[6] && position[0] != .E || // col 0
230-
position[1] == position[4] && position[1] == position[7] && position[1] != .E || // col 1
231-
position[2] == position[5] && position[2] == position[8] && position[2] != .E || // col 2
232-
position[0] == position[4] && position[0] == position[8] && position[0] != .E || // diag 0
233-
position[2] == position[4] && position[2] == position[6] && position[2] != .E // diag 1
226+
position[0] == position[1] && position[0] == position[2] && position[0] != .E || // row 0
227+
position[3] == position[4] && position[3] == position[5] && position[3] != .E || // row 1
228+
position[6] == position[7] && position[6] == position[8] && position[6] != .E || // row 2
229+
position[0] == position[3] && position[0] == position[6] && position[0] != .E || // col 0
230+
position[1] == position[4] && position[1] == position[7] && position[1] != .E || // col 1
231+
position[2] == position[5] && position[2] == position[8] && position[2] != .E || // col 2
232+
position[0] == position[4] && position[0] == position[8] && position[0] != .E || // diag 0
233+
position[2] == position[4] && position[2] == position[6] && position[2] != .E // diag 1
234234

235235
}
236236

@@ -239,49 +239,67 @@ struct Board {
239239
}
240240
}
241241

242-
func minimax(_ board: Board, maximizing: Bool) -> (eval: Int, bestMove: Move) {
243-
if board.isWin { return (1, board.lastMove) }
244-
else if board.isDraw { return (0, board.lastMove) }
242+
// Find the best possible outcome for originalPlayer
243+
func minimax(_ board: Board, maximizing: Bool, originalPlayer: Piece) -> Int {
244+
// Base case — evaluate the position if it is a win or a draw
245+
if board.isWin && originalPlayer == board.turn.opposite { return 1 } // win
246+
else if board.isWin && originalPlayer != board.turn.opposite { return -1 } // loss
247+
else if board.isDraw { return 0 } // draw
245248

249+
// Recursive case — maximize your gains or minimize the opponent's gains
246250
if maximizing {
247-
var bestEval: (eval: Int, bestMove: Move) = (Int.min, -1)
248-
for move in board.legalMoves {
249-
let result = minimax(board.move(move), maximizing: false)
250-
if result.eval > bestEval.eval { bestEval = result }
251+
var bestEval = Int.min
252+
for move in board.legalMoves { // find the move with the highest evaluation
253+
let result = minimax(board.move(move), maximizing: false, originalPlayer: originalPlayer)
254+
bestEval = max(result, bestEval)
251255
}
252256
return bestEval
253257
} else { // minimizing
254-
var worstEval: (eval: Int, bestMove: Move) = (Int.max, -1)
258+
var worstEval = Int.max
255259
for move in board.legalMoves {
256-
let result = minimax(board.move(move), maximizing: true)
257-
if result.eval < worstEval.eval { worstEval = result }
260+
let result = minimax(board.move(move), maximizing: true, originalPlayer: originalPlayer)
261+
worstEval = min(result, worstEval)
258262
}
259263
return worstEval
260264
}
261265
}
262266

267+
// Run minimax on every possible move to find the best one
268+
func findBestMove(_ board: Board) -> Move {
269+
var bestEval = Int.min
270+
var bestMove = -1
271+
for move in board.legalMoves {
272+
let result = minimax(board.move(move), maximizing: false, originalPlayer: board.turn)
273+
if result > bestEval {
274+
bestEval = result
275+
bestMove = move
276+
}
277+
}
278+
return bestMove
279+
}
280+
263281
// win in 1 move
264282
let toWinEasyPosition: [Piece] = [.X, .O, .X,
265283
.X, .E, .O,
266284
.E, .E, .O]
267285
let testBoard1: Board = Board(position: toWinEasyPosition, turn: .X, lastMove: 8)
268-
let answer1 = minimax(testBoard1, maximizing: true)
269-
print(answer1.bestMove)
286+
let answer1 = findBestMove(testBoard1)
287+
print(answer1)
270288

271289
// must block O's win
272290
let toBlockPosition: [Piece] = [.X, .E, .E,
273291
.E, .E, .O,
274292
.E, .X, .O]
275293
let testBoard2: Board = Board(position: toBlockPosition, turn: .X, lastMove: 8)
276-
let answer2 = minimax(testBoard2, maximizing: true)
277-
print(answer2.bestMove)
294+
let answer2 = findBestMove(testBoard2)
295+
print(answer2)
278296

279297
// find the best move to win in 2 moves
280298
let toWinHardPosition: [Piece] = [.X, .E, .E,
281299
.E, .E, .O,
282300
.O, .X, .E]
283301
let testBoard3: Board = Board(position: toWinHardPosition, turn: .X, lastMove: 6)
284-
let answer3 = minimax(testBoard3, maximizing: true)
285-
print(answer3.bestMove)
302+
let answer3 = findBestMove(testBoard3)
303+
print(answer3)
286304
//: [Next](@next)
287305

0 commit comments

Comments
 (0)