summaryrefslogtreecommitdiff
path: root/puzzle.hs
blob: 0401dc3b46c08f74db90b6424b3d30c5d2dd067e (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
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
module Main where

import Data.Array (Array, array, bounds, (!), (//))
import Data.Functor ((<$>))
import Data.Maybe
import Data.List (sortBy)
import Data.Ord (comparing)
import Data.Char (toUpper)
import System.Random (RandomGen, getStdGen, split, randomR, randomRs)
import System.Random.Shuffle

-- Vectors to represent position and orientation.
data Vec = Vec { x::Int, y::Int } deriving (Show, Eq)

origin = Vec 0 0
left = Vec (-1) 0
right = Vec 1 0
up = Vec 0 (-1)
down = Vec 0 1

add :: Vec -> Vec -> Vec
add a b = Vec (x a + x b) (y a + y b)

index :: Vec -> (Int, Int)
index p = (x p, y p)

-- Pose is a tuple of position and orientation.
data Pose = Pose { position::Vec, orientation::Vec } deriving (Show, Eq)

advance :: Pose -> Pose
advance p = p {position = add (position p) (orientation p)}

-- We just use Char for each field.
empty = '.'

-- Board is an immutable array.
type Board = Array (Int, Int) Char

showBoard :: Board -> String
showBoard arr =
  unlines $ map (map (arr !)) indices
  where indices = [[(x, y) | x <- [sX..eX]] | y <- [sY..eY]]
        ((sX, sY), (eX, eY)) = bounds arr

mkBoard :: (Int, Int) -> Array (Int, Int) Char
mkBoard (d, e) = array ((0, 0), (m, n)) [((x, y), empty) | x<-[0..m], y<-[0..n]]
  where m = d-1
        n = e-1

charAt :: Board -> Vec -> Maybe Char
charAt b p =
    if x p >= 0 && y p >= 0 && x p <= eX && y p <= eY then
        Just (b ! index p)
    else
        Nothing
  where ((sX, sY), (eX, eY)) = bounds b

-- Test whether it's possible to insert a string at the given pose.
-- Return the associated cost or Nothing, if the string didn't fit.
tryInsert :: String -> Pose -> Board -> Maybe Int
tryInsert [] _ _ = Just 0
tryInsert (w:ws) p b =
  case charAt b (position p) of
    Nothing -> Nothing
    Just c ->
        if c /= empty && c /= w then
            Nothing
        else do
          t <- tryInsert ws (advance p) b
          return ((if c == w then 0 else 1) + t)

-- Given a string and a pose, return a list of updates for a board to
-- insert the string.
mkPatch :: String -> Pose -> [((Int, Int), Char)]
mkPatch [] _ = []
mkPatch (w:ws) p = (index $ position p, w) : mkPatch ws (advance p)

-- Actually insert the string returning a new board.
insert :: String -> Pose -> Board -> Maybe Board
insert [] _ b = Just b
insert w p b =
  if isNothing (tryInsert w p b) then
    Nothing
  else
    Just (b // mkPatch w p)

insertAll :: RandomGen gen => gen -> [String] -> Board -> Maybe Board
insertAll _ [] b = Just b
insertAll rnd (w:ws) b = do
  b' <- insert w pose b
  insertAll rnd' ws b'
  where
    pose = fst (sortBy (comparing snd) (map (\t -> (fst t, fromJust $ snd t)) weightedMoves) !! nth)
    (nth, rnd') = randomR (0, min 10 (length weightedMoves)) rnd
    weightedMoves = filter (\x -> case snd x of Just x' -> x' > 0; Nothing -> False) [(po, tryInsert w po b) | po <- posOrientation']
    posOrientation' = shuffle' posOrientation (length posOrientation) rnd
    posOrientation = [Pose (Vec x y) o | x <- [sX..eX], y <- [sY..eY], o <- [right, down]]
    ((sX, sY), (eX, eY)) = bounds b

fill :: RandomGen gen => gen -> Board -> Board
fill rnd b = b // zip [(x, y) | x <- [sX..eX], y <- [sY..eY], (b ! (x, y)) == empty] (randomRs ('A', 'Z') rnd)
     where
       ((sX, sY), (eX, eY)) = bounds b

main :: IO ()
main = do
  rnd <- getStdGen
  c <- getContents
  let guests = lines (map toUpper c) in
    case (showBoard . fill rnd) <$> insertAll rnd guests (mkBoard (40, 30)) of
      Just s -> putStr s
      Nothing -> putStr "goesnt"