-
Notifications
You must be signed in to change notification settings - Fork 0
/
Game.fs
157 lines (128 loc) · 3.87 KB
/
Game.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
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
/// Contains the game logic. No IO is performed.
module Game
open Result
type Symbol = X | O
type Cell = Full of Symbol | Empty
type Board = Cell list
type Position = int
type Strategy = Board -> Symbol -> Position
type [<NoEquality>] [<NoComparison>]
Player =
{
symbol: Symbol
strategy: Strategy
}
type Outcome =
| Draw
| Winner of Symbol
type Status =
| InProgress
| Complete of Outcome
type [<NoEquality>] [<NoComparison>]
Game =
{
board: Board
players: Player * Player
nextPlayer: Player
status: Status
}
type MoveFailure =
| PositionFull
| PositionNotInRange
let symbolString = function X -> "X" | O -> "O"
/// A line is a set of 3 positions that would result in a win
let lines =
[
// Horizontals
set [1; 2; 3]
set [4; 5; 6]
set [7; 8; 9]
// Verticals
set [1; 4; 7]
set [2; 5; 8]
set [3; 6; 9]
// Diagonals
set [1; 5; 9]
set [3; 5; 7]
]
let groupPositionsByCell board =
board
|> Seq.zip [1 .. 9]
|> Seq.groupBy snd
|> Seq.map (fun (value, positionAndCells) ->
let positions = positionAndCells |> Seq.map fst
value, Set.ofSeq positions)
|> Map.ofSeq
let getPositions cell groups =
defaultArg
(Map.tryFind cell groups)
Set.empty
let updateListAt index value list =
List.mapi (fun i x -> if i = index then value else x) list
let updateBoard symbol position board =
updateListAt (position - 1) (Full symbol) board
let getCell position board = List.nth board (position - 1)
let swapNextPlayer game =
let (p1, p2) = game.players
{game with
nextPlayer = if game.nextPlayer.symbol = p1.symbol then p2 else p1}
let boardFull = List.exists ((=) Empty) >> not
let getWinner board =
let groups = groupPositionsByCell board
let containsLine positions =
Seq.exists (Set.isSuperset positions) lines
let XPositions = getPositions (Full X) groups
let OPositions = getPositions (Full O) groups
if containsLine XPositions then
Some X
else if containsLine OPositions then
Some O
else
None
let updateStatus game =
{game with
status =
match getWinner game.board with
| Some X -> Complete (Winner X)
| Some Y -> Complete (Winner Y)
| _ when boardFull game.board -> Complete Draw
| None -> InProgress}
let tryPlayMove (position:Position) symbol game =
if not (1 <= position && position <= 9) then
Failure (PositionNotInRange, position, game)
else if getCell position game.board <> Empty then
Failure (PositionFull, position, game)
else
{game with board = updateBoard symbol position game.board}
|> swapNextPlayer
|> updateStatus
|> Success
let getGameFromResult = function
| Success g -> g
| Failure (_, _, g) -> g
let play drawGameResult game =
let rec loop game acc =
let currentPlayer = game.nextPlayer
let symbol = currentPlayer.symbol
let position = currentPlayer.strategy game.board symbol
let gameResult = tryPlayMove position symbol game
drawGameResult gameResult
let nextGame = getGameFromResult gameResult
let nextAcc = (nextGame, symbol, position) :: acc
match nextGame.status with
| InProgress ->
loop nextGame nextAcc
| Complete _ ->
List.rev nextAcc
drawGameResult (Success game)
loop game []
let makeGame playerXStrategy playerOStrategy =
let startingBoard = List.init 9 (fun _ -> Empty)
let playerX = {symbol = X; strategy = playerXStrategy}
let playerO = {symbol = O; strategy = playerOStrategy}
{
board = startingBoard
players = playerX, playerO
nextPlayer = playerX
status = InProgress
}