Skip to content

Commit 7cefc85

Browse files
committed
nimimax
1 parent 320b929 commit 7cefc85

File tree

1 file changed

+101
-34
lines changed
  • code-samples/type-level-tic-tac-toe

1 file changed

+101
-34
lines changed

code-samples/type-level-tic-tac-toe/Main.hs

Lines changed: 101 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,10 @@
77
module Main where
88

99
import Data.Char (toUpper)
10+
import Data.Maybe (mapMaybe)
11+
import qualified Data.Map.Strict as Map
1012
import Data.Dependent.Sum (DSum((:=>)))
13+
import Data.Type.Equality ((:~:)(Refl))
1114
import Data.Void (Void)
1215
import TicTacToe
1316

@@ -26,19 +29,27 @@ loop sboard sp g = do
2629
putStrLn ("Winner: " ++ showSPlayer spw)
2730
Proved (Right _) ->
2831
putStrLn "Draw."
29-
Disproved nw -> do
30-
move <- promptMove sp
31-
case move of
32-
Nothing -> loop sboard sp g
33-
Just (r, c) ->
34-
withSomeIx r \sr ->
35-
withSomeIx c \sc ->
36-
case playAt sp sboard sr sc of
37-
Disproved _ -> do
38-
putStrLn "That cell is already taken. Try again."
39-
loop sboard sp g
40-
Proved (sboard' :=> PlayAt repRow repBoard) ->
41-
loop sboard' (nextSPlayer sp) (AddMove (Play repRow repBoard) (nw . Left) g)
32+
Disproved nw ->
33+
case sp of
34+
SX -> do
35+
move <- promptMove sp
36+
case move of
37+
Nothing -> loop sboard sp g
38+
Just (r, c) ->
39+
withSomeIx r \sr ->
40+
withSomeIx c \sc ->
41+
case playAt sp sboard sr sc of
42+
Disproved _ -> do
43+
putStrLn "That cell is already taken. Try again."
44+
loop sboard sp g
45+
Proved (sboard' :=> PlayAt repRow repBoard) ->
46+
loop sboard' (nextSPlayer sp) (AddMove (Play repRow repBoard) (nw . Left) g)
47+
SO ->
48+
case bestMove sp sboard of
49+
Nothing -> putStrLn "No moves."
50+
Just (r, c, sboard' :=> Play repRow repBoard) -> do
51+
putStrLn ("AI plays " ++ showMove (r, c))
52+
loop sboard' (nextSPlayer sp) (AddMove (Play repRow repBoard) (nw . Left) g)
4253

4354
promptMove :: SPlayer (p :: Player) -> IO (Maybe (Ix, Ix))
4455
promptMove sp = do
@@ -47,30 +58,21 @@ promptMove sp = do
4758
case parseMove input of
4859
Just mv -> pure (Just mv)
4960
Nothing -> do
50-
putStrLn "Invalid move. Use AA/AB/AC or A1/B2/C3."
61+
putStrLn "Invalid move. Use A1/B2/C3."
5162
pure Nothing
5263

5364
parseMove :: String -> Maybe (Ix, Ix)
54-
parseMove input =
55-
case filter (/= ' ') (map toUpper input) of
56-
[r, c] -> (,) <$> parseIx r <*> parseIxAlt c
57-
_ -> Nothing
58-
59-
parseIx :: Char -> Maybe Ix
60-
parseIx c = case c of
61-
'A' -> Just A
62-
'B' -> Just B
63-
'C' -> Just C
64-
_ -> Nothing
65-
66-
parseIxAlt :: Char -> Maybe Ix
67-
parseIxAlt c = case parseIx c of
68-
Just ix -> Just ix
69-
Nothing -> case c of
70-
'1' -> Just A
71-
'2' -> Just B
72-
'3' -> Just C
73-
_ -> Nothing
65+
parseMove input = Map.lookup key moveTable
66+
where
67+
key = filter (/= ' ') (map toUpper input)
68+
69+
moveTable :: Map.Map String (Ix, Ix)
70+
moveTable =
71+
Map.fromList
72+
[ ([r, n], (row, col))
73+
| (r, row) <- [('A', A), ('B', B), ('C', C)]
74+
, (n, col) <- [('1', A), ('2', B), ('3', C)]
75+
]
7476

7577

7678
renderBoard :: Sing (board :: Board) -> String
@@ -111,12 +113,77 @@ showIxNum ix = case ix of
111113
B -> '2'
112114
C -> '3'
113115

116+
showMove :: (Ix, Ix) -> String
117+
showMove (r, c) = [showIx r, showIxNum c]
118+
114119
withSomeIx :: Ix -> (forall (i :: Ix). Sing i -> r) -> r
115120
withSomeIx ix f = case ix of
116121
A -> f SA
117122
B -> f SB
118123
C -> f SC
119124

125+
bestMove
126+
:: SPlayer (p :: Player)
127+
-> Sing (board :: Board)
128+
-> Maybe (Ix, Ix, DSum Sing (Play p board))
129+
bestMove sp sboard = case possibleMoves sp sboard of
130+
[] -> Nothing
131+
(m:ms) -> Just (foldl (better sp) m ms)
132+
133+
better
134+
:: SPlayer (p :: Player)
135+
-> (Ix, Ix, DSum Sing (Play p board))
136+
-> (Ix, Ix, DSum Sing (Play p board))
137+
-> (Ix, Ix, DSum Sing (Play p board))
138+
better sp m1@(_, _, b1) m2@(_, _, b2) =
139+
if scorePlay sp b2 > scorePlay sp b1 then m2 else m1
140+
141+
scorePlay
142+
:: SPlayer (p :: Player)
143+
-> DSum Sing (Play p board)
144+
-> Int
145+
scorePlay sp (board' :=> _) = negate (minimax (nextSPlayer sp) board')
146+
147+
minimax :: SPlayer (p :: Player) -> Sing (board :: Board) -> Int
148+
minimax sp sboard =
149+
case decideOutcome sboard of
150+
Proved (Left (spw :=> Victory _)) ->
151+
case decidePlayerEq sp spw of
152+
Proved Refl -> 1
153+
Disproved _ -> -1
154+
Proved (Right _) -> 0
155+
Disproved _ ->
156+
case possibleMoves sp sboard of
157+
[] -> 0
158+
moves -> maximum (map (scorePlay sp . pickPlay) moves)
159+
where
160+
pickPlay :: (Ix, Ix, DSum Sing (Play p' board)) -> DSum Sing (Play p' board)
161+
pickPlay (_, _, play) = play
162+
163+
possibleMoves
164+
:: SPlayer (p :: Player)
165+
-> Sing (board :: Board)
166+
-> [(Ix, Ix, DSum Sing (Play p board))]
167+
possibleMoves sp sboard =
168+
mapMaybe (uncurry (movesAt sp sboard)) allPairs
169+
170+
movesAt
171+
:: SPlayer (p :: Player)
172+
-> Sing (board :: Board)
173+
-> Ix
174+
-> Ix
175+
-> Maybe (Ix, Ix, DSum Sing (Play p board))
176+
movesAt sp sboard r c =
177+
withSomeIx r \sr ->
178+
withSomeIx c \sc ->
179+
case playAt sp sboard sr sc of
180+
Proved (board' :=> PlayAt repRow repBoard) ->
181+
Just (r, c, board' :=> Play repRow repBoard)
182+
Disproved _ -> Nothing
183+
184+
allPairs :: [(Ix, Ix)]
185+
allPairs = [(r, c) | r <- [A, B, C], c <- [A, B, C]]
186+
120187
sEmptyBoard :: Sing EmptyBoard
121188
sEmptyBoard =
122189
ST

0 commit comments

Comments
 (0)