diff options
author | Kjetil Orbekk <kjetil.orbekk@gmail.com> | 2016-03-27 23:57:50 +0200 |
---|---|---|
committer | Kjetil Orbekk <kjetil.orbekk@gmail.com> | 2016-03-28 00:48:57 +0200 |
commit | bb67830de2f7fbaf2b95245cbb37f09d841f6790 (patch) | |
tree | efbea50b980a6e3dcec1b1e77ae35c432128ca53 | |
parent | 475872efcb9bc8496cedcbf4f978fab44402c046 (diff) |
8 puzzle solver in haskell.
-rw-r--r-- | 8puzzle/Puzzle.hs | 93 | ||||
-rw-r--r-- | 8puzzle/PuzzleTest.hs | 37 |
2 files changed, 130 insertions, 0 deletions
diff --git a/8puzzle/Puzzle.hs b/8puzzle/Puzzle.hs new file mode 100644 index 0000000..ce6fcdb --- /dev/null +++ b/8puzzle/Puzzle.hs @@ -0,0 +1,93 @@ +module Puzzle where + +import Data.List (intercalate) +import Data.Graph.AStar +import qualified Data.Vector as V +import qualified Data.Set as S +import Control.Monad +import Data.Maybe +import Text.Printf +import Debug.Trace + +data Puzzle = Puzzle { pSize :: Int + , pBoard :: V.Vector Int + , pTwin :: Bool } + | Start (S.Set Puzzle) + deriving (Ord, Eq) +instance Show Puzzle where + show p = showBoard p + +showBoard :: Puzzle -> String +showBoard (Start xs) = concatMap s' $ S.toList xs + where s' p = description p ++ showBoard p + description p | pTwin p = "Twin:\n" + | otherwise = "Board:\n" +showBoard p = concatMap (showRow) (expandRows p) + where expandRows = takeWhile (not . null) . + map (take size) . iterate (drop size) . + V.toList . pBoard + showRow r = concatMap (printf "%4d") r ++ "\n" + size = pSize p + +column, row :: Puzzle -> Int -> Int +column p x = x `mod` pSize p +row p x = x `div` pSize p + +distance :: Puzzle -> Int +distance (Start xs) = 1 +distance p = V.sum . V.imap d $ pBoard p + where d i x = if x == 0 then 0 + else abs (c i - c x') + abs (r i - r x') + where x' = x - 1 + c = column p + r = row p + +findEmpty :: Puzzle -> Int +findEmpty p = fromJust $ V.findIndex (==0) (pBoard p) + +swap :: Puzzle -> Int -> Int -> Maybe Puzzle +swap p e n = do + x <- pBoard p V.!? n + when (column p n /= column p e && + row p n /= row p e) Nothing + let b' = pBoard p V.// [(e, x), (n, 0)] + return (p { pBoard = b'}) + +neighbors :: Puzzle -> S.Set Puzzle +neighbors (Start xs) = xs +neighbors p = S.fromList . catMaybes $ swap' <$> ns <*> [p] + where e = findEmpty p + swap' n p = swap p e n + ns = [e - 1, e + 1, e - pSize p, e + pSize p] + +wrapWithTwin :: Puzzle -> Puzzle +wrapWithTwin (Start _) = error "Already wrapped" +wrapWithTwin p = Start $ S.fromList [p, p'] + where p' = p { pBoard = pBoard p V.// [(i1, e2), (i2, e1)], pTwin = True } + (i1, i2) = (0, pSize p + 1) + (e1, e2) = (pBoard p V.! i1, pBoard p V.! i2) + +solve :: Puzzle -> [Puzzle] +solve p = case s of + Just xs -> xs + Nothing -> error "Could not find solution" + + where s = aStar neighbors (const (const 1)) distance ((==0) . distance) p' + p' = wrapWithTwin p + +run p = do + putStrLn "Board:" + putStrLn . show $ (wrapWithTwin p) + let s = solve p + if pTwin (s !! 0) + then putStrLn "No solution. Showing twin:" + else putStrLn "Solution:" + mapM_ (putStrLn . show) (solve p) + +main = do + let example1 = Puzzle 3 (V.fromList [1, 2, 3, 4, 0, 5, 6, 7, 8]) False + let example2 = Puzzle 3 (V.fromList [2, 1, 3, 4, 0, 5, 6, 7, 8]) False + run example1 + run example2 + + diff --git a/8puzzle/PuzzleTest.hs b/8puzzle/PuzzleTest.hs new file mode 100644 index 0000000..cbd88af --- /dev/null +++ b/8puzzle/PuzzleTest.hs @@ -0,0 +1,37 @@ +import Test.Framework (defaultMain, testGroup) +import Test.Framework.Providers.QuickCheck2 (testProperty) +import Test.Framework.Providers.HUnit +import Test.HUnit +import Test.QuickCheck +import Test.QuickCheck.Gen +import qualified Data.Vector as V + +import Puzzle hiding (main) + +instance Arbitrary Puzzle where + arbitrary = do + size <- choose (1, 10) + board <- shuffle [0 .. (size*size - 1)] + return $ Puzzle size (V.fromList board) False + +tests = [ + testGroup "Board" + [ testProperty "neighbors have distance of 1" prop_neighborDistance, + testCase "basic distance" test_distance + ] + ] + +test_distance = distance puzzle @?= 4 + where puzzle = Puzzle 2 (V.fromList [1, 3, 2, 0]) False + +prop_neighborDistance p = all validNeighbor (neighbors p) + where validNeighbor n = abs (distance p - distance n) == 1 + +example = Puzzle 2 (V.fromList [1, 3, 2, 0]) False + +main = do + print example + putStrLn "Neighbors:" + mapM_ print (neighbors example) + putStrLn "" + defaultMain tests |