-
Notifications
You must be signed in to change notification settings - Fork 0
/
UnbeatableComputer.fs
92 lines (78 loc) · 3.03 KB
/
UnbeatableComputer.fs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
module UnbeatableComputer
open Game
(*
The computer strategy will be:
- If I have a winning move, then play it
- If you have a winning move, then play there to block it
- Special case: If I have the centre and you have two opposite corners then play on a side
- If I have a move that creates two possible winning moves (a "winning setup"), then play there
- If you have a move that creates two possible winning moves, then play there to block it
- Otherwise play the first in a hard-coded list of key positions (centre, then corners, then rest)
*)
let winningMoves symbol groups =
let myPositions = getPositions (Full symbol) groups
let emptyPositions = getPositions Empty groups
lines
|> List.map (fun line -> Set.difference line myPositions) // remove the full positions
|> List.filter (Set.count >> ((=) 1)) // keep those with one position left
|> List.filter (Set.isSuperset emptyPositions) // keep those that are for empty positions
|> List.collect Set.toList
let winningMove symbol groups =
winningMoves symbol groups
|> List.sort
|> List.tryPick Some
let myWinningMove = winningMove
let otherSymbol = function X -> O | O -> X
let yourWinningMove mySymbol groups =
let yourSymbol = otherSymbol mySymbol
winningMove yourSymbol groups
let createsTwoWinningMoves board symbol position =
let groups =
board
|> updateBoard symbol position
|> groupPositionsByCell
winningMoves symbol groups
|> List.length
|> (<) 1
let oppositeCorners = [set [1; 9]; set [3; 7] ]
let specialCaseMove mySymbol groups =
let myPositions = getPositions (Full mySymbol) groups
let yourSymbol = otherSymbol mySymbol
let yourPositions = getPositions (Full yourSymbol) groups
let iHaveCenter = myPositions = set [5]
let youHaveOppositeCorners =
List.exists ((=) yourPositions) oppositeCorners
if iHaveCenter && youHaveOppositeCorners then
Some 2
else
None
let winningSetup board symbol groups =
let emptyPositions = getPositions Empty groups
emptyPositions
|> Set.toList
|> List.sort
|> List.filter (createsTwoWinningMoves board symbol)
|> List.tryPick Some
let myWinningSetup = winningSetup
let yourWinningSetup board mySymbol groups =
let yourSymbol = otherSymbol mySymbol
winningSetup board yourSymbol groups
let bestRemainingPosition mySymbol groups =
let emptyPositions = getPositions Empty groups
[5; 1; 3; 7; 9; 2; 4; 6; 8]
|> Seq.filter (fun p -> Set.contains p emptyPositions)
|> Seq.tryPick Some
let strategy board mySymbol =
let groups = groupPositionsByCell board
let bestToWorstMoves =
seq {
yield myWinningMove mySymbol groups
yield yourWinningMove mySymbol groups
yield specialCaseMove mySymbol groups
yield myWinningSetup board mySymbol groups
yield yourWinningSetup board mySymbol groups
yield bestRemainingPosition mySymbol groups
}
|> Seq.choose id
bestToWorstMoves
|> Seq.pick Some