diff --git a/Alphabeta.hs b/Alphabeta.hs index cb48d3d..800e60b 100644 --- a/Alphabeta.hs +++ b/Alphabeta.hs @@ -8,11 +8,18 @@ import Data.List import System.Random -- Improvements: +-- 0. Consolidate alphabeta and chAlphabeta -- 1. Memoizations -- 2. Node heuristics type ABValue = (Value, Value) +data ABParams gs = ABParams {alpha :: {-# UNPACK #-} !Value, beta :: {-# UNPACK #-} !Value, + chooser :: Maybe (ChoiceHeuristic gs)} + +defaultABParams :: forall gs. (GameState gs) => ABParams gs +defaultABParams = ABParams {alpha = -1, beta = 1, chooser = Nothing} + type ChoiceHeuristic gs = gs -> String -> Value type StateHeuristic gs = gs -> Value @@ -27,7 +34,7 @@ instance Show ABSolvedGame where show (ABSolvedGame {gameState}) = show gameState instance GameState ABSolvedGame where - firstplayer (ABSolvedGame {gameState}) = firstplayer gameState + player (ABSolvedGame {gameState}) = player gameState terminal (ABSolvedGame {gameState}) = terminal gameState maxdepth (ABSolvedGame {gameState}) = maxdepth gameState actions = actions' @@ -56,9 +63,9 @@ alphabetaSolver !ab !gameState = ABSolvedGame {gameState, actions', abvalue = ab alphabetize [(_, ns)] !ab' = abvalue ns ab' alphabetize !(x:xs) !(a, b) = let !kidval = abvalue (snd x) (a, b) - in if a >= b then kidval else if firstplayer gameState - then max kidval $ alphabetize xs (max a kidval, b) - else min kidval $ alphabetize xs (a, min b kidval) + in if a >= b then kidval else case player gameState of + Maximizer -> max kidval $ alphabetize xs (max a kidval, b) + Minimizer -> min kidval $ alphabetize xs (a, min b kidval) chAlphabetaSolver :: (GameState a) => ChoiceHeuristic a -> ABValue -> a -> ABSolvedGame chAlphabetaSolver !chooser !ab !gameState = ABSolvedGame {gameState, actions', abvalue = abval, value} where @@ -70,9 +77,9 @@ chAlphabetaSolver !chooser !ab !gameState = ABSolvedGame {gameState, actions', a alphabetize [(_, ns)] !ab' = abvalue ns ab' alphabetize !(x:xs) !(a, b) = let !kidval = abvalue (snd x) (a, b) - in if a >= b then kidval else if firstplayer gameState - then max kidval $ alphabetize xs (max a kidval, b) - else min kidval $ alphabetize xs (a, min b kidval) + in if a >= b then kidval else case player gameState of + Maximizer -> max kidval $ alphabetize xs (max a kidval, b) + Minimizer -> min kidval $ alphabetize xs (a, min b kidval) -- memo should not be hard - we just figure out when we can be sure of the value diff --git a/Interaction.hs b/Interaction.hs deleted file mode 100644 index 81c1cd3..0000000 --- a/Interaction.hs +++ /dev/null @@ -1,111 +0,0 @@ -module Interaction where - -import SolverDefs -import System.IO -import System.Random - - -showGameState :: (Show a) => Bool -> Bool -> GameState (a, b) -> String -showGameState showmoves first ((a, b), rgs) = show a ++ (turn rgs) ++ moves where - name = if not first then "First player's turn\n" else "Second player's turn\n" - turn (Internal _) = name - turn (Terminal _) = "" - showRgs (Terminal x) = "Final position, value is " ++ show x - showRgs (Internal choices) = "Moves: " ++ (unwords $ map fst choices) - moves = if showmoves then (showRgs rgs) ++ "\n" else "" - - -yesno :: String -> IO Bool -yesno msg = do - putStr $ msg ++ " (yes/no): " - hFlush stdout - ans <- getLine - case ans of - "yes" -> return True - "no" -> return False - _ -> (putStrLn $ ans ++ " is not a valid answer.") >> yesno msg - - -multiplayer :: (Statable a, Show a, Show c) => Bool -> b -> SolverRun a b c -> IO (a, Value) -multiplayer showmoves args solver = do - rand <- newStdGen - internal True $ solver rand - where - internal _ ((a, _), Terminal x) = return (a, x) - internal first gs = do - let ((a, f), Internal nss) = gs - b = f args - putStr $ "\n" ++ showGameState showmoves (not first) gs ++ "Make a move: " - hFlush stdout - move <- getLine - let move' = if move == "ai" then show b else move - if move' == "quit" then return (a, 0) else - let valids = filter ((== move') . fst) $ nss in - if null valids - then putStrLn ("\nNot a valid move: " ++ move) >> - internal first gs - else internal (not first) $ snd $ head valids - -playagain :: IO a -> IO a -playagain game = do - x <- game - again <- yesno "\nWould you like to play a new game?" - if again then putStrLn "\n" >> playagain game else return x - - -singlemulti :: (Statable a, Show a, Show c) => Bool -> b -> SolverRun a b c -> IO (a, Value) -singlemulti showmoves args solver = do - single <- yesno "Would you like to challenge the AI? " - if single - then singleplayerstart showmoves args solver - else multiplayer showmoves args solver - - -singleplayerstart :: (Statable a, Show a, Show c) => - Bool -> b -> SolverRun a b c -> IO (a, Value) -singleplayerstart showmoves args solver = do - rand <- newStdGen - let gs = solver rand - first <- yesno "Would you like to start?" - if first then singleplayeriter showmoves args gs else act gs where - act ((a, _), Terminal x) = return (a, x) - act ((a, f), Internal children) = do - let b = f args - putStrLn "" - print a - putStr $ "AI chose the move " - print b - let chosen = snd $ head $ filter ((== show b) . fst) $ children - singleplayeriter showmoves args chosen - - -singleplayeriter :: (Statable a, Show a, Show c) => - Bool -> b -> SolvedGameState a b c -> IO (a, Value) -singleplayeriter _ _ ((a, _), Terminal x) = return (a, x) -singleplayeriter showmoves args gs = do - let ((a, b), Internal children) = gs - first = isfirstplayer a - putStr $ "\n" ++ showGameState showmoves (not first) gs ++ "Make a move: " - hFlush stdout - move <- getLine - if move == "quit" then return (a, 0) else - let valids = filter ((== move) . fst) $ children in - if null valids - then putStrLn ("\nNot a valid move: " ++ move) >> - singleplayeriter showmoves args gs - else act $ snd $ head valids where - act ((a, _), Terminal x) = return (a, x) - act ((a, f), Internal grandchildren) = do - let b = f args - putStrLn "" - print a - putStr "AI chose the move " - print b - singleplayeriter showmoves args $ snd $ head $ filter ((== show b) . fst) grandchildren - -simulator :: (Show a, Show c) => b -> SolverRun a b c -> StdGen -> [String] -simulator args run rand = simulate $ run rand where - simulate ((a, _), Terminal _) = [show a] - simulate ((a, f), Internal children) = show a : simulate gs where - move = show $ f args - gs = snd $ head $ filter ((== move) . fst) $ children diff --git a/LimitMoves.hs b/LimitMoves.hs index 757b839..1e72214 100644 --- a/LimitMoves.hs +++ b/LimitMoves.hs @@ -19,8 +19,8 @@ instance Show LimitInitialMoves where show !(NoLimitInitialMoves !gs) = show gs instance GameState LimitInitialMoves where - firstplayer !(LimitInitialMoves (!gs, _)) = firstplayer gs - firstplayer !(NoLimitInitialMoves !gs) = firstplayer gs + player !(LimitInitialMoves (!gs, _)) = player gs + player !(NoLimitInitialMoves !gs) = player gs terminal !(LimitInitialMoves (!gs, _)) = terminal gs terminal !(NoLimitInitialMoves !gs) = terminal gs actions !(NoLimitInitialMoves !gs) = map mf $ actions gs where diff --git a/MCTS.hs b/MCTS.hs index 8523bab..f75ac18 100644 --- a/MCTS.hs +++ b/MCTS.hs @@ -19,7 +19,7 @@ import System.IO -- 3. Heuristics -- 4. RAVE / EMAF -- 5. Ability to switch to a different solver (ab?) on one thread for the end game --- 6. Multiple rollouts. +-- 6. Multiple rollouts. Depending on the root simulations count, 1 + round (#/1000000) -- 7. Params need to sit in a reader monad / belong to advnace and not the game (only defaults) data MCSolvedGame = forall gs. (GameState gs) => @@ -43,22 +43,30 @@ data MCActions = Terminal (Value, [MCAction]) | Trunk (PQ.PQueue MCAction, [MCAction]) data MCParams = MCParams - {evalfunc :: Bool -> Value -> Value -> Value -> Value, - alpha :: Value, beta :: Value, - duration :: Int, maxsim :: Int, background :: Bool, - uniform :: Bool} + {evalfunc :: Player -> Value -> Value -> Value -> Value, + alpha :: {-# UNPACK #-} !Value, beta :: {-# UNPACK #-} !Value, + duration :: {-# UNPACK #-} !Int, maxsim :: {-# UNPACK #-} !Int, + background :: !Bool, uniform :: !Bool} defaultMCParams :: MCParams defaultMCParams = MCParams {evalfunc = ucb1 2, alpha = (-1), beta = 1, - duration = 1000, maxsim = 2000000, background = True, + duration = 1000, maxsim = 1000000, background = True, uniform = False} +playerBound :: Player -> MCParams -> Value +playerBound Maximizer = alpha +playerBound Minimizer = beta + +playerObjectiveBy :: (Foldable t) => Player -> (a -> a -> Ordering) -> t a -> a +playerObjectiveBy Maximizer = maximumBy +playerObjectiveBy Minimizer = minimumBy + instance Show MCSolvedGame where show (MCSolvedGame {gameState}) = show gameState instance GameState MCSolvedGame where terminal (MCSolvedGame {gameState}) = terminal gameState - firstplayer (MCSolvedGame {gameState}) = firstplayer gameState + player (MCSolvedGame {gameState}) = player gameState maxdepth (MCSolvedGame {gameState}) = maxdepth gameState actions (MCSolvedGame {children = Terminal (_, xs)}) = map (snd . unMCAction) xs actions (MCSolvedGame {children = Trunk (xs, ys)}) = map (snd . unMCAction) ((PQ.toAscList xs) ++ ys) @@ -75,7 +83,7 @@ instance SolvedGameState MCSolvedGame where f (MCAction (_, (_, MCSolvedGame {wins=w1, simulations=s1}))) (MCAction (_, (_, MCSolvedGame {wins=w2, simulations=s2}))) = compare (w1/s1) (w2/s2) - objective = if firstplayer mgs then maximumBy else minimumBy + objective = playerObjectiveBy $! player mgs think = advanceuntil mkLeaf :: (GameState gs, RandomGen rg) => MCParams -> rg -> gs -> (MCSolvedGame, rg) @@ -98,12 +106,12 @@ mkLeaf' !params !gameState = then Terminal (fromJust maybeval, []) else Bud (fromIntegral $ numactions $! gameState, [], actions $! gameState) -mkTrunk :: Bool -> Value -> [MCAction] -> MCActions -mkTrunk !first !testval !xs = maybeTrunk $! partition f xs where +mkTrunk :: Player -> Value -> [MCAction] -> MCActions +mkTrunk !player !testval !xs = maybeTrunk $! partition f xs where f (MCAction (_, (_, !MCSolvedGame {children = Terminal _}))) = False f _ = True g (MCAction (_, (_, !MCSolvedGame {children = Terminal (realval, _)}))) = realval - !objective = if first then maximum else minimum + !objective = playerObjective player maybeTrunk !([], !ys) = Terminal (objective $ map g ys, ys) maybeTrunk !(!xs, !ys) = if (or $ map ((==testval) . g) ys) then Terminal (testval, xs ++ ys) @@ -138,15 +146,15 @@ timedadvance mgs = do if ct > st || stopcond cgs then return cgs else internal $! multiadvance 1000 cgs rand stopcond (MCSolvedGame {children = !(Terminal _)}) = True stopcond (MCSolvedGame {simulations}) = simulations > maxsim' - -- internal mgs - res <- internal mgs - let sims1 = simulations mgs - sims2 = simulations res - denom = (fromIntegral $ duration $ params mgs)/1000 - persec = (sims2-sims1) / denom - putStr "Performance: " - print ((sims1, sims2, denom), persec) - return res + internal mgs + -- res <- internal mgs + -- let sims1 = simulations mgs + -- sims2 = simulations res + -- denom = (fromIntegral $ duration $ params mgs)/1000 + -- persec = (sims2-sims1) / denom + -- putStr "Performance: " + -- print ((sims1, sims2, denom), persec) + -- return res multiadvance :: (RandomGen rg) => Int -> MCSolvedGame -> rg -> MCSolvedGame multiadvance n gs rand = fst $ (iterate f (gs, rand)) !! n where @@ -157,16 +165,16 @@ advanceNode :: (RandomGen rg) => MCSolvedGame -> rg -> (MCSolvedGame, rg, Value) advanceNode !mgs@(MCSolvedGame {children = (Terminal (tval, _))}) rand = (mgs, rand, tval) advanceNode !mgs@(MCSolvedGame {simulations, wins=w, gameState, children = (Bud (len, post, pre)), - params = !p@(MCParams {evalfunc, alpha, beta})}) rand = + params = !p@(MCParams {evalfunc})}) rand = (mgs {simulations = simulations+1, wins = w+val, children = f children'}, rand', val) where !(!str, !gs) = head pre !(!ngs, !rand') = mkLeaf p rand gs !val = wins ngs - !first = firstplayer gameState - !eval = fromMaybe (evalfunc first val 1 len) (terminalVal $ children ngs) + !player' = player gameState + !eval = fromMaybe (evalfunc player' val 1 len) (terminalVal $ children ngs) !nact = MCAction $! (eval, (str, ngs)) !children' = Bud (len, nact : post, tail pre) - f !(Bud (_, !post', [])) = mkTrunk first (if first then beta else alpha) post' + f !(Bud (_, !post', [])) = mkTrunk player' (playerBound player' p) post' f !bud = bud advanceNode !mgs@(MCSolvedGame {simulations=s, wins=w, gameState, children = (Trunk (nonterminals, terminals)), @@ -174,17 +182,17 @@ advanceNode !mgs@(MCSolvedGame {simulations=s, wins=w, gameState, (mgs {simulations = s', wins = w+val, children = f children', params = p'}, rand', val) where (MCAction (_, (!str, !child)), !queue) = fromJust $ PQ.extract nonterminals !p' = p {uniform = False} - !evalfunc' = if uniform then (\_ _ n _ -> s-n) else evalfunc - !first = firstplayer gameState - !objective = if first then maximum else minimum + !evalfunc' = if uniform then (\_ _ n nn -> nn-n) else evalfunc + !player' = player gameState + !objective = playerObjective player' !s' = s+1 (!child', !rand', !val) = advanceNode child rand !children' = case child' of - MCSolvedGame {children = (Terminal (tval, _))} -> if tval == (if first then beta else alpha) + MCSolvedGame {children = (Terminal (tval, _))} -> if tval == playerValue player' then Terminal (tval, (MCAction (tval, (str, child')):terminals) ++ PQ.toAscList queue) else Trunk (queue, MCAction (tval, (str, child')):terminals) otherwise -> Trunk (PQ.insert nact queue, terminals) where - !eval = fromMaybe (evalfunc' first (wins child') (simulations child) s') + !eval = fromMaybe (evalfunc' player' (wins child') (simulations child) s') (terminalVal $ children child') !nact = MCAction (eval ,(str, child')) f ch@(Trunk (!q', nt')) = if isNothing $ PQ.extract q' @@ -196,8 +204,8 @@ terminalVal :: MCActions -> Maybe Value terminalVal !(Terminal (!v, _)) = Just v terminalVal _ = Nothing -ucb1 :: Value -> Bool -> Value -> Value -> Value -> Value -ucb1 !c !first !w !n !nn = sqrt (c*(log nn)/n) + (w/n)*(if first then 1 else -1) +ucb1 :: Value -> Player -> Value -> Value -> Value -> Value +ucb1 !c !player !w !n !nn = sqrt (c*(log nn)/n) + (w/n)*(playerValue player) rollout :: (GameState a, RandomGen b) => a -> b -> (Value, b) rollout !gs !rand = if isJust tgs then (fromJust tgs, rand) else rollout gs' rand' where @@ -215,7 +223,8 @@ rollouts n gs rand = v + (rollouts (n-1) gs rand') where mctsSolver :: GameState a => MCParams -> a -> MCSolvedGame mctsSolver params gs = mkLeaf' params gs - +combineMCTS :: [MCSolvedGame] -> [(String, MCSolvedGame)] +combineMCTS = undefined -- For performace measuring only! diff --git a/Minmax.hs b/Minmax.hs index d02e423..33dcc2a 100644 --- a/Minmax.hs +++ b/Minmax.hs @@ -14,7 +14,7 @@ instance Show MMSolvedGame where show (MMSolvedGame {gameState}) = show gameState instance GameState MMSolvedGame where - firstplayer (MMSolvedGame {gameState}) = firstplayer gameState + player (MMSolvedGame {gameState}) = player gameState terminal (MMSolvedGame {gameState}) = terminal gameState maxdepth (MMSolvedGame {gameState}) = maxdepth gameState actions = actions' @@ -27,7 +27,7 @@ minmaxSolver gameState = MMSolvedGame {gameState, value = value', actions'} wher internal (str, ngs) = (str, minmaxSolver ngs) actions' = map internal $ actions gameState tval = terminal gameState - objective = if firstplayer gameState then maximum else minimum + objective = playerObjective $! player gameState value' = if isJust $ tval then fromJust tval else objective $ map (value . snd) $ actions' @@ -40,7 +40,7 @@ memominmax gameState = do then return MMSolvedGame {gameState, value = fromJust tval, actions' = []} else do actions' <- mapM internal $ actions gameState memo2 <- get - let objective = if firstplayer gameState then maximum else minimum + let objective = playerObjective $! player gameState cval = objective $ map (value . snd) $ actions' ngs = MMSolvedGame {gameState, value = cval, actions'} put $ M.insert gameState ngs memo2 diff --git a/README.md b/README.md index 36ebaf7..29639ec 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Games are instances of the class `GameState`. The minimal complete definition for this class must implement the following function: -* `firstplayer` - this Boolean is set to `True` if it's the first (maximizing) player's turn. +* `player` - the player whose current turn it is (either `Maximizer` or `Minimizer`). * `terminal` - this must be set to `Nothing` for all terminal nodes, and to `Just val` where `val` is the outcome of the game. * `actions` - these is a list of pairs `(name, result)` where `name` is the move's name, and `branch` is the is sub branch of the game that is reached with the move. diff --git a/SolverDefs.hs b/SolverDefs.hs index cd78583..c059740 100644 --- a/SolverDefs.hs +++ b/SolverDefs.hs @@ -4,7 +4,8 @@ , BangPatterns #-} -module SolverDefs (Value, GameState(..), SolvedGameState(..), +module SolverDefs (Value, GameState(..), SolvedGameState(..), Player(..), + playerValue, playerObjective, otherPlayer, randomAction, statelessSolver, randomSolver, humanSolver, interaction, humanInteraction) where @@ -12,10 +13,26 @@ import System.Random import System.IO import Data.Maybe +-- improvements: +-- 1. Allow games with chance nodes (backgammon?) + type Value = Double +data Player = Maximizer | Minimizer deriving (Eq, Ord) -- + +playerValue :: Player -> Value +playerValue Maximizer = 1 +playerValue Minimizer = -1 + +playerObjective :: (Foldable t, Ord a) => Player -> t a -> a +playerObjective Maximizer = maximum +playerObjective Minimizer = minimum + +otherPlayer :: Player -> Player +otherPlayer Maximizer = Minimizer +otherPlayer Minimizer = Maximizer class (Show gs) => GameState gs where - firstplayer :: gs -> Bool + player :: gs -> Player terminal :: gs -> Maybe Value actions :: gs -> [(String, gs)] numactions :: gs -> Int @@ -45,7 +62,7 @@ instance Show StatelessSolvedGame where show (StatelessSolvedGame {gameState}) = show gameState instance GameState StatelessSolvedGame where - firstplayer (StatelessSolvedGame {gameState}) = firstplayer gameState + player (StatelessSolvedGame {gameState}) = player gameState terminal (StatelessSolvedGame {gameState}) = terminal gameState actions = actions' diff --git a/connect4.hs b/connect4.hs index b9ebe40..127841e 100644 --- a/connect4.hs +++ b/connect4.hs @@ -23,7 +23,9 @@ instance Show BoardState where 1 -> "Mrs. Cross wins!" 0 -> "It's a draw!" (-1) -> "Mr. Knott wins!" - playermessage = if firstplayer gs then "It's Mrs. Cross' turn.\n" else "It's Mr. Knott's turn.\n" + playermessage = case player gs of + Maximizer -> "It's Mrs. Cross' turn.\n" + Minimizer -> "It's Mr. Knott's turn.\n" movemessage = "Possible moves are: " ++ (unwords $ map fst $ actions gs) ++ "\n" row n = intersperse ' ' [f $ content A.! (5-n, i) | i <- [0..6]] f Ex = 'X' @@ -34,7 +36,7 @@ movename :: Int -> String movename n = ("abcdefg" !! n) : [] instance GameState BoardState where - firstplayer = (== 0) . (flip mod 2) . totalmoves + player Board {totalmoves} = if mod totalmoves 2 == 0 then Maximizer else Minimizer terminal = terminal' actions !gs@(Board {heights}) = [(movename n, mkState gs n) | n <- [0..6], (heights A.! n) < 6] numactions = numactions' @@ -63,10 +65,10 @@ isWinner (!Board {content, heights}) !player !col = mkState :: BoardState -> Int -> BoardState mkState !gs@(Board {content, heights, totalmoves, numactions'}) !col = Board {content=con', heights=hei', totalmoves=tot', numactions'=num', terminal'=ter'} where - !first = firstplayer gs - !sqrtype = if first then Ex else Oh + (!sqrtype, !winval) = case mod totalmoves 2 of + 0 -> (Ex, Just 1) + 1 -> (Oh, Just (-1)) !height = heights A.! col - !winval = Just $! if first then 1 else -1 !draw = totalmoves == 41 !tot' = totalmoves + 1 !con' = content A.// [((height, col), sqrtype)] @@ -82,10 +84,11 @@ initial = Board {content = A.listArray ((0,0), (5, 6)) $ repeat None, terminal' = Nothing, numactions' = 7} -mymctssolver = mctsSolver defaultMCParams +mymctssolver = mctsSolver defaultMCParams {background=True} -main = putStrLn "" >> humanInteraction initial mymctssolver +-- main = putStrLn "" >> humanInteraction initial mymctssolver +main = putStrLn "\n\n\n" >> interaction initial mymctssolver mymctssolver >> main -- main = do -- x <- multitimed initial 2500 diff --git a/nim.hs b/nim.hs index b275885..06896af 100644 --- a/nim.hs +++ b/nim.hs @@ -5,19 +5,21 @@ import Data.Bits import System.Random import qualified Data.Set as Set -newtype NimGame = Nim (Bool, [Integer]) deriving Eq +newtype NimGame = Nim (Player, [Integer]) deriving Eq instance Show NimGame where - show (Nim (first, piles)) = (unwords $ map show piles) ++ message where + show (Nim (player', piles)) = (unwords $ map show piles) ++ message where message = "\nIt's " ++ pl ++ " player's turn\n" - pl = if first then "first" else "second" + pl = case player' of + Maximizer -> "first" + Minimizer -> "second" instance GameState NimGame where - firstplayer (Nim (first, _)) = first - terminal (Nim (first, piles)) = if null piles then Just (if first then (-1) else 1) else Nothing - actions (Nim (first, piles)) = [(movename (n, m), newng n m) | n <- uniq piles, m <- [0..n-1]] where + player (Nim (player', _)) = player' + terminal (Nim (player', piles)) = if null piles then Just (playerValue player') else Nothing + actions (Nim (player', piles)) = [(movename (n, m), newng n m) | n <- uniq piles, m <- [0..n-1]] where uniq = Set.toList . Set.fromList - newng n m = Nim (not first, filter (/= 0) $ a ++ [m] ++ b) where + newng n m = Nim (otherPlayer player', filter (/= 0) $ a ++ [m] ++ b) where (a, b) = fmap (drop 1) $ break (n ==) piles movename :: (Integer, Integer) -> String @@ -39,7 +41,7 @@ pilemaker _ strs = map read strs main = do args <- getArgs rand <- newStdGen - let initial = Nim (True, pilemaker rand args) + let initial = Nim (Maximizer, pilemaker rand args) putStrLn $ "\n\nWelcome to Nim!" putStrLn $ "Take any amount of stones from any pile, last player to move wins!" putStrLn $ "To take 'y' stones from a pile of size 'x', type 'x-y'\n\n" diff --git a/nim_euclidian.hs b/nim_euclidian.hs index 975c787..9b5e91a 100644 --- a/nim_euclidian.hs +++ b/nim_euclidian.hs @@ -7,23 +7,24 @@ import Control.Monad.State import System.Environment import System.Random -data NimGame = Nim {first :: Bool, +data NimGame = Nim {player' :: Player, message :: String, p1 :: Integer, p2 :: Integer} instance Eq NimGame where - Nim {first = f1, p1=p11, p2=p12} == Nim {first = f2, p1=p21, p2=p22} = (f1, p11, p12) == (f2, p21, p22) + Nim {player'=f1, p1=p11, p2=p12} == Nim {player'=f2, p1=p21, p2=p22} = (f1, p11, p12) == (f2, p21, p22) -gameOver :: Integer -> Bool -> String -gameOver n first = "Only one pile left: " ++ (show n) ++ movestr ++ winstr ++ "\n" where - movestr = "\nNo more moves for " ++ player first ++ ", " - winstr = player (not first) ++ " wins!" - player p = if p then "first" else "second" +gameOver :: Integer -> Player -> String +gameOver n player' = "Only one pile left: " ++ (show n) ++ movestr ++ winstr ++ "\n" where + movestr = "\nNo more moves for " ++ playerstr player' ++ ", " + winstr = playerstr player' ++ " wins!" + playerstr Maximizer = "second" + playerstr Minimizer = "first" instance Show NimGame where - show (Nim {first, p1 = 0, p2}) = gameOver p2 first - show (Nim {first, p1, p2 = 0}) = gameOver p1 first + show (Nim {player', p1 = 0, p2}) = gameOver p2 player' + show (Nim {player', p1, p2 = 0}) = gameOver p1 player' show (Nim {message, p1, p2}) = message ++ pilestr ++ movestr where pilestr = "The piles on the table are " ++ (show p1) ++ " and " ++ (show p2) ++ "\n" [a, b] = sort [p1, p2] @@ -41,20 +42,20 @@ nimsolver Nim {p1, p2} = show play where play = if q == 1 then 1 else if m == 0 then q else if ratio1 < ratio2 then q else q-1 instance GameState NimGame where - firstplayer = first - terminal (Nim {first, p1 = 0}) = Just $ if first then (-1) else 1 - terminal (Nim {first, p2 = 0}) = Just $ if first then (-1) else 1 + player = player' + terminal (Nim {player', p1 = 0}) = Just $ playerValue player' + terminal (Nim {player', p2 = 0}) = Just $ playerValue player' terminal _ = Nothing - actions (Nim {first, p1, p2}) + actions (Nim {player', p1, p2}) | p1 > p2 = [(show n, newng (p1,n,p2) (p1-n*p2) p2) | n <- [1..quot p1 p2]] | otherwise = [(show n, newng (p2,n,p1) p1 (p2-n*p1)) | n <- [1..quot p2 p1]] where - newng tup p1 p2 = Nim {first = not first, message = msg tup, p1, p2} + newng tup p1 p2 = Nim {player' = otherPlayer player', message = msg tup, p1, p2} msg (big, q, small) = (show big) ++ " - " ++ (show q) ++ "*" ++ (show small) ++ " = " ++ (show $ big-q*small) ++ "\n" initial :: Integer -> Integer -> NimGame -initial p1 p2 = Nim {first = True, message = "", p1, p2} +initial p1 p2 = Nim {player' = Maximizer, message = "", p1, p2} boundary :: [Integer] -> [Integer] boundary [] = boundary [10000] diff --git a/nim_fibonacci.hs b/nim_fibonacci.hs index ba6949b..421b07b 100644 --- a/nim_fibonacci.hs +++ b/nim_fibonacci.hs @@ -6,28 +6,28 @@ import Data.Ratio import System.Environment import System.Random -data NimGame = Nim {first :: Bool, +data NimGame = Nim {player' :: Player, limit :: Integer, pile :: Integer} deriving Eq instance Show NimGame where - show (Nim {first = False, pile = 0}) = "Second player has no more moves.\nFirst player wins!" - show (Nim {first = True, pile = 0}) = "First player has no more moves.\nSecond player wins!" - show (Nim {limit, pile, first}) = pilestr ++ playerstr ++ movestr where + show (Nim {player' = Maximizer, pile = 0}) = "Second player has no more moves.\nFirst player wins!" + show (Nim {player' = Minimizer, pile = 0}) = "First player has no more moves.\nSecond player wins!" + show (Nim {limit, pile, player'}) = pilestr ++ playerstr ++ movestr where pilestr = "There are " ++ (show pile) ++ " stones in the pile.\n" movestr = if limit < pile then "You can take at most " ++ (show limit) ++ " of them...\n" else "You can take as many of them as you'd like!\n" - playerstr = if first - then "First player's turn to make a move.\n" - else "Second player's turn to make a move.\n" + playerstr = case player' of + Maximizer -> "First player's turn to make a move.\n" + Minimizer -> "Second player's turn to make a move.\n" instance GameState NimGame where - firstplayer = first - terminal (Nim {first, pile = 0}) = Just $ if first then (-1) else 1 + player = player' + terminal (Nim {player', pile = 0}) = Just $ playerValue player' terminal _ = Nothing - actions (Nim {first, pile, limit}) = [(show n, newnim n) | n <- [1..limit]] where - newnim n = Nim {first = not first, pile = pile-n, limit = min (pile-n) (2*n)} + actions (Nim {player', pile, limit}) = [(show n, newnim n) | n <- [1..limit]] where + newnim n = Nim {player' = otherPlayer player', pile = pile-n, limit = min (pile-n) (2*n)} fibonacci :: [Integer] fibonacci = 0 : 1 : zipWith (+) fibonacci (tail fibonacci) @@ -55,7 +55,7 @@ nimsolver2 (Nim {pile = p, limit = l}) = if null wins then "1" else show $ head isgood n = (last $ zeckendorf $ p-n) > 2*n initial :: Integer -> NimGame -initial p = Nim {first = True, limit = p-1, pile = p} +initial p = Nim {player' = Maximizer, limit = p-1, pile = p} boundary :: [Integer] -> [Integer] boundary [] = boundary [100] diff --git a/ttt.hs b/ttt.hs index c865201..16e1398 100644 --- a/ttt.hs +++ b/ttt.hs @@ -21,11 +21,13 @@ instance Show BoardState where 1 -> "Mrs. Cross wins!" 0 -> "It's a draw!" (-1) -> "Mr. Knott wins!" - playermessage = if firstplayer gs then "It's Mrs. Cross' turn.\n" else "It's Mr. Knott's turn.\n" + playermessage = case player gs of + Maximizer -> "It's Mrs. Cross' turn.\n" + Minimizer -> "It's Mr. Knott's turn.\n" movemessage = "Possible moves are " ++ (unwords $ map fst $ actions gs) ++ "\n" instance GameState BoardState where - firstplayer (Board (n, _, _)) = mod n 2 == 0 + player (Board (n, _, _)) = if mod n 2 == 0 then Maximizer else Minimizer terminal (Board (_, _, v)) = v actions gs@(Board (_, _, v)) = catMaybes [fmap ((,) (show (n+1))) (mkState gs n) | n <- [0..8]] @@ -45,9 +47,11 @@ isWinner player pos (Board (_, xs, _)) = or [and $ map f comps | comps <- comple mkState :: BoardState -> Int -> Maybe BoardState mkState gs@(Board (l, b, v)) n = if b A.! n /= None then Nothing else let - first = firstplayer gs - sqrtype = if first then Ex else Oh - winval = Just $ if first then 1 else -1 + player' = player gs + sqrtype = case player' of + Maximizer -> Ex + Minimizer -> Oh + winval = Just $ playerValue player' nb = b A.// [(n, sqrtype)] winner = isWinner sqrtype n gs next x = Just $ Board (l+1, nb, x) diff --git a/utt.hs b/utt.hs index 726a13a..c8ad445 100644 --- a/utt.hs +++ b/utt.hs @@ -16,7 +16,7 @@ type Miniboard = A.Array Int Square type MiniPlus = Either Square (Int, Miniboard) type Bigboard = A.Array Int MiniPlus -newtype BoardState = Board (Bool, Bigboard, Maybe Int, Maybe Value) deriving (Eq, Ord) +newtype BoardState = Board (Player, Bigboard, Maybe Int, Maybe Value) deriving (Eq, Ord) miniShow :: Bool -> MiniPlus -> [String] miniShow _ (Left Ex) = ["\\ /", " X ", "/ \\"] @@ -45,7 +45,9 @@ instance Show BoardState where 1 -> "Mrs. Cross wins!" 0 -> "It's a draw!" (-1) -> "Mr. Knott wins!" - playermessage = if firstplayer gs then "It's Mrs. Cross' turn.\n" else "It's Mr. Knot's turn.\n" + playermessage = case player gs of + Maximizer -> "It's Mrs. Cross' turn.\n" + Minimizer -> "It's Mr. Knot's turn.\n" movemessage = "Possible moves are " ++ (unwords $ map fst $ actions gs) ++ "\n" movename :: (Int, Int) -> String @@ -54,7 +56,7 @@ movename !(a, b) = [x, y] where y = ("123456789" !!) $ 3*(quot a 3) + (quot b 3) instance GameState BoardState where - firstplayer !(Board (!first, _, _, _)) = first + player !(Board (!player', _, _, _)) = player' terminal !(Board (_, _, _, !v)) = v actions !gs@(Board (_, _, Just !pos, _)) = miniActions gs $! pos actions !gs@(Board (_, !bb, Nothing, _)) = concatMap (miniActions gs) [0..8] @@ -89,9 +91,11 @@ miniActions !gs@(Board (_, !bb, _, _)) x = internal !y = (movename (x, y), mkState gs x y) mkState :: BoardState -> Int -> Int -> BoardState -mkState !gs@(Board (!first, !bb, _, _)) !x !y = Board (not first, nbb, nextpos, terminal) where +mkState !gs@(Board (!player', !bb, _, _)) !x !y = Board (otherPlayer player', nbb, nextpos, terminal) where !(!cnt, !rbbx) = fromRight undefined $! bb A.! x - !sqrtype = if first then Ex else Oh + !sqrtype = case player' of + Maximizer -> Ex + Minimizer -> Oh !winner = cnt > 2 && isMiniWinner sqrtype y rbbx !draw = cnt == 8 !nmb = if winner then Left sqrtype else @@ -102,12 +106,12 @@ mkState !gs@(Board (!first, !bb, _, _)) !x !y = Board (not first, nbb, nextpos, bigdraw = all isLeft $ A.elems nbb !terminal = if winner then if bigwinner - then if first then Just 1 else Just (-1) + then Just $! playerValue $! player' else if bigdraw then Just 0 else Nothing else if draw && bigdraw then Just 0 else Nothing initial :: BoardState -initial = Board (True, array8 $ Right $ (0, array8 None), Nothing, Nothing) where +initial = Board (Maximizer, array8 $ Right $ (0, array8 None), Nothing, Nothing) where array8 x = A.listArray (0, 8) $ repeat x @@ -124,9 +128,11 @@ symmetries = [["e5"], ["d4", "f4", "d6", "f6"], ["e4", "d5", "f5", "e6"], -- main = putStrLn "\n" >> interaction initial randomSolver randomSolver -mymctssolver1 = mctsSolver defaultMCParams +mymctssolver = mctsSolver defaultMCParams {background=False} -main = putStrLn " ">> humanInteraction initial mymctssolver1 +-- main = putStrLn " ">> humanInteraction initial mymctssolver1 + +main = putStrLn "\n\n\n" >> interaction initial mymctssolver mymctssolver >> main -- main = do