{-# LANGUAGE RecordWildCards #-}
module GameData where
import qualified Data.DAWG.Packed as D
import Data.Array.Unboxed
import Data.Int
import Data.Char
import Data.Ix
import Data.Bits
import Data.List
import Control.Arrow
import Control.DeepSeq
-- ***************** Cell data ********************
wildcard = '_'
type CellIndex = (Int32, Int32)
type PrefixData = (D.Node, String, (Int, String))
data Cell = Filled !Char | Anchor {crossScore :: !Int, lset:: !LetterSet} | Empty deriving (Eq, Show)
data Direction = V | H deriving (Eq, Show)
data Play = Play {direction :: !Direction, location :: !CellIndex, score :: !Int, word :: !String} deriving (Eq, Show)
instance NFData Direction where
rnf = const ()
instance NFData Play where
rnf (Play d l s w) = rnf d `seq` rnf l `seq` rnf s `seq` rnf w `seq` ()
tableBounds = ((1,1), (15,15)) :: (CellIndex, CellIndex)
inBounds = inRange tableBounds
outOfBounds = not . inBounds
stepIndex direction = takeWhile inBounds . iterate direction
[stepUp, stepDown, stepLeft, stepRight] = map stepIndex [pred *** id, succ *** id, id *** pred, id *** succ]
isEmpty Empty = True
isEmpty _ = False
isFilled (Filled _) = True
isFilled _ = False
isAnchor (Anchor {..}) = True
isAnchor _ = False
-- ****************** Scoring *********************
letterScores :: UArray Int Int
letterScores = array (ord 'A', ord 'Z') $ map (first ord) $
[('E', 1), ('A', 1), ('I', 1), ('O', 1), ('N', 1), ('R', 1), ('T', 1), ('L', 0), ('S', 1), ('U', 1),
('D', 2), ('G', 2), ('B', 3), ('C', 3), ('M', 3), ('P', 3), ('F', 4), ('H', 4),
('V', 4), ('W', 4), ('Y', 4), ('K', 5), ('J', 8), ('X', 8), ('Q', 10), ('Z', 10)]
pieceScore :: Char -> Int
pieceScore c | isLower c = 0
| otherwise = letterScores ! (ord c)
data Bonus = Nil | LS2 | LS3 | WS2 | WS3 deriving (Enum, Eq, Show)
bonusTable :: Array CellIndex Bonus
bonusTable = listArray tableBounds $ concatMap (map toEnum) $ (\x-> x ++ (reverse $ init x)) [
[4, 0, 0, 1, 0, 0, 0, 4, 0, 0, 0, 1, 0, 0, 4],
[0, 3, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 3, 0],
[0, 0, 3, 0, 0, 0, 1, 0, 1, 0, 0, 0, 3, 0, 0],
[1, 0, 0, 3, 0, 0, 0, 1, 0, 0, 0, 3, 0, 0, 1],
[0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0],
[0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0],
[0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0],
[4, 0, 0, 1, 0, 0, 0, 3, 0, 0, 0, 1, 0, 0, 4]]
-- ***************** LetterSet ********************
newtype LetterSet = LSet Int32 deriving (Eq)
hasLetter (LSet x) l = testBit x (ord l - ord 'A')
setLetter (LSet x) l = LSet $ setBit x (ord l - ord 'A')
fromList = foldl' setLetter (LSet 0)
fullLSet = fromList $ wildcard:['A'..'Z']
instance Show LetterSet where
show x = "{LSet " ++ (filter (hasLetter x) (wildcard:['A'..'Z'])) ++ "}"
-- ************************************************
parseTable :: [String] -> D.Node -> Array CellIndex Cell
parseTable table dawg | check table = listArray tableBounds $ map go $ assocs table'
| otherwise = error "Table must be 15x15" where
table' :: Array CellIndex Char
table' = listArray tableBounds $ concat table
check t = all (==15) $ length t: map length t
neighs (i,j) = [table' ! i'| (di, dj) <- [(-1, 0), (1, 0), (0, -1), (0, 1)],
let i' = (i + di, j + dj), inBounds i']
go (i, c) | isLetter c = Filled c
| all (==' ') $ neighs i = Empty
| otherwise = Anchor crsScore lset where
step = takeWhile (/=' ') . map (toUpper . (table'!)) . tail
fromUp = reverse $ step $ stepUp i
fromDown = step $ stepDown i
crsScore = sum $ map (sum . map pieceScore) [fromUp, fromDown]
lset | null (fromUp ++ fromDown) = fullLSet
| otherwise = fromList $ wildcard: [c | c <- ['A'..'Z'],
D.elem (fromUp ++ c:fromDown) dawg]