packages feed

Verba 0.1.0.0 → 0.1.0.1

raw patch · 7 files changed

+228/−1 lines, 7 files

Files

Verba.cabal view
@@ -1,5 +1,5 @@ name:                Verba-version:             0.1.0.0+version:             0.1.0.1 synopsis:            A solver for the WordBrain game description:         A solver for the WordBrain game license:             MIT@@ -16,6 +16,12 @@  executable Verba     main-is:            Main.hs+    other-modules:      Verba.CLI,+                        Verba.Dictionary,+                        Verba.Formatting,+                        Verba.Puzzle,+                        Verba.Solver,+                        Verba.Utils     build-depends:      base >= 4.8 && < 4.9,                         matrix >= 0.3 && < 0.4,                         containers >= 0.5 && < 0.6
+ src/Verba/CLI.hs view
@@ -0,0 +1,57 @@+module Verba.CLI (runCLI) where++import Data.List (intersperse, intercalate)+import System.IO (hFlush, stdout)+import Verba.Solver (solve)+import Verba.Dictionary (Dictionary)+import Verba.Puzzle (Puzzle)+import Verba.Formatting+import Verba.Utils+import qualified Verba.Dictionary as Dictionary+import qualified Verba.Puzzle as Puzzle++runCLI :: FilePath -> IO ()+runCLI dictFile = do+    putStrLn $ blue "Matrix (row by row):"+    puz <- Puzzle.ask+    putStr $ blue "Word lengths (separated by space): "+    hFlush stdout+    lengths <- getLine >>= (return . map read . words)+    putStr $ blue "Known correct words (separated by space): "+    hFlush stdout+    knownWords <- getLine >>= (return . words)+    dict <- Dictionary.load (maximum lengths) dictFile+    let sols = solve dict lengths puz+    putStrLn "---------------------------------"+    let initFilter = filter (supersetOf knownWords) sols+    confirmSolutions (length lengths) knownWords initFilter++putGuess :: String -> [String] -> [String] -> IO ()+putGuess guessedWord correctOnes guess = do+    let styledWords = map (\w -> if (w `elem` correctOnes)+            then green w+            else if w == guessedWord+                then orange w+                else w) guess+    putStr $ (intercalate " " styledWords) ++ "? "++-- Takes a list of known solutions and a +-- list of grouped solutions (pair of first word and +-- rest of the solutions) and returns the list of confirmed solutions+-- going deep as long as the user confirms.+-- Once all options are exhausted, it returns back the list+-- of confirmed solutions up until now.+confirmSolutions :: Int -> [String] -> [[String]] -> IO ()+confirmSolutions _ _ [] = return ()+confirmSolutions n correctOnes (sol:sols) = do+    let guessedWord = head $ filter (not . (`elem` correctOnes)) sol+    putGuess guessedWord correctOnes sol+    hFlush stdout+    answer <- getLine+    if answer == "y"+        then +            let newCorrectOnes = guessedWord:correctOnes in+            if length newCorrectOnes == n+                then putStrLn "My job here is done! :)"+                else confirmSolutions n newCorrectOnes (filter (elem guessedWord) sols)+        else confirmSolutions n correctOnes (filter (not . elem guessedWord) sols)
+ src/Verba/Dictionary.hs view
@@ -0,0 +1,28 @@+module Verba.Dictionary where++import Data.Map (Map)+import Data.Maybe (fromMaybe)+import qualified Data.Map as Map+import System.IO++type Dictionary = Map Int [String]++-- Pushes a word in the right bucket+-- based on its length.+insertWord :: String -> Dictionary -> Dictionary+insertWord w = Map.alter fn (length w)+    where fn Nothing = Just []+          fn (Just lst) = Just $ w : lst++-- Loads the dictionary so that only words up to+-- the specified length are loaded.+load :: Int -> FilePath -> IO Dictionary+load n file = withFile file ReadMode $ (\handle -> do+    hSetEncoding handle latin1+    contents <- hGetContents handle+    let wordList = takeWhile ((<= n) . length) $ words contents+    return $! foldr insertWord Map.empty wordList)++-- Gets all the words with a certain length.+wordsWithLength :: Int -> Dictionary -> [String]+wordsWithLength n = fromMaybe [] . Map.lookup n
+ src/Verba/Formatting.hs view
@@ -0,0 +1,10 @@+module Verba.Formatting where++blue :: String -> String+blue str = "\o33[1;34m" ++ str ++ "\o33[0m"++green :: String -> String+green str = "\o33[0;32m" ++ str ++ "\o33[0m"++orange :: String -> String+orange str = "\o33[0;33m" ++ str ++ "\o33[0m"
+ src/Verba/Puzzle.hs view
@@ -0,0 +1,96 @@+module Verba.Puzzle (Puzzle, consume, ask, fromLists) where++import Data.Matrix (Matrix, (!), setElem, ncols, nrows)+import qualified Data.Matrix as Matrix+import Data.List (intercalate)+import Data.Maybe (fromMaybe)+import Control.Monad (replicateM)++-- The size of the puzzle needs to be at least 2x2.+newtype Puzzle = Puzzle { getMatrix :: Matrix (Maybe Char) }++instance Show Puzzle where+    show = map (fromMaybe ' ') . intercalate [Just '\n'] . Matrix.toLists . getMatrix++-- Moves the element in the specified position+-- down one cell and puts nothing in its place if the+-- current element has something and the cell below doesn't.+sortWithBelow :: (Int, Int) -> Puzzle -> Puzzle+sortWithBelow current@(i, j) puz@(Puzzle mat) = +    let below = (i + 1, j) in+    if mat ! current /= Nothing && mat ! below == Nothing then+        Puzzle $ setElem Nothing current $ setElem (mat ! current) below mat+    else puz++-- If the above cell contains something and the below+-- one doesn't, it drops the above cell down+fixColumnCell :: (Int, Int) -> Puzzle -> Puzzle+fixColumnCell (1, _) puz = puz +fixColumnCell (i, j) puz =+    let calcIx x = (x, j) in+    foldr sortWithBelow puz (reverse . map calcIx $ [1..(i - 1)])++-- Takes a column index and applies gravity+-- to the column from the bottom up. +fixColumn :: Int -> Puzzle -> Puzzle+fixColumn j puz@(Puzzle mat) = +    let calcIx i = (i, j) in+    foldr fixColumnCell puz (reverse . map calcIx $ [1..nrows mat])++-- Drops a character if the underlying cell+-- is empty.+applyGravity :: Puzzle -> Puzzle+applyGravity puz@(Puzzle mat) = foldr fixColumn puz [1..ncols mat]++-- Function that takes a puzzle and a position+-- and returns all valid neightbours from that position.+getNeightbours :: (Int, Int) -> Puzzle -> [(Int, Int)]+getNeightbours (i, j) (Puzzle puz) =+    let allNeightbours = [(i - 1, j - 1), (i - 1, j), (i - 1, j + 1),+                          (i    , j - 1),             (i    , j + 1),+                          (i + 1, j - 1), (i + 1, j), (i + 1, j + 1)] in+    filter isValid allNeightbours+        where isValid (i, j) = i > 0 && i <= nrows puz && j > 0 && j <= ncols puz++-- Generates a list of all indices in the+-- puzzle matrix.+allPositions :: Puzzle -> [(Int, Int)]+allPositions (Puzzle puz) = +    let (r, c) = (nrows puz, ncols puz) in+    map (fn c) [0..(r * c) - 1]+        where fn nc i = ((i `div` nc) + 1, (i `mod` nc) + 1)++-- Consumes a character in the specified location+-- and drops the characters above that one.+consumeChar :: (Int, Int) -> Puzzle -> Puzzle+consumeChar ix (Puzzle puz) = Puzzle $ setElem Nothing ix puz++-- Tries to consume the string starting at the +-- specified position.+consumeAt :: String -> Puzzle -> (Int, Int) -> [Puzzle]+consumeAt (ch : rest) (Puzzle puz) ix =+    if (puz ! ix) == (Just ch) then +        let newPuz = consumeChar ix (Puzzle puz)+            neightbours = getNeightbours ix (Puzzle puz) in+        if rest /= [] then+            concatMap (consumeAt rest newPuz) neightbours+        else [newPuz]+    else []++-- Tries to consume the string and returns+-- a list of resulting puzzles from the consumption+-- of said string.+consume :: String -> Puzzle -> [Puzzle]+consume str puz = map applyGravity $ concatMap (consumeAt str puz) (allPositions puz)++-- Asks for a matrix in the form of +-- rows of strings.+ask :: IO Puzzle+ask = do+    hd <- getLine+    tl <- replicateM (length hd - 1) getLine+    return . Puzzle . Matrix.fromLists . map (map Just) $ hd : tl++-- Constructs a puzzle from a list of list+-- of characters.o+fromLists = Puzzle . Matrix.fromLists
+ src/Verba/Solver.hs view
@@ -0,0 +1,24 @@+module Verba.Solver (solve) where++import Data.List (permutations)+import Verba.Dictionary (Dictionary)+import Verba.Puzzle (Puzzle)+import qualified Verba.Dictionary as Dictionary+import qualified Verba.Puzzle as Puzzle++-- Reduces to a list of puzzles starting from+-- the given puzzle.+reduce :: Dictionary -> [Int] -> (Puzzle, [String]) -> [(Puzzle, [String])]+reduce _ [] puz = [puz]+reduce dict (i:is) (puz, ws) =+    let fn w = map (\x -> (x, w:ws)) (Puzzle.consume w puz)+        puzs = concatMap fn (Dictionary.wordsWithLength i dict) in+    concatMap (reduce dict is) puzs+    ++-- Takes a list of word lengths, and a puzzle and+-- generates all strings that result in an empty puzzle.+solve :: Dictionary -> [Int] -> Puzzle -> [[String]]+solve dict is puz = +    let allLengths = permutations is in+    concatMap (\ls -> map (reverse . snd) (reduce dict ls (puz, []))) allLengths
+ src/Verba/Utils.hs view
@@ -0,0 +1,6 @@+module Verba.Utils where++-- Checks if the first list is contained in the+-- second list.+supersetOf :: (Eq a) => [a] -> [a] -> Bool+supersetOf set maybeSs = all (\x -> x `elem` maybeSs) set