summaryrefslogtreecommitdiff
path: root/8puzzle/Puzzle.hs
blob: ce6fcdb9efa81ce289039a579cc4dec92f37d751 (plain)
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
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