77module Main where
88
99import Data.Char (toUpper )
10+ import Data.Maybe (mapMaybe )
11+ import qualified Data.Map.Strict as Map
1012import Data.Dependent.Sum (DSum ((:=>) ))
13+ import Data.Type.Equality ((:~:) (Refl ))
1114import Data.Void (Void )
1215import 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
4354promptMove :: SPlayer (p :: Player ) -> IO (Maybe (Ix , Ix ))
4455promptMove 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
5364parseMove :: 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
7678renderBoard :: 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+
114119withSomeIx :: Ix -> (forall (i :: Ix ). Sing i -> r ) -> r
115120withSomeIx 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+
120187sEmptyBoard :: Sing EmptyBoard
121188sEmptyBoard =
122189 ST
0 commit comments