-
Notifications
You must be signed in to change notification settings - Fork 1
/
Main.elm
378 lines (299 loc) · 9.24 KB
/
Main.elm
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
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
module Main exposing (..)
import AI exposing (..)
import Html as Html
import Html exposing (Html)
import Html.App
import Html.Attributes as Attr
import Html.Events as Events
import Othello exposing (..)
import Svg as Svg
import Svg exposing (Svg, svg)
import Svg.Attributes as SvgAttr
import Task exposing (Task, fail, succeed)
difficulty : Level
difficulty =
3
main : Program Never
main =
Html.App.program
{ init = ( init, Cmd.none )
, subscriptions = \_ -> Sub.none
, update = update
, view = view
}
type GameState
= Moving Stone
| GameOver
type alias Model =
{ board : Board
, game : GameState
, hoover : Maybe Coord
, validMoves : Moves
}
init : Model
init =
{ board = startBoard
, game = Moving White
, hoover = Nothing
, validMoves = initialValidMoves
}
currentPlayer : Model -> Maybe Stone
currentPlayer model =
case model.game of
GameOver ->
Nothing
Moving player ->
Just player
type Message
= NoOp
| Hoover (Maybe Coord)
| ClickAt Coord
| BlackAiMoved Move
| BlackAiFailed
update : Message -> Model -> ( Model, Cmd Message )
update msg model =
case msg of
NoOp ->
( model, Cmd.none )
Hoover over ->
case over of
Just coord ->
if isValidMove model coord then
( { model | hoover = Just coord }, Cmd.none )
else
( { model | hoover = Nothing }, Cmd.none )
Nothing ->
( { model | hoover = Nothing }, Cmd.none )
BlackAiMoved move ->
computerMoves model move
BlackAiFailed ->
( { model
| game = GameOver
, hoover = Nothing
, validMoves = emptyMoves
}
, Cmd.none
)
ClickAt coord ->
case model.game of
GameOver ->
( model, Cmd.none )
Moving Black ->
( model, Cmd.none )
Moving White ->
humanMoves model coord
isValidMove : Model -> Coord -> Bool
isValidMove model coord =
case moveAtCoord model.validMoves coord of
Just _ ->
True
Nothing ->
False
calculateAiMove : Board -> Cmd Message
calculateAiMove board =
Task.perform (\_ -> BlackAiFailed) BlackAiMoved (computerMove board)
computerMove : Board -> Task () Move
computerMove board =
case blackAI difficulty board of
Nothing ->
fail ()
Just move ->
succeed move
computerMoves : Model -> Move -> ( Model, Cmd Message )
computerMoves model move =
let
board' =
applyMove move model.board
nextMoves =
validNextMoves board' White
in
case nextMoves of
NoValidMoves ->
( { model
| board = board'
, game = GameOver
, hoover = Nothing
, validMoves = emptyMoves
}
, Cmd.none
)
ValidMoves White moves ->
( { model
| board = board'
, game = Moving White
, hoover = Nothing
, validMoves = moves
}
, Cmd.none
)
ValidMoves Black moves ->
( { model
| board = board'
, game = Moving Black
, hoover = Nothing
, validMoves = emptyMoves
}
, calculateAiMove board'
)
humanMoves : Model -> Coord -> ( Model, Cmd Message )
humanMoves model coord =
case moveAtCoord model.validMoves coord of
Nothing ->
( model, Cmd.none )
Just move ->
let
board' =
applyMove move model.board
nextMoves =
validNextMoves board' Black
in
case nextMoves of
NoValidMoves ->
( { model
| board = board'
, game = GameOver
, hoover = Nothing
, validMoves = emptyMoves
}
, Cmd.none
)
ValidMoves White moves ->
( { model
| board = board'
, game = Moving White
, hoover = Nothing
, validMoves = moves
}
, Cmd.none
)
ValidMoves Black moves ->
( { model
| board = board'
, game = Moving Black
, hoover = Nothing
, validMoves = emptyMoves
}
, calculateAiMove board'
)
view : Model -> Html Message
view model =
Html.div
[]
[ Html.h1 [] [ Html.text (points model ++ " = " ++ statusText model) ]
, viewBoard model
]
statusText : Model -> String
statusText model =
case model.game of
GameOver ->
"Game Over - " ++ winner model
Moving Black ->
"Black's turn"
Moving White ->
"White's turn"
winner : Model -> String
winner model =
let
whites =
countStones model.board White
blacks =
countStones model.board Black
in
if whites == blacks then
"draw"
else if whites < blacks then
"Black WON"
else
"White WON"
points : Model -> String
points model =
let
whites =
countStones model.board White
blacks =
countStones model.board Black
in
toString whites ++ ":" ++ toString blacks
viewBoard : Model -> Html Message
viewBoard model =
Html.div
[]
(List.map (viewRow model) [0..7])
viewRow : Model -> Int -> Html Message
viewRow model row =
Html.div [ Attr.style [ ( "height", "40px" ) ] ]
(List.map (viewCell model row) [0..7])
viewCell : Model -> Int -> Int -> Html Message
viewCell model row col =
cellAt model.board ( row, col )
|> Maybe.withDefault Empty
|> let
coord =
( row, col )
in
renderCell
(isValidMove model coord)
(model.hoover == Just coord)
(currentPlayer model)
coord
renderCell : Bool -> Bool -> Maybe Stone -> Coord -> Cell -> Html Message
renderCell highlighted hooverOver player coord cell =
svg
[ SvgAttr.width "40"
, SvgAttr.height "40"
, SvgAttr.viewBox "-5 -5 10 10"
]
(Svg.rect [ SvgAttr.x "-5", SvgAttr.y "-5", SvgAttr.width "10", SvgAttr.height "10", SvgAttr.fill "green" ] []
:: case cell of
Empty ->
[ Svg.circle
[ Attr.style
[ ( "cursor"
, if highlighted then
"pointer"
else
"default"
)
]
, Events.onMouseEnter (Hoover (Just coord))
, Events.onMouseLeave (Hoover Nothing)
, Events.onClick (ClickAt coord)
, SvgAttr.r "4.5"
, SvgAttr.fill
(if hooverOver then
stoneColor player
else if highlighted then
highlightColor
else
holeColor
)
]
[]
]
Occupied stone ->
renderStone (Just stone)
)
renderStone : Maybe Stone -> List (Svg Message)
renderStone stone =
[ Svg.circle [ SvgAttr.r "4.5", SvgAttr.fill (stoneColor stone) ] [] ]
stoneColor : Maybe Stone -> String
stoneColor stone =
case stone of
Just White ->
whiteColor
Just Black ->
blackColor
Nothing ->
holeColor
whiteColor : String
whiteColor =
"#efefef"
blackColor : String
blackColor =
"#020202"
highlightColor : String
highlightColor =
"#77c777"
holeColor : String
holeColor =
"#77a777"