packages feed

wordify 0.6.0.0 → 0.7.0.0

raw patch · 17 files changed

+253/−250 lines, 17 filesdep ~HUnitdep ~QuickCheckdep ~arrayPVP ok

version bump matches the API change (PVP)

Dependency ranges changed: HUnit, QuickCheck, array, bytestring, conduit, containers, directory, errors, filepath, listsafe, mtl, parsec, random, safe, semigroups, split, test-framework, test-framework-hunit, test-framework-quickcheck2, text, transformers, unordered-containers

API changes (from Hackage documentation)

- Wordify.Rules.Move: restoreGame :: Game -> NonEmpty Move -> Either ScrabbleError (NonEmpty GameTransition)
- Wordify.Rules.Move: restoreGameLazy :: Game -> NonEmpty Move -> NonEmpty (Either ScrabbleError GameTransition)
- Wordify.Rules.ScrabbleError: CannotExchangeWhenNoLettersInBag :: ScrabbleError
- Wordify.Rules.ScrabbleError: CannotPlaceBlankWithoutLetter :: Pos -> ScrabbleError
- Wordify.Rules.ScrabbleError: DictionaryFileNotFound :: FilePath -> ScrabbleError
- Wordify.Rules.ScrabbleError: DoesNotConnectWithWord :: ScrabbleError
- Wordify.Rules.ScrabbleError: DoesNotCoverTheStarTile :: ScrabbleError
- Wordify.Rules.ScrabbleError: GameNotInProgress :: ScrabbleError
- Wordify.Rules.ScrabbleError: InvalidTileLetters :: Pos -> String -> ScrabbleError
- Wordify.Rules.ScrabbleError: LetterBagFileNotOpenable :: String -> ScrabbleError
- Wordify.Rules.ScrabbleError: MalformedDictionaryFile :: String -> ScrabbleError
- Wordify.Rules.ScrabbleError: MalformedLetterBagFile :: FilePath -> String -> ScrabbleError
- Wordify.Rules.ScrabbleError: MiscError :: String -> ScrabbleError
- Wordify.Rules.ScrabbleError: MisplacedLetter :: Pos -> ScrabbleError
- Wordify.Rules.ScrabbleError: NoTilesPlaced :: ScrabbleError
- Wordify.Rules.ScrabbleError: NotAssignableToBlank :: Pos -> String -> ValidBlankValues -> ScrabbleError
- Wordify.Rules.ScrabbleError: NotEnoughLettersInStartingBag :: Int -> ScrabbleError
- Wordify.Rules.ScrabbleError: PlacedTileOnOccupiedSquare :: Pos -> Tile -> ScrabbleError
- Wordify.Rules.ScrabbleError: PlayerCannotExchange :: LettersOnRack -> [Tile] -> ScrabbleError
- Wordify.Rules.ScrabbleError: PlayerCannotPlace :: LettersOnRack -> [Tile] -> ScrabbleError
- Wordify.Rules.ScrabbleError: WordsNotInDictionary :: [String] -> ScrabbleError
- Wordify.Rules.ScrabbleError: data ScrabbleError
- Wordify.Rules.ScrabbleError: instance GHC.Classes.Eq Wordify.Rules.ScrabbleError.ScrabbleError
- Wordify.Rules.ScrabbleError: instance GHC.Show.Show Wordify.Rules.ScrabbleError.ScrabbleError
+ Wordify.Rules.Game: restoreGame :: (Player, Player, Maybe (Player, Maybe Player)) -> LetterBag -> Dictionary -> NonEmpty Move -> Either WordifyError (NonEmpty GameTransition)
+ Wordify.Rules.Game: restoreGameLazy :: (Player, Player, Maybe (Player, Maybe Player)) -> LetterBag -> Dictionary -> NonEmpty Move -> Either WordifyError (NonEmpty (Either WordifyError GameTransition))
+ Wordify.Rules.Game.Internal: movesMade :: Game -> [Move]
+ Wordify.Rules.Game.Internal: numberOfPlayers :: Game -> Int
+ Wordify.Rules.Game.Internal: optionalsToPlayers :: Maybe (Player, Maybe Player) -> [Player]
+ Wordify.Rules.Game.Internal: players :: Game -> [Player]
+ Wordify.Rules.WordifyError: CannotExchangeWhenNoLettersInBag :: WordifyError
+ Wordify.Rules.WordifyError: CannotPlaceBlankWithoutLetter :: Pos -> WordifyError
+ Wordify.Rules.WordifyError: DictionaryFileNotFound :: FilePath -> WordifyError
+ Wordify.Rules.WordifyError: DoesNotConnectWithWord :: WordifyError
+ Wordify.Rules.WordifyError: DoesNotCoverTheStarTile :: WordifyError
+ Wordify.Rules.WordifyError: GameNotInProgress :: WordifyError
+ Wordify.Rules.WordifyError: InvalidTileLetters :: Pos -> String -> WordifyError
+ Wordify.Rules.WordifyError: LetterBagFileNotOpenable :: String -> WordifyError
+ Wordify.Rules.WordifyError: MalformedDictionaryFile :: String -> WordifyError
+ Wordify.Rules.WordifyError: MalformedLetterBagFile :: FilePath -> String -> WordifyError
+ Wordify.Rules.WordifyError: MiscError :: String -> WordifyError
+ Wordify.Rules.WordifyError: MisplacedLetter :: Pos -> WordifyError
+ Wordify.Rules.WordifyError: NoTilesPlaced :: WordifyError
+ Wordify.Rules.WordifyError: NotAssignableToBlank :: Pos -> String -> ValidBlankValues -> WordifyError
+ Wordify.Rules.WordifyError: NotEnoughLettersInStartingBag :: Int -> WordifyError
+ Wordify.Rules.WordifyError: PlacedTileOnOccupiedSquare :: Pos -> Tile -> WordifyError
+ Wordify.Rules.WordifyError: PlayerCannotExchange :: LettersOnRack -> [Tile] -> WordifyError
+ Wordify.Rules.WordifyError: PlayerCannotPlace :: LettersOnRack -> [Tile] -> WordifyError
+ Wordify.Rules.WordifyError: WordsNotInDictionary :: [String] -> WordifyError
+ Wordify.Rules.WordifyError: data WordifyError
+ Wordify.Rules.WordifyError: instance GHC.Classes.Eq Wordify.Rules.WordifyError.WordifyError
+ Wordify.Rules.WordifyError: instance GHC.Show.Show Wordify.Rules.WordifyError.WordifyError
- Wordify.Rules.Dictionary: makeDictionary :: FilePath -> IO (Either ScrabbleError Dictionary)
+ Wordify.Rules.Dictionary: makeDictionary :: FilePath -> IO (Either WordifyError Dictionary)
- Wordify.Rules.FormedWord: wordFormedFirstMove :: Board -> Map Pos Tile -> Either ScrabbleError FormedWords
+ Wordify.Rules.FormedWord: wordFormedFirstMove :: Board -> Map Pos Tile -> Either WordifyError FormedWords
- Wordify.Rules.FormedWord: wordsFormedMidGame :: Board -> Map Pos Tile -> Either ScrabbleError FormedWords
+ Wordify.Rules.FormedWord: wordsFormedMidGame :: Board -> Map Pos Tile -> Either WordifyError FormedWords
- Wordify.Rules.Game: makeGame :: (Player, Player, Maybe (Player, Maybe Player)) -> LetterBag -> Dictionary -> Either ScrabbleError Game
+ Wordify.Rules.Game: makeGame :: (Player, Player, Maybe (Player, Maybe Player)) -> LetterBag -> Dictionary -> Either WordifyError Game
- Wordify.Rules.LetterBag: makeBag :: FilePath -> IO (Either ScrabbleError LetterBag)
+ Wordify.Rules.LetterBag: makeBag :: FilePath -> IO (Either WordifyError LetterBag)
- Wordify.Rules.Move: makeMove :: Game -> Move -> Either ScrabbleError GameTransition
+ Wordify.Rules.Move: makeMove :: Game -> Move -> Either WordifyError GameTransition

Files

src/Wordify/Rules/Dictionary.hs view
@@ -24,7 +24,7 @@ import qualified Data.Text.Encoding as TE import Text.Parsec.Prim import Text.ParserCombinators.Parsec-import Wordify.Rules.ScrabbleError+import Wordify.Rules.WordifyError  data Dictionary = Dictionary (HashSet.HashSet T.Text) deriving (Show) @@ -56,7 +56,7 @@  -- | --    Creates a dictionary from a file containing a list of valid words, each word being seperated by a newline.-makeDictionary :: FilePath -> IO (Either ScrabbleError Dictionary)+makeDictionary :: FilePath -> IO (Either WordifyError Dictionary) makeDictionary filePath = do   convertFileError <$> (Exc.try (loadDictionaryFromFile filePath) :: (IO (Either Exc.IOException Dictionary)))   where
src/Wordify/Rules/FormedWord.hs view
@@ -33,7 +33,7 @@ import Wordify.Rules.Board import Wordify.Rules.LetterBag import Wordify.Rules.Pos-import Wordify.Rules.ScrabbleError+import Wordify.Rules.WordifyError import Wordify.Rules.Square import Wordify.Rules.Tile @@ -121,7 +121,7 @@ -- | --     Returns the word formed by the first move on the board. The word must cover --     the star tile, and be linear. Any blank tiles must be labeled.-wordFormedFirstMove :: Board -> Map Pos Tile -> Either ScrabbleError FormedWords+wordFormedFirstMove :: Board -> Map Pos Tile -> Either WordifyError FormedWords wordFormedFirstMove board tiles   | starPos `Map.notMember` tiles = Left DoesNotCoverTheStarTile   | otherwise = placedSquares board tiles >>= fmap (FirstWord . main) . wordsFormed board@@ -130,7 +130,7 @@ --    Returns the words formed by the tiles played on the board. A played word --    must be connected to a tile already on the board (or intersect tiles on the board), --    and be formed linearly. Any blank tiles must be labeled.-wordsFormedMidGame :: Board -> Map Pos Tile -> Either ScrabbleError FormedWords+wordsFormedMidGame :: Board -> Map Pos Tile -> Either WordifyError FormedWords wordsFormedMidGame board tiles =   placedSquares board tiles     >>= \squares ->@@ -191,7 +191,7 @@   Checks that the tiles can be placed, and if so returns a map of the squares at the placed positions.   A tile may be placed if the square is not already occupied, and if it is not an unlabeled blank tile. -}-placedSquares :: Board -> Map Pos Tile -> Either ScrabbleError (Map Pos Square)+placedSquares :: Board -> Map Pos Tile -> Either WordifyError (Map Pos Square) placedSquares board tiles = squares   where     squares =@@ -211,7 +211,7 @@         $ unoccupiedSquareAt board pos     mapAsList = Map.toList tiles -wordsFormed :: Board -> Map Pos Square -> Either ScrabbleError FormedWords+wordsFormed :: Board -> Map Pos Square -> Either WordifyError FormedWords wordsFormed board tiles   | Map.null tiles = Left NoTilesPlaced   | otherwise =
src/Wordify/Rules/Game.hs view
@@ -1,5 +1,7 @@ module Wordify.Rules.Game(Game,                           makeGame,+                          restoreGame,+                          restoreGameLazy,                           movesMade,                           gameStatus,                           board,@@ -23,11 +25,13 @@   import Wordify.Rules.Board   import Wordify.Rules.Dictionary   import Wordify.Rules.LetterBag-  import Wordify.Rules.ScrabbleError+  import Wordify.Rules.WordifyError+  import Wordify.Rules.Move (makeMove, newGame, GameTransition)   import Data.Maybe   import Wordify.Rules.Game.Internal+  import qualified Data.List.NonEmpty as NE   import qualified Data.Sequence as Seq-  import qualified Data.Foldable as F+  import qualified Data.Traversable as T   import Safe   import Control.Monad   import Control.Applicative@@ -44,7 +48,7 @@     Yields a tuple with the first player and the initial game state. Returns a 'Left' if there are not enough     tiles in the letter bag to distribute to the players.   -}-  makeGame :: (Player, Player, Maybe (Player, Maybe Player)) -> LetterBag -> Dictionary -> Either ScrabbleError Game+  makeGame :: (Player, Player, Maybe (Player, Maybe Player)) -> LetterBag -> Dictionary -> Either WordifyError Game   makeGame playing startBag dict =     do       (remainingBag, initialPlayers) <- initialisePlayers playing startBag@@ -66,7 +70,7 @@         toOptionalPlayers _ = Nothing  -  initialisePlayers :: (Player, Player, Maybe (Player, Maybe Player)) -> LetterBag -> Either ScrabbleError (LetterBag, [Player])+  initialisePlayers :: (Player, Player, Maybe (Player, Maybe Player)) -> LetterBag -> Either WordifyError (LetterBag, [Player])   initialisePlayers (play1, play2, maybePlayers) initialBag =      let transitions = distributeFinite givePlayerTiles initialBag allPlayers     in case (join . lastMay) transitions of@@ -90,26 +94,31 @@                 Just (currentDistributer, currentReceiver) : go giveBy (Just currentDistributer) receivers  -  optionalsToPlayers :: Maybe (Player, Maybe Player) -> [Player]-  optionalsToPlayers optionals = case optionals of -                    Nothing -> []-                    Just (player3, Nothing) -> [player3]-                    Just (player3, Just player4) -> [player3, player4]---  players :: Game -> [Player]-  players game = [player1 game, player2 game] ++ optionalsToPlayers (optionalPlayers game)-   getPlayer :: Game -> Int -> Maybe Player-  getPlayer game playerNumber = (players game) LS.!! (playerNumber - 1)+  getPlayer game playerNo = (players game) LS.!! (playerNo - 1) -  numberOfPlayers :: Game -> Int-  numberOfPlayers game = 2 + maybe 0 (\(_, maybePlayer4) -> if isJust maybePlayer4 then 2 else 1) (optionalPlayers game)+  -- |+  --    Restores a game from a list of moves. Constructs the initial game from the given players, letter bag+  --    and dictionary, then replays each move in order.+  --+  --    The letter bag must be constructed with the same tiles and random generator as the original game,+  --    the same dictionary must be used and the players must be in the original order.+  --+  --    Returns an error if the game cannot be constructed, or if any move is invalid.+  restoreGame :: (Player, Player, Maybe (Player, Maybe Player)) -> LetterBag -> Dictionary -> NE.NonEmpty Move -> Either WordifyError (NE.NonEmpty GameTransition)+  restoreGame playersSetup letterBag dict moves = do+    game <- makeGame playersSetup letterBag dict+    T.sequence $ replayMoves game moves -  {- |-    Returns a history of the moves made in the game.-  -}-  movesMade :: Game -> [Move]-  movesMade game = -    case history game of -      History _ moves -> F.toList moves+  -- |+  --    Like 'restoreGame' but returns a list of 'Either' so that laziness can be maintained,+  --    meaning all the game transitions don't have to be buffered before they can be consumed.+  restoreGameLazy :: (Player, Player, Maybe (Player, Maybe Player)) -> LetterBag -> Dictionary -> NE.NonEmpty Move -> Either WordifyError (NE.NonEmpty (Either WordifyError GameTransition))+  restoreGameLazy playersSetup letterBag dict moves = do+    game <- makeGame playersSetup letterBag dict+    return $ replayMoves game moves++  replayMoves :: Game -> NE.NonEmpty Move -> NE.NonEmpty (Either WordifyError GameTransition)+  replayMoves game (mv NE.:| moves) = NE.scanl nextMove (makeMove game mv) moves+    where+      nextMove transition move = transition >>= \success -> makeMove (newGame success) move
src/Wordify/Rules/Game/Internal.hs view
@@ -16,7 +16,11 @@       updateHistory,       Move(PlaceTiles, Exchange, Pass),       History(History),-      history) where+      history,+      movesMade,+      players,+      numberOfPlayers,+      optionalsToPlayers) where    import Wordify.Rules.Player   import Wordify.Rules.Board@@ -24,6 +28,8 @@   import Wordify.Rules.Dictionary   import Control.Applicative   import Data.Map+  import Data.Maybe (isJust)+  import qualified Data.Foldable as F   import Wordify.Rules.Pos   import Wordify.Rules.Tile   import Data.Sequence@@ -105,3 +111,20 @@   updateHistory game move = game {history = History originalBag (moveList |> move) }     where       History originalBag moveList = history game++  optionalsToPlayers :: Maybe (Player, Maybe Player) -> [Player]+  optionalsToPlayers optionals = case optionals of+    Nothing -> []+    Just (player3, Nothing) -> [player3]+    Just (player3, Just player4) -> [player3, player4]++  players :: Game -> [Player]+  players game = [player1 game, player2 game] ++ optionalsToPlayers (optionalPlayers game)++  numberOfPlayers :: Game -> Int+  numberOfPlayers game = 2 + maybe 0 (\(_, maybePlayer4) -> if isJust maybePlayer4 then 2 else 1) (optionalPlayers game)++  movesMade :: Game -> [Move]+  movesMade game =+    case history game of+      History _ moves -> F.toList moves
src/Wordify/Rules/LetterBag.hs view
@@ -29,7 +29,7 @@ import System.Random import Text.ParserCombinators.Parsec import Wordify.Rules.LetterBag.Internal-import Wordify.Rules.ScrabbleError+import Wordify.Rules.WordifyError import Wordify.Rules.Tile  -- |@@ -37,14 +37,14 @@ --  A blank letter is represented by a '_' character and has a disribution, but no value. -- -- If successful, the letter bag is shuffled before it is returned.-makeBag :: FilePath -> IO (Either ScrabbleError LetterBag)+makeBag :: FilePath -> IO (Either WordifyError LetterBag) makeBag path = do-  ioOutcome <- Exc.try $ withFile path ReadMode (hGetContents >=> parseBagString path) :: IO (Either Exc.IOException (Either ScrabbleError LetterBag))+  ioOutcome <- Exc.try $ withFile path ReadMode (hGetContents >=> parseBagString path) :: IO (Either Exc.IOException (Either WordifyError LetterBag))   case ioOutcome of     Left _ -> return $ Left (LetterBagFileNotOpenable path)     Right x -> return $ fmap shuffleBag x -parseBagString :: String -> String -> IO (Either ScrabbleError LetterBag)+parseBagString :: String -> String -> IO (Either WordifyError LetterBag) parseBagString path bagString =   let parseResult = parseBag bagString    in case parseResult of
src/Wordify/Rules/Move.hs view
@@ -3,8 +3,6 @@     GameTransition (MoveTransition, ExchangeTransition, PassTransition, GameFinished),     makeMove,     newGame,-    restoreGame,-    restoreGameLazy,   ) where @@ -13,20 +11,17 @@ import Control.Error import Control.Error.Util import Control.Monad-import qualified Data.List.NonEmpty as NE import Data.Map import qualified Data.Map as M import qualified Data.Map as Map-import qualified Data.Traversable as T import Wordify.Rules.Board import Wordify.Rules.Dictionary import Wordify.Rules.FormedWord-import Wordify.Rules.Game import Wordify.Rules.Game.Internal import Wordify.Rules.LetterBag import Wordify.Rules.Player import Wordify.Rules.Pos-import Wordify.Rules.ScrabbleError+import Wordify.Rules.WordifyError import Wordify.Rules.Tile  data GameTransition@@ -45,8 +40,8 @@ -- | --    Transitiions the game to the next state. If the move places tiles, the player must have the tiles to place and --    place the tiles legally. If the move exchanges tiles, the bag must not be empty and the player must have the---    tiles to exchange. A ScrabbleError is returned if these condtions are not the case.-makeMove :: Game -> Move -> Either ScrabbleError GameTransition+--    tiles to exchange. A WordifyError is returned if these condtions are not the case.+makeMove :: Game -> Move -> Either WordifyError GameTransition makeMove game move   | gameStatus game /= InProgress = Left GameNotInProgress   | otherwise = flip addMoveToHistory move <$> gameTransition@@ -56,7 +51,7 @@       Exchange exchanged -> exchangeMove game exchanged       Pass -> (Right . passMove) game -makeBoardMove :: Game -> M.Map Pos Tile -> Either ScrabbleError GameTransition+makeBoardMove :: Game -> M.Map Pos Tile -> Either WordifyError GameTransition makeBoardMove game placed =   do     validPlacedTiles <- validateTiles (validLetters letterBag) placed@@ -91,7 +86,7 @@       PlaceTiles _ -> True       _ -> False -exchangeMove :: Game -> [Tile] -> Either ScrabbleError GameTransition+exchangeMove :: Game -> [Tile] -> Either WordifyError GameTransition exchangeMove game exchangedTiles =   let exchangeOutcome = exchangeLetters (bag game) exchangedTiles    in case exchangeOutcome of@@ -118,30 +113,11 @@     numPasses = passes game + 1     gameFinished = numPasses == numberOfPlayers game * 2 --- |---    Restores a game from a list of moves. The game must be set up in the way the original game was set up---    (including the letter bag constructed with the same tiles and random generator, dictionary and the list of players---    in the original order.)------    If the game is not set up as it was originally, this function will return a scrabble error with the move which was invalid---    with the given state. For example, if the original players are not ordered in the correct way then the player will not have---    the required tiles to make the move.-restoreGame :: Game -> NE.NonEmpty Move -> Either ScrabbleError (NE.NonEmpty GameTransition)-restoreGame game = T.sequence . restoreGameLazy game --- |---    Maps each move to a resulting game transition, if the move is legal. Has the same semantics as 'restoreGame'---    but returns a list of 'Either' so that laziness can be maintained, meaning all the game transitions---    dont have to be buffered before they can be consumed.-restoreGameLazy :: Game -> NE.NonEmpty Move -> NE.NonEmpty (Either ScrabbleError GameTransition)-restoreGameLazy game (mv NE.:| moves) = NE.scanl nextMove (makeMove game mv) moves-  where-    nextMove transition move = transition >>= \success -> makeMove (newGame success) move--validateTiles :: ValidTiles -> M.Map Pos Tile -> Either ScrabbleError (M.Map Pos Tile)+validateTiles :: ValidTiles -> M.Map Pos Tile -> Either WordifyError (M.Map Pos Tile) validateTiles validTiles placed = fromList <$> mapM (validateTilePlacement validTiles) (toList placed)   where-    validateTilePlacement :: ValidTiles -> (Pos, Tile) -> Either ScrabbleError (Pos, Tile)+    validateTilePlacement :: ValidTiles -> (Pos, Tile) -> Either WordifyError (Pos, Tile)     validateTilePlacement validTiles (pos, Letter letters x) = (,) pos <$> note (InvalidTileLetters pos letters) (Map.lookup letters validTiles)     validateTilePlacement validTiles (pos, Blank (Just assigned)) =       note (NotAssignableToBlank pos assigned validTileStrings) (Map.lookup assigned validTiles) >>= \x -> Right (pos, Blank (Just assigned))@@ -192,17 +168,17 @@   where     tilesInBag = bagSize letterBag -newBoard :: Board -> M.Map Pos Tile -> Either ScrabbleError Board+newBoard :: Board -> M.Map Pos Tile -> Either WordifyError Board newBoard currentBoard placed = foldM (\oldBoard (pos, tile) -> newBoardIfUnoccupied oldBoard pos tile) currentBoard $ Map.toList placed   where     newBoardIfUnoccupied brd pos tile = note (PlacedTileOnOccupiedSquare pos tile) $ placeTile brd tile pos -removeLettersandGiveScore :: Player -> [Tile] -> Int -> Either ScrabbleError Player+removeLettersandGiveScore :: Player -> [Tile] -> Int -> Either WordifyError Player removeLettersandGiveScore player playedTiles justScored = do   let newPlayer = flip increaseScore justScored <$> removePlayedTiles player playedTiles    in note (PlayerCannotPlace (tilesOnRack player) playedTiles) newPlayer -scoresIfWordsLegal :: Dictionary -> FormedWords -> Either ScrabbleError (Int, [(String, Int)])+scoresIfWordsLegal :: Dictionary -> FormedWords -> Either WordifyError (Int, [(String, Int)]) scoresIfWordsLegal dict formedWords =   let strings = wordStrings formedWords    in case invalidWords dict strings of
− src/Wordify/Rules/ScrabbleError.hs
@@ -1,93 +0,0 @@-module Wordify.Rules.ScrabbleError-  ( ScrabbleError-      ( LetterBagFileNotOpenable,-        MalformedLetterBagFile,-        MalformedDictionaryFile,-        DictionaryFileNotFound,-        NotEnoughLettersInStartingBag,-        MisplacedLetter,-        DoesNotConnectWithWord,-        NoTilesPlaced,-        DoesNotCoverTheStarTile,-        PlacedTileOnOccupiedSquare,-        CannotPlaceBlankWithoutLetter,-        InvalidTileLetters,-        NotAssignableToBlank,-        WordsNotInDictionary,-        PlayerCannotPlace,-        GameNotInProgress,-        CannotExchangeWhenNoLettersInBag,-        PlayerCannotExchange,-        MiscError-      ),-  )-where--import Data.List-import Wordify.Rules.Pos-import Wordify.Rules.Tile--type LettersOnRack = [Tile]--type ValidBlankValues = [String]--data ScrabbleError-  = -- | The caller has supplied an invalid path to a letter bag file, or the file is not openable-    LetterBagFileNotOpenable String-  | -- | The letter bag file is marformed, so could not be parsed.-    MalformedLetterBagFile FilePath String-  | -- | The path given to a dictionary file was invalid.-    DictionaryFileNotFound FilePath-  | -- | The dictionary file could not be parsed as it was malformed.-    MalformedDictionaryFile String-  | -- | A letter bag with insufficient tiles was used to create a game.-    NotEnoughLettersInStartingBag Int-  | -- | The player has made an illegal tile placement. Tiles placed must form a line of tiles.-    MisplacedLetter Pos-  | -- | The tiles the player placed do not connect with any word (applies after the first move on the board)-    DoesNotConnectWithWord-  | -- | The client put the player in the situation to be able to place no tiles.-    NoTilesPlaced-  | -- | The first move on the board does not cover the star.-    DoesNotCoverTheStarTile-  | -- | The client allowed the player to place tiles on a square that is already occupied with tiles.-    PlacedTileOnOccupiedSquare Pos Tile-  | -- | A blank tile must be labeled with a letter before being placed.-    CannotPlaceBlankWithoutLetter Pos-  | -- | The tiles the player placed formed one or more words which are not in the dictionary.-    WordsNotInDictionary [String]-  | -- | The caller allowed the client to place tiles on the board which were not in their rack.-    PlayerCannotPlace LettersOnRack [Tile]-  | -- | The string applied to the blank letter isn't a valid tile value-    NotAssignableToBlank Pos String ValidBlankValues-  | -- | The letters on the played tile aren't valid-    InvalidTileLetters Pos String-  | -- | The caller allowed the player to attempt to exchange when no letters were left in the bag.-    CannotExchangeWhenNoLettersInBag-  | -- | The caller allowed the player to attempt to exchange tiles that they do not have.-    PlayerCannotExchange LettersOnRack [Tile]-  | -- | The caller allowed a move to be made when the game is finished.-    GameNotInProgress-  | MiscError String-  deriving (Eq)--instance Show ScrabbleError where-  show (MalformedDictionaryFile reason) = "Dictionary file could not be parsed for the following reason: " ++ reason-  show (MalformedLetterBagFile path err) = "Letter bag file " ++ path ++ " was malformed. Reason: " ++ err-  show (DictionaryFileNotFound path) = "Dictionary file " ++ path ++ " was not found."-  show (LetterBagFileNotOpenable path) = "Letter bag file " ++ path ++ " was not openable"-  show (NotEnoughLettersInStartingBag num) = "A starting bag must have enough tiles to distribute to the players to start a game. Bag has " ++ show num ++ " tiles."-  show (MisplacedLetter pos) = "Placed tiles were not legally placed. Starting at tile placed at pos: " ++ show pos-  show (DoesNotConnectWithWord) = "Placed tiles do not connect with an existing word on the board."-  show (NoTilesPlaced) = "No tiles were placed in the move."-  show (DoesNotCoverTheStarTile) = "First move must go through the star."-  show (PlacedTileOnOccupiedSquare pos _) = "Move replaces a tile already on the board at " ++ show pos ++ ". This is not a legal move."-  show (CannotPlaceBlankWithoutLetter pos) = "A played blank tile must be given a letter. Blank tile played at " ++ show pos ++ " was not given a letter."-  show (WordsNotInDictionary xs) = "The following words are not in the scrabble dictionary: " ++ show xs-  show (InvalidTileLetters pos letters) = "A played tile is not a valid tile. " ++ letters ++ " played at " ++ show pos ++ " was not given a letter."-  show (PlayerCannotPlace letterRack tiles) = "The player cannot place: " ++ show tiles ++ ". Tiles on rack: " ++ show letterRack ++ ". Blank tiles must be labeled and the placed tiles must be on the rack."-  show (NotAssignableToBlank pos assigned validAssignments) = "Cannot assign value " ++ show assigned ++ " to blank tile. Valid values: " ++ (intercalate ", " validAssignments) ++ " Blank placed at " ++ show pos-  show (CannotExchangeWhenNoLettersInBag) = "Cannot exchange letters when there are no letters in the bag."-  show (PlayerCannotExchange letterRack tiles) = "Player does not have the letters to exchange " ++ show tiles ++ ". Tiles on rack: " ++ show letterRack ++ ". Blank tiles must not be labeled."-  show (GameNotInProgress) = "A move was attempted on a game that is not in progress."-  show (MiscError str) = str
+ src/Wordify/Rules/WordifyError.hs view
@@ -0,0 +1,93 @@+module Wordify.Rules.WordifyError+  ( WordifyError+      ( LetterBagFileNotOpenable,+        MalformedLetterBagFile,+        MalformedDictionaryFile,+        DictionaryFileNotFound,+        NotEnoughLettersInStartingBag,+        MisplacedLetter,+        DoesNotConnectWithWord,+        NoTilesPlaced,+        DoesNotCoverTheStarTile,+        PlacedTileOnOccupiedSquare,+        CannotPlaceBlankWithoutLetter,+        InvalidTileLetters,+        NotAssignableToBlank,+        WordsNotInDictionary,+        PlayerCannotPlace,+        GameNotInProgress,+        CannotExchangeWhenNoLettersInBag,+        PlayerCannotExchange,+        MiscError+      ),+  )+where++import Data.List+import Wordify.Rules.Pos+import Wordify.Rules.Tile++type LettersOnRack = [Tile]++type ValidBlankValues = [String]++data WordifyError+  = -- | The caller has supplied an invalid path to a letter bag file, or the file is not openable+    LetterBagFileNotOpenable String+  | -- | The letter bag file is marformed, so could not be parsed.+    MalformedLetterBagFile FilePath String+  | -- | The path given to a dictionary file was invalid.+    DictionaryFileNotFound FilePath+  | -- | The dictionary file could not be parsed as it was malformed.+    MalformedDictionaryFile String+  | -- | A letter bag with insufficient tiles was used to create a game.+    NotEnoughLettersInStartingBag Int+  | -- | The player has made an illegal tile placement. Tiles placed must form a line of tiles.+    MisplacedLetter Pos+  | -- | The tiles the player placed do not connect with any word (applies after the first move on the board)+    DoesNotConnectWithWord+  | -- | The client put the player in the situation to be able to place no tiles.+    NoTilesPlaced+  | -- | The first move on the board does not cover the star.+    DoesNotCoverTheStarTile+  | -- | The client allowed the player to place tiles on a square that is already occupied with tiles.+    PlacedTileOnOccupiedSquare Pos Tile+  | -- | A blank tile must be labeled with a letter before being placed.+    CannotPlaceBlankWithoutLetter Pos+  | -- | The tiles the player placed formed one or more words which are not in the dictionary.+    WordsNotInDictionary [String]+  | -- | The caller allowed the client to place tiles on the board which were not in their rack.+    PlayerCannotPlace LettersOnRack [Tile]+  | -- | The string applied to the blank letter isn't a valid tile value+    NotAssignableToBlank Pos String ValidBlankValues+  | -- | The letters on the played tile aren't valid+    InvalidTileLetters Pos String+  | -- | The caller allowed the player to attempt to exchange when no letters were left in the bag.+    CannotExchangeWhenNoLettersInBag+  | -- | The caller allowed the player to attempt to exchange tiles that they do not have.+    PlayerCannotExchange LettersOnRack [Tile]+  | -- | The caller allowed a move to be made when the game is finished.+    GameNotInProgress+  | MiscError String+  deriving (Eq)++instance Show WordifyError where+  show (MalformedDictionaryFile reason) = "Dictionary file could not be parsed for the following reason: " ++ reason+  show (MalformedLetterBagFile path err) = "Letter bag file " ++ path ++ " was malformed. Reason: " ++ err+  show (DictionaryFileNotFound path) = "Dictionary file " ++ path ++ " was not found."+  show (LetterBagFileNotOpenable path) = "Letter bag file " ++ path ++ " was not openable"+  show (NotEnoughLettersInStartingBag num) = "A starting bag must have enough tiles to distribute to the players to start a game. Bag has " ++ show num ++ " tiles."+  show (MisplacedLetter pos) = "Placed tiles were not legally placed. Starting at tile placed at pos: " ++ show pos+  show (DoesNotConnectWithWord) = "Placed tiles do not connect with an existing word on the board."+  show (NoTilesPlaced) = "No tiles were placed in the move."+  show (DoesNotCoverTheStarTile) = "First move must go through the star."+  show (PlacedTileOnOccupiedSquare pos _) = "Move replaces a tile already on the board at " ++ show pos ++ ". This is not a legal move."+  show (CannotPlaceBlankWithoutLetter pos) = "A played blank tile must be given a letter. Blank tile played at " ++ show pos ++ " was not given a letter."+  show (WordsNotInDictionary xs) = "The following words are not in the scrabble dictionary: " ++ show xs+  show (InvalidTileLetters pos letters) = "A played tile is not a valid tile. " ++ letters ++ " played at " ++ show pos ++ " was not given a letter."+  show (PlayerCannotPlace letterRack tiles) = "The player cannot place: " ++ show tiles ++ ". Tiles on rack: " ++ show letterRack ++ ". Blank tiles must be labeled and the placed tiles must be on the rack."+  show (NotAssignableToBlank pos assigned validAssignments) = "Cannot assign value " ++ show assigned ++ " to blank tile. Valid values: " ++ (intercalate ", " validAssignments) ++ " Blank placed at " ++ show pos+  show (CannotExchangeWhenNoLettersInBag) = "Cannot exchange letters when there are no letters in the bag."+  show (PlayerCannotExchange letterRack tiles) = "Player does not have the letters to exchange " ++ show tiles ++ ". Tiles on rack: " ++ show letterRack ++ ". Blank tiles must not be labeled."+  show (GameNotInProgress) = "A move was attempted on a game that is not in progress."+  show (MiscError str) = str
test/Tests/FormedWordsTest.hs view
@@ -12,7 +12,7 @@ import Wordify.Rules.FormedWord import Wordify.Rules.Pos import Wordify.Rules.Pos.Internal-import Wordify.Rules.ScrabbleError+import Wordify.Rules.WordifyError import Wordify.Rules.Square import Wordify.Rules.Tile 
test/Tests/FullGameTest.hs view
@@ -17,20 +17,21 @@ import Wordify.Rules.Move import Wordify.Rules.Player import Wordify.Rules.Pos-import Wordify.Rules.ScrabbleError+import Wordify.Rules.WordifyError import Wordify.Rules.Tile  letterValues :: M.Map String Int letterValues = M.fromList $ [("A", 1), ("B", 3), ("C", 3), ("D", 2), ("E", 1), ("F", 4), ("G", 2), ("H", 4), ("I", 1), ("J", 8), ("K", 5), ("L", 1), ("M", 3), ("N", 1), ("O", 1), ("P", 3), ("Q", 10), ("R", 1), ("S", 1), ("T", 1), ("U", 1), ("V", 4), ("W", 4), ("X", 8), ("Y", 4), ("Z", 10)] -testDictionary :: IO (Either ScrabbleError Dictionary)-testDictionary = makeDictionary $ "Config" ++ [F.pathSeparator] ++ "engSet" ++ [F.pathSeparator] ++ "en.txt"- letterBag :: IO LetterBag letterBag = bagFromTiles $ map toTileBag tilesAsLetters   where     tilesAsLetters = "JEARVINENVO_NILLEWBKONUIEUWEAZBDESIAPAEOOURGOCDSNIADOAACAR_RMYELTUTYTEREOSITNIRFGPHAQLHESOIITXFDMETG" +testPlayers :: (Player, Player, Maybe (Player, Maybe Player))+testPlayers = let [p1, p2, p3, p4] = map makePlayer ["a", "b", "c", "d"]+              in (p1, p2, Just (p3, Just p4))+ moves :: [Move] moves = moveList   where@@ -66,12 +67,9 @@ playThroughTest :: Assertion playThroughTest =   do-    game <- letterBag >>= setupGame-    assertBool "Could not initialise game for test " $ isValid game--    let Right testGame = game     bag <- letterBag-    let moveTransitions = restoreGame testGame $ NE.fromList $ moves+    Right dict <- testDictionary+    let moveTransitions = restoreGame testPlayers bag dict $ NE.fromList $ moves      case moveTransitions of       Left err ->@@ -110,13 +108,11 @@ gameEndsOnConsecutiveSkips :: Assertion gameEndsOnConsecutiveSkips =   do-    game <- letterBag >>= setupGame+    bag <- letterBag+    Right dict <- testDictionary     -- 8 consecutive passes ends the game     let skipMoves = NE.fromList $ replicate 8 Pass-    assertBool "Could not initialise game for test " $ isValid game--    let Right testGame = game-    let transitions = restoreGame testGame skipMoves+    let transitions = restoreGame testPlayers bag dict skipMoves     let lastGame = fmap NE.last transitions     assertBool ("Unexpected failure when playing moves ") $ isValid lastGame @@ -129,11 +125,10 @@ gameDoesNotEndOnNonConsecutiveSkips :: Assertion gameDoesNotEndOnNonConsecutiveSkips =   do-    game <- letterBag >>= setupGame-    assertBool "Could not initialise game for test " $ isValid game-    let Right testGame = game+    bag <- letterBag+    Right dict <- testDictionary     let movesWithSkips = take 10 $ concat $ intersperse (replicate 4 Pass) $ splitEvery 4 moves-    let transitions = restoreGame testGame $ NE.fromList movesWithSkips+    let transitions = restoreGame testPlayers bag dict $ NE.fromList movesWithSkips      assertBool "Unexpected error making moves" $ isValid transitions 
test/Tests/Internationalisation/Spanish/MoveTest.hs view
@@ -1,7 +1,7 @@ module Tests.Internationalisation.Spanish.MoveTest (playSpanishMoveTest) where      import Wordify.Rules.LetterBag-    import Wordify.Rules.ScrabbleError (ScrabbleError)+    import Wordify.Rules.WordifyError (WordifyError)     import Wordify.Rules.Dictionary (Dictionary, makeDictionary)     import qualified System.FilePath as F     import Wordify.Rules.Game (Game, makeGame)@@ -13,7 +13,7 @@     import Wordify.Rules.Pos (starPos, rightPositions)     import Wordify.Rules.Player(makePlayer)     -    testSpanishDictionary :: IO (Either ScrabbleError Dictionary)+    testSpanishDictionary :: IO (Either WordifyError Dictionary)     testSpanishDictionary = makeDictionary $ "Config" ++ [F.pathSeparator] ++ "spanishSet" ++ [F.pathSeparator] ++ "dictionary.txt"      testSpanishLetterBag :: IO LetterBag@@ -33,7 +33,7 @@                 Letter "E" 1,Letter "U" 1,Letter "P" 3,Letter "N" 1,Letter "S" 1,Letter "O" 1,Letter "S" 1,Letter "N" 1,                 Letter "U" 1,Letter "RR" 8,Letter "A" 1,Letter "D" 2,Letter "N" 1,Letter "H" 4,Letter "A" 1] -    testGame :: IO (Either ScrabbleError Game)+    testGame :: IO (Either WordifyError Game)     testGame = do         letterBag <- testSpanishLetterBag         dictionaryResult <- testSpanishDictionary@@ -43,7 +43,7 @@             Right dict -> pure $ setupGame letterBag dict          where-            setupGame :: LetterBag -> Dictionary -> Either ScrabbleError Game+            setupGame :: LetterBag -> Dictionary -> Either WordifyError Game             setupGame bag dict = makeGame (player1, player2, Nothing) bag dict                 where                     player1 = makePlayer "player 1"
test/Tests/Internationalisation/SpanishExtraRuleTest.hs view
@@ -1,7 +1,7 @@  module Tests.Internationalisation.SpanishExtraRuleTest where     import qualified System.FilePath as F-    import Wordify.Rules.ScrabbleError (ScrabbleError)+    import Wordify.Rules.WordifyError (WordifyError)     import Wordify.Rules.Dictionary (Dictionary, makeDictionary)     import Wordify.Rules.LetterBag (LetterBag, bagFromTiles)     import Wordify.Rules.Tile (Tile(..))@@ -21,7 +21,7 @@     import Data.Maybe (fromJust)     import qualified Control.Arrow as A -    testSpanishDictionary :: IO (Either ScrabbleError Dictionary)+    testSpanishDictionary :: IO (Either WordifyError Dictionary)     testSpanishDictionary = makeDictionary $ "Config" ++ [F.pathSeparator] ++ "spanishSet" ++ [F.pathSeparator] ++ "dictionary.txt"      testSpanishLetterBag :: IO LetterBag@@ -42,7 +42,7 @@                     Letter "X" 8,Letter "E" 1,Letter "O" 1,Letter "R" 1,Letter "M" 3,Letter "S" 1,Letter "G" 2,Letter "U" 1,                     Letter "H" 4,Letter "V" 4,Letter "A" 1,Letter "Q" 5,Letter "O" 1,Blank Nothing] -    testGame :: IO (Either ScrabbleError Game)+    testGame :: IO (Either WordifyError Game)     testGame = do         letterBag <- testSpanishLetterBag         dictionaryResult <- testSpanishDictionary@@ -52,7 +52,7 @@             Right dict -> pure $ setupGame letterBag dict          where-            setupGame :: LetterBag -> Dictionary -> Either ScrabbleError Game+            setupGame :: LetterBag -> Dictionary -> Either WordifyError Game             setupGame bag dict = makeGame (player1, player2, Nothing) bag dict                 where                     player1 = makePlayer "player 1"
test/Tests/LetterBagTest.hs view
@@ -11,7 +11,7 @@ import Tests.Utils import Wordify.Rules.LetterBag import Wordify.Rules.LetterBag.Internal-import Wordify.Rules.ScrabbleError+import Wordify.Rules.WordifyError import Wordify.Rules.Tile  bagFromTilesProperty :: [Tile] -> Property
test/Tests/MoveTest.hs view
@@ -14,7 +14,7 @@ import Wordify.Rules.Player import Wordify.Rules.Pos import Wordify.Rules.Pos.Internal-import Wordify.Rules.ScrabbleError (ScrabbleError (CannotPlaceBlankWithoutLetter, NotAssignableToBlank))+import Wordify.Rules.WordifyError (WordifyError (CannotPlaceBlankWithoutLetter, NotAssignableToBlank)) import Wordify.Rules.Tile  letterBag :: IO LetterBag
test/Tests/SharedTestData.hs view
@@ -10,7 +10,7 @@ import Wordify.Rules.Player import Wordify.Rules.Pos import Wordify.Rules.Pos.Internal-import Wordify.Rules.ScrabbleError (ScrabbleError)+import Wordify.Rules.WordifyError (WordifyError) import Wordify.Rules.Square import Wordify.Rules.Tile @@ -34,7 +34,7 @@  verticals = zip verticalPositions verticalSquares -testDictionary :: IO (Either ScrabbleError Dictionary)+testDictionary :: IO (Either WordifyError Dictionary) testDictionary = makeDictionary $ "Config" ++ [F.pathSeparator] ++ "engSet" ++ [F.pathSeparator] ++ "en.txt"  letterValues :: M.Map String Int@@ -61,7 +61,7 @@     '_' -> Blank Nothing     x -> Letter [x] $ M.findWithDefault 0 [x] letterValues -setupGame :: LetterBag -> IO (Either ScrabbleError Game)+setupGame :: LetterBag -> IO (Either WordifyError Game) setupGame bag =   do     dict <- testDictionary
test/Tests/Utils.hs view
@@ -6,7 +6,7 @@ 	import System.Directory (removeFile, getTemporaryDirectory)  	withTempFile :: (FilePath -> Handle -> IO a) -> IO a-	withTempFile = bracket (getTemporaryDirectory >>= \tempDir -> openTempFile tempDir "scrabbleTest.txt") cleanupTemp . uncurry+	withTempFile = bracket (getTemporaryDirectory >>= \tempDir -> openTempFile tempDir "wordifyTest.txt") cleanupTemp . uncurry 	  where 	    cleanupTemp (path,h) = do 	      open <- hIsOpen h
wordify.cabal view
@@ -5,7 +5,7 @@ -- see: https://github.com/sol/hpack  name:           wordify-version:        0.6.0.0+version:        0.7.0.0 description:    Please see the README on GitHub at <https://github.com/githubuser/wordify#readme> category:       Game homepage:       https://github.com/happy0/wordify#readme@@ -41,30 +41,30 @@       Wordify.Rules.Player       Wordify.Rules.Pos       Wordify.Rules.Pos.Internal-      Wordify.Rules.ScrabbleError       Wordify.Rules.Square       Wordify.Rules.Tile+      Wordify.Rules.WordifyError   other-modules:       Paths_wordify   hs-source-dirs:       src   build-depends:-      array+      array ==0.5.*     , base >=4.7 && <5-    , bytestring-    , conduit-    , containers-    , errors-    , listsafe-    , mtl-    , parsec-    , random-    , safe-    , semigroups-    , split-    , text-    , transformers-    , unordered-containers+    , bytestring >=0.10 && <0.13+    , conduit ==1.3.*+    , containers >=0.6 && <0.9+    , errors ==2.3.*+    , listsafe ==0.1.*+    , mtl >=2.2 && <2.4+    , parsec ==3.1.*+    , random >=1.2 && <1.4+    , safe ==0.3.*+    , semigroups >=0.18 && <0.21+    , split ==0.2.*+    , text >=1.2 && <2.2+    , transformers >=0.5 && <0.7+    , unordered-containers ==0.2.*   default-language: Haskell2010  executable wordify-exe@@ -75,22 +75,22 @@       app   ghc-options: -threaded -rtsopts -with-rtsopts=-N   build-depends:-      array+      array ==0.5.*     , base >=4.7 && <5-    , bytestring-    , conduit-    , containers-    , errors-    , listsafe-    , mtl-    , parsec-    , random-    , safe-    , semigroups-    , split-    , text-    , transformers-    , unordered-containers+    , bytestring >=0.10 && <0.13+    , conduit ==1.3.*+    , containers >=0.6 && <0.9+    , errors ==2.3.*+    , listsafe ==0.1.*+    , mtl >=2.2 && <2.4+    , parsec ==3.1.*+    , random >=1.2 && <1.4+    , safe ==0.3.*+    , semigroups >=0.18 && <0.21+    , split ==0.2.*+    , text >=1.2 && <2.2+    , transformers >=0.5 && <0.7+    , unordered-containers ==0.2.*     , wordify   default-language: Haskell2010 @@ -117,28 +117,28 @@       test   ghc-options: -threaded -rtsopts -with-rtsopts=-N   build-depends:-      HUnit-    , QuickCheck-    , array+      HUnit ==1.6.*+    , QuickCheck >=2.14 && <2.19+    , array ==0.5.*     , base >=4.7 && <5-    , bytestring-    , conduit-    , containers-    , directory-    , errors-    , filepath-    , listsafe-    , mtl-    , parsec-    , random-    , safe-    , semigroups-    , split-    , test-framework-    , test-framework-hunit-    , test-framework-quickcheck2-    , text-    , transformers-    , unordered-containers+    , bytestring >=0.10 && <0.13+    , conduit ==1.3.*+    , containers >=0.6 && <0.9+    , directory ==1.3.*+    , errors ==2.3.*+    , filepath >=1.4 && <1.6+    , listsafe ==0.1.*+    , mtl >=2.2 && <2.4+    , parsec ==3.1.*+    , random >=1.2 && <1.4+    , safe ==0.3.*+    , semigroups >=0.18 && <0.21+    , split ==0.2.*+    , test-framework ==0.8.*+    , test-framework-hunit ==0.3.*+    , test-framework-quickcheck2 ==0.3.*+    , text >=1.2 && <2.2+    , transformers >=0.5 && <0.7+    , unordered-containers ==0.2.*     , wordify   default-language: Haskell2010