diff --git a/src/Wordify/Rules/Dictionary.hs b/src/Wordify/Rules/Dictionary.hs
--- a/src/Wordify/Rules/Dictionary.hs
+++ b/src/Wordify/Rules/Dictionary.hs
@@ -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
diff --git a/src/Wordify/Rules/FormedWord.hs b/src/Wordify/Rules/FormedWord.hs
--- a/src/Wordify/Rules/FormedWord.hs
+++ b/src/Wordify/Rules/FormedWord.hs
@@ -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 =
diff --git a/src/Wordify/Rules/Game.hs b/src/Wordify/Rules/Game.hs
--- a/src/Wordify/Rules/Game.hs
+++ b/src/Wordify/Rules/Game.hs
@@ -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
diff --git a/src/Wordify/Rules/Game/Internal.hs b/src/Wordify/Rules/Game/Internal.hs
--- a/src/Wordify/Rules/Game/Internal.hs
+++ b/src/Wordify/Rules/Game/Internal.hs
@@ -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
diff --git a/src/Wordify/Rules/LetterBag.hs b/src/Wordify/Rules/LetterBag.hs
--- a/src/Wordify/Rules/LetterBag.hs
+++ b/src/Wordify/Rules/LetterBag.hs
@@ -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
diff --git a/src/Wordify/Rules/Move.hs b/src/Wordify/Rules/Move.hs
--- a/src/Wordify/Rules/Move.hs
+++ b/src/Wordify/Rules/Move.hs
@@ -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
diff --git a/src/Wordify/Rules/ScrabbleError.hs b/src/Wordify/Rules/ScrabbleError.hs
deleted file mode 100644
--- a/src/Wordify/Rules/ScrabbleError.hs
+++ /dev/null
@@ -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
diff --git a/src/Wordify/Rules/WordifyError.hs b/src/Wordify/Rules/WordifyError.hs
new file mode 100644
--- /dev/null
+++ b/src/Wordify/Rules/WordifyError.hs
@@ -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
diff --git a/test/Tests/FormedWordsTest.hs b/test/Tests/FormedWordsTest.hs
--- a/test/Tests/FormedWordsTest.hs
+++ b/test/Tests/FormedWordsTest.hs
@@ -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
 
diff --git a/test/Tests/FullGameTest.hs b/test/Tests/FullGameTest.hs
--- a/test/Tests/FullGameTest.hs
+++ b/test/Tests/FullGameTest.hs
@@ -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
 
diff --git a/test/Tests/Internationalisation/Spanish/MoveTest.hs b/test/Tests/Internationalisation/Spanish/MoveTest.hs
--- a/test/Tests/Internationalisation/Spanish/MoveTest.hs
+++ b/test/Tests/Internationalisation/Spanish/MoveTest.hs
@@ -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"
diff --git a/test/Tests/Internationalisation/SpanishExtraRuleTest.hs b/test/Tests/Internationalisation/SpanishExtraRuleTest.hs
--- a/test/Tests/Internationalisation/SpanishExtraRuleTest.hs
+++ b/test/Tests/Internationalisation/SpanishExtraRuleTest.hs
@@ -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"
diff --git a/test/Tests/LetterBagTest.hs b/test/Tests/LetterBagTest.hs
--- a/test/Tests/LetterBagTest.hs
+++ b/test/Tests/LetterBagTest.hs
@@ -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
diff --git a/test/Tests/MoveTest.hs b/test/Tests/MoveTest.hs
--- a/test/Tests/MoveTest.hs
+++ b/test/Tests/MoveTest.hs
@@ -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
diff --git a/test/Tests/SharedTestData.hs b/test/Tests/SharedTestData.hs
--- a/test/Tests/SharedTestData.hs
+++ b/test/Tests/SharedTestData.hs
@@ -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
diff --git a/test/Tests/Utils.hs b/test/Tests/Utils.hs
--- a/test/Tests/Utils.hs
+++ b/test/Tests/Utils.hs
@@ -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
diff --git a/wordify.cabal b/wordify.cabal
--- a/wordify.cabal
+++ b/wordify.cabal
@@ -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
