diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,30 @@
+Copyright (c) 2015, Alexander Vieth
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+
+    * Redistributions in binary form must reproduce the above
+      copyright notice, this list of conditions and the following
+      disclaimer in the documentation and/or other materials provided
+      with the distribution.
+
+    * Neither the name of Alexander Vieth nor the names of other
+      contributors may be used to endorse or promote products derived
+      from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/Main.hs b/Main.hs
new file mode 100644
--- /dev/null
+++ b/Main.hs
@@ -0,0 +1,114 @@
+{-|
+Module      : 
+Description : 
+Copyright   : (c) Alexander Vieth, 2015
+Licence     : BSD3
+Maintainer  : aovieth@gmail.com
+Stability   : experimental
+Portability : non-portable (GHC only)
+-}
+
+{-# LANGUAGE AutoDeriveTypeable #-}
+{-# LANGUAGE RankNTypes #-}
+{-# LANGUAGE GADTs #-}
+
+import Control.Concurrent
+import Control.Concurrent.Async
+import Control.Concurrent.STM
+import qualified Data.Map as M
+import Data.AtLeast
+import Data.TypeNat.Vect
+import Data.Monoid
+import Data.Hourglass
+import System.Hourglass
+import Rest.Api
+import Rest.Driver.Wai
+import Network.Wai
+import Network.Wai.Handler.Warp
+import Network.Wai.Handler.WarpTLS
+import Options.Applicative
+import Diplomacy.Game
+import Types.GameState
+import Types.ServerOptions as Options
+import Types.Credentials
+import Types.Server
+import Types.GameId
+import Router
+import qualified Resources.Advance as Advance
+
+main :: IO ()
+main = do
+    opts <- execParser (info Options.parser mempty)
+    let username = adminUsername opts
+    let password = adminPassword opts
+    let cert = certificateFile opts
+    let key = Options.keyFile opts
+    let port = Options.port opts
+    initialState <- serverState username password
+    tvar <- newTVarIO initialState
+    let app = waiApp tvar
+    let daemon = advanceDaemon tvar
+    async daemon
+    --x <- mkJsApi (ModuleName "diplomacy") True (mkVersion 1 0 0) (router)
+    --putStrLn x
+    putStrLn ("Starting secure server on port " ++ show port)
+    runTLS (tlsSettings cert key) (setPort port defaultSettings) app
+    putStrLn "Goodbye"
+
+waiApp :: TVar ServerState -> Application
+waiApp tvar =
+    let runner :: forall a . Server a -> IO a
+        runner = runDiplomacyServer tvar
+    in  apiToApplication runner api
+
+-- To be run in a separate thread; this IO will periodically check every game
+-- and advance it if it's started and hasn't been advanced for longer than its
+-- period.
+--
+-- TODO this should get its own module. That would allow us to trim many
+-- imports in this Main file.
+advanceDaemon :: TVar ServerState -> IO ()
+advanceDaemon tvar = do
+    -- We check every second.
+    threadDelay oneSecond
+    advanceGamesIO tvar
+    advanceDaemon tvar
+  where
+    -- threadDelay uses microseconds.
+    oneSecond = 1000000
+    advanceGamesIO :: TVar ServerState -> IO ()
+    advanceGamesIO tvar = do
+        t <- timeCurrent
+        gameIds <- atomically $ do
+            state <- readTVar tvar
+            let (advancedGameIds, newState) = advanceState t state
+            writeTVar tvar newState
+            return advancedGameIds
+        return ()
+    advanceState :: Elapsed -> ServerState -> ([GameId], ServerState)
+    advanceState t state = 
+        let (gameIds, nextGames) = M.foldWithKey (advanceGameFold t) ([], M.empty) (games state)
+        in  (gameIds, state { games = nextGames })
+    advanceGameFold
+        :: Elapsed
+        -> GameId
+        -> (Password, GameState)
+        -> ([GameId], M.Map GameId (Password, GameState))
+        -> ([GameId], M.Map GameId (Password, GameState))
+    advanceGameFold t gameId (pwd, gameState) (ids, out) = case gameState of
+        GameStarted m (AtLeast (VCons (SomeGame game) VNil) rest) duration duration' elapsed paused ->
+            let Elapsed t' = t - elapsed 
+                (observedDuration, _) = fromSeconds t'
+                thresholdDuration = case game of
+                    TypicalGame _ _ _ _ _ -> duration
+                    RetreatGame _ _ _ _ _ _ _ -> duration'
+                    AdjustGame _ _ _ _ _ -> duration'
+            in  if not paused && observedDuration > thresholdDuration
+                then let nextGame = Advance.advance (SomeGame game)
+                     in  (gameId : ids, M.insert gameId (pwd, GameStarted m (AtLeast (VCons nextGame VNil) ((SomeGame game) : rest)) duration duration' t False) out)
+                else (ids, M.insert gameId (pwd, GameStarted m (AtLeast (VCons (SomeGame game) VNil) rest) duration duration' elapsed paused) out)
+        _ -> (ids, M.insert gameId (pwd, gameState) out)
+
+api = [(mkVersion 1 0 0, Some1 router)]
+
+
diff --git a/README.md b/README.md
new file mode 100644
--- /dev/null
+++ b/README.md
@@ -0,0 +1,81 @@
+# diplomacy-server
+
+Play the board game
+[Diplomacy](https://en.wikipedia.org/wiki/Diplomacy_%28game%29) over HTTP.
+Players may participate via their own private devices, so long as they have
+network capabilities and a good web browser.
+
+## Quick start
+
+Start by installing the program.
+
+```bash`
+git clone git@github.com:avieth/diplomacy-server.git
+cd diplomacy-server
+cabal install
+```
+
+In order to get up and running you need a public/private key pair and
+certificate, because the web
+server uses secure HTTP. Here's how to create a key pair and self-sign a
+certificate using OpenSSL.
+Note that your browser will warn of an untrusted certificate when you connect
+to the server.
+
+```bash
+openssl req -x509 -newkey rsa:2048 -keyout server.key -out server.pem -days 365 -nodes
+```
+
+When running the server, the key and certificate file locations can be
+specified using `-k` and `-c` respectively; the defaults are `./server.key` and
+`./server.pem`. When running the server you must also give a username and
+password for the administrator. These credentials allows you to create, start,
+advance, pause, and destroy games.
+
+```bash
+./Main -u <username> -p <password>
+```
+
+The default port is `4347`, but this can be changed using `--port`. With
+the server running, navigate to `https://<host>:4347/v1.0.0/diplomacy` to
+begin playing via the simple but effective web client. The workflow is as
+follows:
+
+1. Administartor creates a game, choosing a password for it.
+2. Players join the game using the administartor's chosen password.
+3. Adminstrator starts the game.
+4. Players use their own devices (smartphones, laptops) to view game state and
+   issue orders.
+
+The game will advance automatically unless the adminstrator pauses the game.
+In this case, the game will advance only when the adminstrator explicitly
+advances it, at which point the automatic advance feature kicks in again.
+This is useful for putting a game on indefinite hiatus.
+
+## The simple client
+
+The file `client.html` is a barebones, not-so-user-friendly interface for
+players and administrators (game-masters, if you will). It *should* provide
+all necessary functionality to play a complete match, and *should* work
+properly on all smartphones and personal computers with up-to-date web browsers.
+
+To input typical and retreat phase orders using this client, one must type the
+order object as it would be written in a pen-and-paper game; the map is not
+interactive, but this would be a nice improvement.
+
+A better HTML/JavaScript client, or even native iOS and Android clients,
+would be *very* nice to have. Ideally, we could use the definitions of the
+[diplomacy](https://github.com/avieth/diplomacy) Haskell library to produce
+these clients. This way, we don't need to reproduce the basic definitions like
+those related to provinces and their adjacency, and we could also use order
+validation definitions to give helpful feedback when the user attempts to input
+a new order (highlighting valid move targets or support subjects, for instance).
+
+## A note on REST
+
+This program uses the rest package, but it doesn't give a RESTful API; the
+server *is* stateful. This package was chosen because it was relatively
+quick and easy. It ought to be swapped out for something which
+
+1. Does not claim to be RESTful.
+2. Allows us to easily reuse handlers for HTTP and Email input.
diff --git a/Resources/Advance.hs b/Resources/Advance.hs
new file mode 100644
--- /dev/null
+++ b/Resources/Advance.hs
@@ -0,0 +1,152 @@
+{-|
+Module      : Resources.Advance
+Description : 
+Copyright   : (c) Alexander Vieth, 2015
+Licence     : BSD3
+Maintainer  : aovieth@gmail.com
+Stability   : experimental
+Portability : non-portable (GHC only)
+-}
+
+{-# LANGUAGE AutoDeriveTypeable #-}
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE StandaloneDeriving #-}
+{-# LANGUAGE GADTs #-}
+
+module Resources.Advance (
+
+      resource
+    , advance
+
+    ) where
+
+import GHC.Generics
+import Data.Typeable
+import Control.Monad.Trans.Class
+import Control.Monad.Trans.Reader
+import Control.Monad.Trans.Except
+import Control.Monad.State.Class as SC
+import qualified Data.Map as M
+import Data.AtLeast
+import Data.TypeNat.Vect
+import Data.Aeson
+import Data.JSON.Schema
+import Rest
+import Rest.Resource as R
+import Diplomacy.Game
+import Diplomacy.SupplyCentreDeficit
+import Types.Server
+import Types.GameId
+import Types.Credentials
+import Types.GameState
+
+resource :: Resource (ReaderT GameId Server) (ReaderT GameId Server) () Void Void
+resource = mkResourceId
+    { R.name = "advance"
+    , R.schema = singleton () $ unnamedSingle (const ())
+    , R.update = Just update
+    }
+  where
+
+    update :: Handler (ReaderT GameId Server)
+    update = secureHandler $ mkInputHandler (jsonO . jsonE . jsonI) $ doUpdate
+
+    doUpdate :: AdvanceInput -> ExceptT (Reason AdvanceError) (ReaderT GameId Server) AdvanceOutput
+    doUpdate input = withAdminCredentials creds advanceGame
+      where
+        AdvanceInput creds = input
+
+    advanceGame :: ExceptT (Reason AdvanceError) (ReaderT GameId Server) AdvanceOutput
+    advanceGame = do
+        gameId <- lift ask
+        modifyGameState gameId modifier
+        return GameAdvanced
+
+    modifier :: GameState -> ExceptT (Reason AdvanceError) (ReaderT GameId Server) GameState
+    modifier gameState = case gameState of
+        GameNotStarted _ _ _ -> throwE (domainReason AdvanceGameNotStarted)
+        GameStarted m (AtLeast (VCons someGame VNil) rest) duration duration' elapsed _ -> do
+            state <- SC.get
+            let nextGame = advance someGame
+            return (GameStarted m (AtLeast (VCons nextGame VNil) (someGame : rest)) duration duration' (currentTime state) False)
+
+-- | Advance a game, resolving and then continuing, so that we also go from
+--   Unresolved to Unresolved. Retreat and Adjust phases in which there is
+--   nothing to do are skipped, but they are not forgotten! They're given
+--   in the return value.
+advance :: SomeGame -> SomeGame
+advance (SomeGame game) = SomeGame (continue (resolve game))
+{-
+advance (SomeGame game) = case game of
+    TypicalGame TypicalRoundOne Unresolved _ _ _ ->
+        let resolved = resolve game
+            continued = continue resolved
+        in  case M.size (gameDislodged continued) of
+                -- Automatically skip retreat phases where nobody is
+                -- dislodged.
+                0 -> advance (SomeGame continued)
+                _ -> AtLeast (VCons (SomeGame continued) VNil) []
+    TypicalGame TypicalRoundTwo Unresolved _ _ _ ->
+        let resolved = resolve game
+            continued = continue resolved
+        in  case M.size (gameDislodged continued) of
+                -- Automatically skip retreat phases where nobody is
+                -- dislodged.
+                0 -> advance (SomeGame continued)
+                _ -> SomeGame continued
+    RetreatGame RetreatRoundOne Unresolved _ _ _ _ _ ->
+        let resolved = resolve game
+            continued = continue resolved
+        in  SomeGame continued
+    RetreatGame RetreatRoundTwo Unresolved _ _ _ _ _ ->
+        let resolved = resolve game
+            continued = continue resolved
+            occupation = gameOccupation continued
+            control = gameControl continued
+            deficits = fmap (\greatPower -> supplyCentreDeficit greatPower occupation control) [minBound..maxBound]
+        in  if all (== 0) deficits
+            -- Automatically skip adjust phases where nobody has a deficit,
+            -- positive or negative.
+            -- TODO must also check that, in case there's a negative
+            -- deficit, this great power actually has a place to build.
+            -- Really, we ought to use the order synthesizers to see whether
+            -- any player actually has a choice. That goes for typical
+            -- phase as well; it's rare, but it could happen, maybe?
+            -- No, that would cause infinite loop behaviour in advance.
+            then advance (SomeGame continued)
+            else SomeGame continued
+    AdjustGame _ Unresolved _ _ _ ->
+        let resolved = resolve game
+            continued = continue resolved
+        in  SomeGame continued
+-}
+
+data AdvanceError = AdvanceGameNotStarted
+
+instance ToResponseCode AdvanceError where
+    toResponseCode _ = 403
+
+deriving instance Generic AdvanceError
+deriving instance Typeable AdvanceError
+instance FromJSON AdvanceError
+instance ToJSON AdvanceError
+instance JSONSchema AdvanceError where
+    schema = gSchema
+
+newtype AdvanceInput = AdvanceInput Credentials
+
+deriving instance Generic AdvanceInput
+deriving instance Typeable AdvanceInput
+instance FromJSON AdvanceInput
+instance ToJSON AdvanceInput
+instance JSONSchema AdvanceInput where
+    schema = gSchema
+
+data AdvanceOutput = GameAdvanced
+
+deriving instance Generic AdvanceOutput
+deriving instance Typeable AdvanceOutput
+instance FromJSON AdvanceOutput
+instance ToJSON AdvanceOutput
+instance JSONSchema AdvanceOutput where
+    schema = gSchema
diff --git a/Resources/Client.hs b/Resources/Client.hs
new file mode 100644
--- /dev/null
+++ b/Resources/Client.hs
@@ -0,0 +1,56 @@
+{-|
+Module      : Resources.Client
+Description : 
+Copyright   : (c) Alexander Vieth, 2015
+Licence     : BSD3
+Maintainer  : aovieth@gmail.com
+Stability   : experimental
+Portability : non-portable (GHC only)
+-}
+
+{-# LANGUAGE AutoDeriveTypeable #-}
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE StandaloneDeriving #-}
+{-# LANGUAGE RankNTypes #-}
+
+module Resources.Client (
+
+      resource
+
+    ) where
+
+import Control.Monad.Trans.Class
+import Control.Monad.Trans.State as S
+import Control.Monad.Trans.Reader
+import Control.Monad.Trans.Except
+import qualified Data.Map as M
+import Data.Hourglass
+import Data.ByteString as BS
+import qualified Data.ByteString.Lazy as BL
+import Rest
+import Rest.Resource as R
+import Types.Server
+import Types.GameId
+import Types.Credentials
+import Types.GameState
+import Resources.Game.Create as Create
+import Resources.Game.Remove as Remove
+
+resource :: Resource Server Server () Void Void
+resource = (mkResource enter)
+    { R.name = "diplomacy"
+    , R.schema = Rest.singleton () (named [])
+    , R.get = Just get
+    }
+  where
+
+    enter :: forall b . () -> Server b -> Server b
+    enter _ = id
+
+    get :: Handler Server
+    get = secureHandler $ mkConstHandler (jsonE . fileO) $ doGet
+
+    doGet :: ExceptT (Reason Void) Server (BL.ByteString, String, Bool)
+    doGet = do
+        state <- lift S.get
+        return $ (clientHtml state, "client.html", False)
diff --git a/Resources/Game.hs b/Resources/Game.hs
new file mode 100644
--- /dev/null
+++ b/Resources/Game.hs
@@ -0,0 +1,113 @@
+{-|
+Module      : Resources.Game
+Description : 
+Copyright   : (c) Alexander Vieth, 2015
+Licence     : BSD3
+Maintainer  : aovieth@gmail.com
+Stability   : experimental
+Portability : non-portable (GHC only)
+-}
+
+{-# LANGUAGE AutoDeriveTypeable #-}
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE StandaloneDeriving #-}
+
+module Resources.Game (
+
+      resource
+
+    ) where
+
+import Control.Monad.Trans.Class
+import Control.Monad.Trans.State as S
+import Control.Monad.Trans.Reader
+import Control.Monad.Trans.Except
+import qualified Data.Map as M
+import Data.Hourglass
+import Rest
+import Rest.Resource as R
+import Rest.Dictionary.Types
+import Types.Server
+import Types.GameId
+import Types.Credentials
+import Types.GameState
+import Resources.Game.Create as Create
+import Resources.Game.Remove as Remove
+import Diplomacy.Game
+import Diplomacy.Turn
+
+-- | The game resource will use a string identifier to pick out a game, and
+--   give methods
+--     GET : all info about the game state
+--     GET : listing of the games
+--     CREATE : make a new game
+--     DELETE : destroy a game
+--   TBD parameterize on the great power who's asking?!
+--   Admin | Player GreatPower
+--   Admin | Player String? Use player names rather than their power?
+resource :: Resource Server (ReaderT GameId Server) GameId () Void
+resource = mkResourceReader
+    { R.name = "game"
+    , R.schema = withListing () $ unnamedSingle GameId
+    --, R.schema = withListing () $ named [("id", singleBy GameId)]
+    -- We use update (PUT) where we really ought to use get (GET) because
+    -- there is a lot of resistance against including bodies in GET requests,
+    -- so much so that the W3C XMLHttpRequest NEVER includes a body in a
+    -- GET request.
+    , R.update = Just get
+    -- TODO TBD listing doesn't work; unsupported route. Why?!?
+    , R.list = const listing
+    , R.create = Just create
+    , R.remove = Just remove
+    }
+  where
+
+    get :: Handler (ReaderT GameId Server)
+    get = secureHandler $ mkHandler (jsonO . jsonE . jsonI) handler
+
+    handler :: Env h p Credentials -> ExceptT (Reason Void) (ReaderT GameId Server) (Maybe GameData)
+    handler env =
+        let credentials = input env
+        in  lift ask >>= \gameId -> doGet credentials gameId
+
+    doGet
+        :: Credentials
+        -> GameId
+        -> ExceptT (Reason Void) (ReaderT GameId Server) (Maybe GameData)
+    doGet credentials gameId = withUserCredentialsForGame credentials gameId f
+      where
+        f gameStateView = return $ do
+            metadata <- gameStateViewMetadata gameStateView
+            let turn = metadataTurn metadata
+            let round = metadataRound metadata
+            gameStateViewData turn round gameStateView
+
+    listing :: ListHandler Server
+    listing = mkListing (stringO) $ \_ -> lift doListing
+    doListing :: Server [String]
+    doListing = listGames >>= return . fmap show
+
+    create :: Handler Server
+    create = secureHandler $ mkInputHandler (jsonO . jsonE . jsonI) $ doCreate
+    doCreate :: CreateGameInput -> ExceptT (Reason Void) Server CreateGameOutput
+    doCreate input = withAdminCredentials creds (lift (createGame gameId password duration duration'))
+      where
+        creds = Create.credentials input
+        gameId = Create.gameId input
+        password = Create.gamePassword input
+        duration = maybe (makeDuration 15) makeDuration (Create.gameDuration input)
+        duration' = maybe (makeDuration 5) makeDuration (Create.gameSecondDuration input)
+        makeDuration x = Duration (fromIntegral 0) (fromIntegral x) (fromIntegral 0) (fromIntegral 0)
+
+    remove :: Handler (ReaderT GameId Server)
+    remove = secureHandler $ mkIdHandler (jsonO . jsonE . jsonI) $ \credentials gameId -> doRemove (RemoveGameInput gameId credentials)
+    doRemove :: RemoveGameInput -> ExceptT (Reason Void) (ReaderT GameId Server) RemoveGameOutput
+    doRemove input = withAdminCredentials creds (removeGame gameId)
+      where
+        creds = Remove.credentials input
+        gameId = Remove.gameId input
+
+listGames :: Server [GameId]
+listGames = do
+    state <- S.get
+    return $ M.keys (games state)
diff --git a/Resources/Game/Create.hs b/Resources/Game/Create.hs
new file mode 100644
--- /dev/null
+++ b/Resources/Game/Create.hs
@@ -0,0 +1,76 @@
+{-|
+Module      : Resources.Game.Create
+Description : 
+Copyright   : (c) Alexander Vieth, 2015
+Licence     : BSD3
+Maintainer  : aovieth@gmail.com
+Stability   : experimental
+Portability : non-portable (GHC only)
+-}
+
+{-# LANGUAGE AutoDeriveTypeable #-}
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE StandaloneDeriving #-}
+
+module Resources.Game.Create (
+
+      CreateGameInput(..)
+    , CreateGameOutput(..)
+    , createGame
+
+    ) where
+
+import GHC.Generics
+import Control.Monad.Trans.State as S
+import Data.Typeable
+import qualified Data.Map as M
+import Data.Aeson
+import Data.JSON.Schema
+import Data.Hourglass
+import Types.GameId
+import Types.Server
+import Types.Credentials
+import Types.GameState
+
+data CreateGameInput = CreateGameInput {
+      gameId :: GameId
+    , gamePassword :: Password
+    -- ^ A password for the game, to share with the people who you wish to
+    -- allow to join.
+    , credentials :: Credentials
+    -- ^ Administrator credentials
+    , gameDuration :: Maybe Int
+    -- ^ Duration of typical phases.
+    , gameSecondDuration :: Maybe Int
+    -- ^ Duration of adjust and retreat phases.
+    }
+
+deriving instance Generic CreateGameInput
+deriving instance Typeable CreateGameInput
+instance FromJSON CreateGameInput
+instance ToJSON CreateGameInput
+instance JSONSchema CreateGameInput where
+    schema = gSchema
+
+data CreateGameOutput where
+    GameCreated :: CreateGameOutput
+    NameAlreadyTaken :: CreateGameOutput
+
+deriving instance Generic CreateGameOutput
+deriving instance Typeable CreateGameOutput
+instance FromJSON CreateGameOutput
+instance ToJSON CreateGameOutput
+instance JSONSchema CreateGameOutput where
+    schema = gSchema
+
+createGame :: GameId -> Password -> Duration -> Duration -> Server CreateGameOutput
+createGame gameId password duration duration' = do
+    state <- S.get
+    case M.member gameId (games state) of
+        True -> return $ NameAlreadyTaken
+        False -> do
+            let gameState = GameNotStarted M.empty duration duration'
+            let nextState = state { games = M.insert gameId (password, gameState) (games state) }
+            S.put nextState
+            return GameCreated
diff --git a/Resources/Game/Remove.hs b/Resources/Game/Remove.hs
new file mode 100644
--- /dev/null
+++ b/Resources/Game/Remove.hs
@@ -0,0 +1,70 @@
+{-|
+Module      : Resources.Game.Remove
+Description : 
+Copyright   : (c) Alexander Vieth, 2015
+Licence     : BSD3
+Maintainer  : aovieth@gmail.com
+Stability   : experimental
+Portability : non-portable (GHC only)
+-}
+
+{-# LANGUAGE AutoDeriveTypeable #-}
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE StandaloneDeriving #-}
+{-# LANGUAGE FlexibleContexts #-}
+
+module Resources.Game.Remove (
+
+      RemoveGameInput(..)
+    , RemoveGameOutput(..)
+    , removeGame
+
+    ) where
+
+import GHC.Generics
+import Control.Monad.State.Class as SC
+import Control.Monad.Trans.Except
+import Data.Typeable
+import qualified Data.Map as M
+import Data.Aeson
+import Data.JSON.Schema
+import Rest
+import Types.GameId
+import Types.Server
+import Types.Credentials
+
+data RemoveGameInput = RemoveGameInput {
+      gameId :: GameId
+    , credentials :: Credentials
+    }
+
+deriving instance Generic RemoveGameInput
+deriving instance Typeable RemoveGameInput
+instance FromJSON RemoveGameInput
+instance ToJSON RemoveGameInput
+instance JSONSchema RemoveGameInput where
+    schema = gSchema
+
+data RemoveGameOutput where
+    GameRemoved :: RemoveGameOutput
+
+deriving instance Generic RemoveGameOutput
+deriving instance Typeable RemoveGameOutput
+instance FromJSON RemoveGameOutput
+instance ToJSON RemoveGameOutput
+instance JSONSchema RemoveGameOutput where
+    schema = gSchema
+
+removeGame
+    :: MonadState ServerState m
+    => GameId
+    -> ExceptT (Reason Void) m RemoveGameOutput
+removeGame gameId = do
+    state <- SC.get
+    case M.member gameId (games state) of
+        False -> throwE NotFound
+        True -> do
+            let nextState = state { games = M.delete gameId (games state) }
+            SC.put nextState
+            return GameRemoved
diff --git a/Resources/Join.hs b/Resources/Join.hs
new file mode 100644
--- /dev/null
+++ b/Resources/Join.hs
@@ -0,0 +1,123 @@
+{-|
+Module      : Resources.Join
+Description : 
+Copyright   : (c) Alexander Vieth, 2015
+Licence     : BSD3
+Maintainer  : aovieth@gmail.com
+Stability   : experimental
+Portability : non-portable (GHC only)
+-}
+
+{-# LANGUAGE AutoDeriveTypeable #-}
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE StandaloneDeriving #-}
+
+module Resources.Join (
+
+      resource
+
+    ) where
+
+import GHC.Generics
+import Control.Monad.State.Class as SC
+import Control.Monad.Trans.Class
+import Control.Monad.Trans.Reader
+import Control.Monad.Trans.Except
+import Data.Typeable
+import qualified Data.Map as M
+import Data.Functor.Constant as FC
+import Data.Aeson
+import Data.JSON.Schema
+import Rest
+import Rest.Resource as R
+import Types.GameId
+import Types.UserData hiding (password)
+import Types.Credentials hiding (password)
+import qualified Types.Credentials as Credentials
+import Types.GameState
+import Types.Server
+
+-- | Use this resource to join a game.
+--
+--     PUT : ask to join the game, passing the game password and your
+--           credentials in the request body.
+--
+resource :: Resource (ReaderT GameId Server) (ReaderT GameId Server) () Void Void
+resource = mkResourceId
+    { R.name = "join"
+    , R.schema = singleton () $ unnamedSingle (const ())
+    , R.update = Just update
+    }
+  where
+    update :: Handler (ReaderT GameId Server)
+    update = secureHandler $ mkInputHandler (jsonO . jsonE . jsonI) $ doUpdate
+    doUpdate :: JoinGameInput -> ExceptT (Reason JoinGameError) (ReaderT GameId Server) JoinGameOutput
+    doUpdate input = do
+        gameId <- lift ask
+        state <- SC.get
+        case M.lookup gameId (games state) of
+            Nothing -> throwE NotFound
+            Just (pwd', gameState) -> case pwd' == pwd of
+                False -> throwE NotAllowed
+                True -> case registerUser creds gameState of
+                    Left e -> throwE (domainReason e)
+                    Right nextGameState -> do
+                        SC.put (state { games = M.alter (const (Just (pwd', nextGameState))) gameId (games state) })
+                        return Joined
+      where
+        creds = credentials input
+        pwd = password input
+
+registerUser :: Credentials -> GameState -> Either JoinGameError GameState
+registerUser credentials gameState = case gameState of
+    GameNotStarted map duration duration' -> case M.lookup uname map of
+        Nothing -> 
+            if M.size map == 7
+            then Left GameFull
+            else Right (GameNotStarted (M.insert uname userData map) duration duration')
+          where
+            userData = UserData pwd (FC.Constant ())
+        Just _ -> Left UsernameTaken
+    GameStarted _ _ _ _ _ _ -> Left GameAlreadyStarted
+  where
+    uname = Credentials.username credentials
+    pwd = Credentials.password credentials
+
+data JoinGameInput = JoinGameInput {
+      password :: Password
+      -- ^ Password for the game.
+    , credentials :: Credentials
+      -- ^ Credentials of the user who wishes to join.
+    }
+
+deriving instance Generic JoinGameInput
+deriving instance Typeable JoinGameInput
+instance FromJSON JoinGameInput
+instance ToJSON JoinGameInput
+instance JSONSchema JoinGameInput where
+    schema = gSchema
+
+data JoinGameOutput = Joined
+
+deriving instance Generic JoinGameOutput
+deriving instance Typeable JoinGameOutput
+instance FromJSON JoinGameOutput
+instance ToJSON JoinGameOutput
+instance JSONSchema JoinGameOutput where
+    schema = gSchema
+
+data JoinGameError
+    = UsernameTaken
+    | GameAlreadyStarted
+    | GameFull
+    deriving (Show, Generic, Typeable)
+
+instance JSONSchema JoinGameError where
+    schema = gSchema
+
+instance ToJSON JoinGameError
+
+instance ToResponseCode JoinGameError where
+    toResponseCode UsernameTaken = 403
+    toResponseCode GameAlreadyStarted = 403
+    toResponseCode GameFull = 403
diff --git a/Resources/Metadata.hs b/Resources/Metadata.hs
new file mode 100644
--- /dev/null
+++ b/Resources/Metadata.hs
@@ -0,0 +1,48 @@
+{-|
+Module      : Resources.Metadata
+Description : 
+Copyright   : (c) Alexander Vieth, 2015
+Licence     : BSD3
+Maintainer  : aovieth@gmail.com
+Stability   : experimental
+Portability : non-portable (GHC only)
+-}
+
+{-# LANGUAGE AutoDeriveTypeable #-}
+
+module Resources.Metadata (
+
+      resource
+
+    ) where
+
+import Control.Monad.Trans.Class
+import Control.Monad.Trans.Reader
+import Control.Monad.Trans.Except
+import Rest
+import Rest.Resource as R
+import Types.Server
+import Types.GameState
+import Types.GameId
+import Types.Credentials
+
+resource :: Resource (ReaderT GameId Server) (ReaderT GameId Server) () Void Void
+resource = mkResourceId
+    { R.name = "metadata"
+    , R.schema = singleton () $ unnamedSingle (const ())
+    , R.update = Just get
+    }
+  where
+    get :: Handler (ReaderT GameId Server)
+    get = secureHandler $ mkHandler (jsonO . jsonE . jsonI) handler
+
+    handler :: Env h p Credentials -> ExceptT (Reason Void) (ReaderT GameId Server) (Maybe GameMetadata)
+    handler env =
+        let credentials = input env
+        in  lift ask >>= \gameId -> doGet credentials gameId
+
+    doGet
+        :: Credentials
+        -> GameId
+        -> ExceptT (Reason Void) (ReaderT GameId Server) (Maybe GameMetadata)
+    doGet credentials gameId = withUserCredentialsForGame credentials gameId (return . gameStateViewMetadata)
diff --git a/Resources/Order.hs b/Resources/Order.hs
new file mode 100644
--- /dev/null
+++ b/Resources/Order.hs
@@ -0,0 +1,200 @@
+{-|
+Module      : Resources.Order
+Description : 
+Copyright   : (c) Alexander Vieth, 2015
+Licence     : BSD3
+Maintainer  : aovieth@gmail.com
+Stability   : experimental
+Portability : non-portable (GHC only)
+-}
+
+{-# LANGUAGE AutoDeriveTypeable #-}
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE StandaloneDeriving #-}
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE DataKinds #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+
+module Resources.Order (
+
+      resource
+
+    ) where
+
+import GHC.Generics
+import Control.Applicative
+import Control.Monad.Trans.Class
+import Control.Monad.Trans.Reader
+import Control.Monad.Trans.Except
+import Data.Typeable
+import Data.AtLeast
+import Data.TypeNat.Vect
+import Data.Aeson
+import Data.JSON.Schema
+import qualified Data.Map as M
+import qualified Data.Set as S
+import Rest
+import Rest.Resource as R
+import qualified Diplomacy.Phase as Phase
+import Diplomacy.Game as Game
+import Diplomacy.Aligned
+import qualified Diplomacy.GreatPower as DGP
+import Diplomacy.Zone
+import Diplomacy.Unit
+import Diplomacy.Subject
+import qualified Diplomacy.OrderObject as DOO
+import qualified Diplomacy.Order as DO
+import Types.Order
+import Types.Credentials
+import Types.GreatPower
+import Types.GameId
+import Types.Server
+import Types.GameState
+
+-- | The order resource will give methods
+--     PUT : ask to issue an order
+resource :: Resource (ReaderT GameId Server) (ReaderT GameId Server) () Void Void
+resource = mkResourceId
+    { R.name = "order"
+    , R.schema = singleton () $ unnamedSingle (const ())
+    , R.update = Just update
+    }
+  where
+
+    update :: Handler (ReaderT GameId Server)
+    update = secureHandler $ mkInputHandler (jsonO . jsonE . jsonI) $ doUpdate
+
+    doUpdate :: IssueOrdersInput -> ExceptT (Reason IssueOrdersError) (ReaderT GameId Server) IssueOrdersOutput
+    doUpdate input = do
+        gameId <- lift ask
+        withUserCredentialsForGame creds gameId (issueOrders gameId)
+      where
+
+        creds = credentials input
+        issuedOrders = orders input
+
+        issueOrders
+            :: GameId
+            -> GameStateView
+            -> ExceptT (Reason IssueOrdersError) (ReaderT GameId Server) IssueOrdersOutput
+        issueOrders gameId gameStateView = do
+            checkAuthorization gameStateView
+            (output, nextGame) <- issueOrders' gameStateView
+            modifyGameState gameId (return . modifier nextGame)
+            return output
+        
+        modifier :: SomeGame -> GameState -> GameState
+        modifier (SomeGame x) state = case state of
+            GameNotStarted y duration duration' -> GameNotStarted y duration duration' -- Impossible; should not have this case...
+            GameStarted m (AtLeast _ rest) duration duration' elapsed paused ->
+                GameStarted m (AtLeast (VCons (SomeGame x) VNil) rest) duration duration' elapsed paused
+
+        checkAuthorization :: GameStateView -> ExceptT (Reason IssueOrdersError) (ReaderT GameId Server) ()
+        checkAuthorization gameStateView = case gameStateView of
+            GameNotStartedView -> return ()
+            GameStartedView greatPowers _ _ _ _ _ ->
+                if issuedOrdersGreatPowers issuedOrders `S.isSubsetOf` (S.map GreatPower greatPowers)
+                then return ()
+                else throwE NotAllowed
+
+        issueOrders' :: GameStateView -> ExceptT (Reason IssueOrdersError) (ReaderT GameId Server) (IssueOrdersOutput, SomeGame)
+        issueOrders' gameStateView = case gameStateView of
+            GameNotStartedView -> throwE (domainReason IssueOrdersGameNotStarted)
+            GameStartedView _ (AtLeast (VCons (SomeGame someGame) VNil) _) _ _ _ _ -> case (someGame, issuedOrders) of
+                (TypicalGame TypicalRoundOne Unresolved x y z, Typical os) -> issueOrders'' os someGame
+                (RetreatGame RetreatRoundOne Unresolved _ _ _ _ _, Retreat os) -> issueOrders'' os someGame
+                (TypicalGame TypicalRoundTwo Unresolved x y z, Typical os) -> issueOrders'' os someGame
+                (RetreatGame RetreatRoundTwo Unresolved _ _ _ _ _, Retreat os) -> issueOrders'' os someGame
+                (AdjustGame AdjustRound Unresolved _ _ _, Adjust os) -> issueOrders'' os someGame
+                _ -> throwE (domainReason (IssueOrdersWrongPhase))
+
+        issueOrders''
+            :: forall round .
+               [(GreatPower, SomeOrder (RoundPhase round))]
+            -> Game round RoundUnresolved
+            -> ExceptT (Reason IssueOrdersError) (ReaderT GameId Server) (IssueOrdersOutput, SomeGame)
+        issueOrders'' orders game = return $ case game of
+            TypicalGame TypicalRoundOne _ _ _ _ -> (IssueOrdersOutputTypical, SomeGame . snd $ Game.issueOrders ordersMap game)
+            TypicalGame TypicalRoundTwo _ _ _ _ -> (IssueOrdersOutputTypical, SomeGame . snd $ Game.issueOrders ordersMap game)
+            RetreatGame RetreatRoundOne _ _ _ _ _ _ -> (IssueOrdersOutputRetreat, SomeGame . snd $ Game.issueOrders ordersMap game)
+            RetreatGame RetreatRoundTwo _ _ _ _ _ _ -> (IssueOrdersOutputRetreat, SomeGame . snd $ Game.issueOrders ordersMap game)
+            AdjustGame AdjustRound _ _ _ _ -> (IssueOrdersOutputAdjust, SomeGame . snd $ Game.issueOrders ordersMap game)
+          where
+            ordersMap :: M.Map Zone (Aligned Unit, DOO.SomeOrderObject (RoundPhase round))
+            ordersMap = foldr insertOrder M.empty orders
+            insertOrder
+                :: (GreatPower, SomeOrder (RoundPhase round))
+                -> M.Map Zone (Aligned Unit, DOO.SomeOrderObject (RoundPhase round))
+                -> M.Map Zone (Aligned Unit, DOO.SomeOrderObject (RoundPhase round))
+            insertOrder (greatPower, order) = case order of
+                SomeOrder (DO.SomeOrder (DO.Order (subject, object))) ->
+                    M.insert (Zone (subjectProvinceTarget subject)) (align (subjectUnit subject) (outGreatPower greatPower), DOO.SomeOrderObject object)
+
+-- Order inputs look something like this:
+--
+--     [["England", "F Spa NC - Mid"], ["France", "A Mar - Spa"]]
+--
+-- It's important that we accept orders for multiple great powers, since one
+-- player may control more than one great power.
+data IssuedOrders where
+    Typical :: [(GreatPower, SomeOrder Phase.Typical)] -> IssuedOrders
+    Retreat :: [(GreatPower, SomeOrder Phase.Retreat)] -> IssuedOrders
+    Adjust :: [(GreatPower, SomeOrder Phase.Adjust)] -> IssuedOrders
+
+deriving instance Generic IssuedOrders
+deriving instance Typeable IssuedOrders
+instance FromJSON IssuedOrders
+instance ToJSON IssuedOrders
+instance JSONSchema IssuedOrders where
+    schema = gSchema
+
+-- | The set of all GreatPowers for which at least one order is relevant.
+issuedOrdersGreatPowers :: IssuedOrders -> S.Set GreatPower
+issuedOrdersGreatPowers orders = case orders of
+    Typical os -> foldr (S.insert . fst) S.empty os
+    Retreat os -> foldr (S.insert . fst) S.empty os
+    Adjust os -> foldr (S.insert . fst) S.empty os
+
+data IssueOrdersInput = IssueOrdersInput {
+      credentials :: Credentials
+    , orders :: IssuedOrders
+    }
+
+deriving instance Generic IssueOrdersInput
+deriving instance Typeable IssueOrdersInput
+instance FromJSON IssueOrdersInput
+instance ToJSON IssueOrdersInput
+instance JSONSchema IssueOrdersInput where
+    schema = gSchema
+
+-- | TODO throw in the sets of validity criterion, indexed by the relevant
+--   order: [["France", "A Mar - Spa SC"], ["MoveUnitCanOccupy"]]
+--   And of course, for the adjust phase, take the other set indexed by the
+--   relevant great power.
+data IssueOrdersOutput where
+    IssueOrdersOutputTypical :: IssueOrdersOutput
+    IssueOrdersOutputRetreat :: IssueOrdersOutput
+    IssueOrdersOutputAdjust :: IssueOrdersOutput
+
+deriving instance Generic IssueOrdersOutput
+deriving instance Typeable IssueOrdersOutput
+instance FromJSON IssueOrdersOutput
+instance ToJSON IssueOrdersOutput
+instance JSONSchema IssueOrdersOutput where
+    schema = gSchema
+
+-- | We do not consider invalid orders to be an error. If invalid orders
+--   are given, the response will reflect it.
+data IssueOrdersError
+    = IssueOrdersGameNotStarted
+    | IssueOrdersWrongPhase
+
+instance ToResponseCode IssueOrdersError where
+    toResponseCode _ = 403
+
+deriving instance Generic IssueOrdersError
+deriving instance Typeable IssueOrdersError
+instance FromJSON IssueOrdersError
+instance ToJSON IssueOrdersError
+instance JSONSchema IssueOrdersError where
+    schema = gSchema
diff --git a/Resources/Pause.hs b/Resources/Pause.hs
new file mode 100644
--- /dev/null
+++ b/Resources/Pause.hs
@@ -0,0 +1,97 @@
+{-|
+Module      : Resources.Paused
+Description : 
+Copyright   : (c) Alexander Vieth, 2015
+Licence     : BSD3
+Maintainer  : aovieth@gmail.com
+Stability   : experimental
+Portability : non-portable (GHC only)
+-}
+
+{-# LANGUAGE AutoDeriveTypeable #-}
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE StandaloneDeriving #-}
+{-# LANGUAGE GADTs #-}
+
+module Resources.Pause (
+
+      resource
+
+    ) where
+
+import GHC.Generics
+import Data.Typeable
+import Control.Monad.Trans.Class
+import Control.Monad.Trans.Reader
+import Control.Monad.Trans.Except
+import Control.Monad.State.Class as SC
+import qualified Data.Map as M
+import Data.AtLeast
+import Data.TypeNat.Vect
+import Data.Aeson
+import Data.JSON.Schema
+import Rest
+import Rest.Resource as R
+import Diplomacy.Game
+import Types.Server
+import Types.GameId
+import Types.Credentials
+import Types.GameState
+
+resource :: Resource (ReaderT GameId Server) (ReaderT GameId Server) () Void Void
+resource = mkResourceId
+    { R.name = "pause"
+    , R.schema = singleton () $ unnamedSingle (const ())
+    , R.update = Just update
+    }
+  where
+
+    update :: Handler (ReaderT GameId Server)
+    update = secureHandler $ mkInputHandler (jsonO . jsonE . jsonI) $ doUpdate
+
+    doUpdate :: PauseInput -> ExceptT (Reason PauseError) (ReaderT GameId Server) PauseOutput
+    doUpdate input = withAdminCredentials creds pauseGame
+      where
+        PauseInput creds = input
+
+    pauseGame :: ExceptT (Reason PauseError) (ReaderT GameId Server) PauseOutput
+    pauseGame = do
+        gameId <- lift ask
+        modifyGameState gameId modifier
+        return GamePaused
+
+    modifier :: GameState -> ExceptT (Reason PauseError) (ReaderT GameId Server) GameState
+    modifier gameState = case gameState of
+        GameNotStarted _ _ _ -> throwE (domainReason PauseGameNotStarted)
+        GameStarted m games duration duration' elapsed _ ->
+            return (GameStarted m games duration duration' elapsed True)
+
+data PauseError = PauseGameNotStarted
+
+instance ToResponseCode PauseError where
+    toResponseCode _ = 403
+
+deriving instance Generic PauseError
+deriving instance Typeable PauseError
+instance FromJSON PauseError
+instance ToJSON PauseError
+instance JSONSchema PauseError where
+    schema = gSchema
+
+newtype PauseInput = PauseInput Credentials
+
+deriving instance Generic PauseInput
+deriving instance Typeable PauseInput
+instance FromJSON PauseInput
+instance ToJSON PauseInput
+instance JSONSchema PauseInput where
+    schema = gSchema
+
+data PauseOutput = GamePaused
+
+deriving instance Generic PauseOutput
+deriving instance Typeable PauseOutput
+instance FromJSON PauseOutput
+instance ToJSON PauseOutput
+instance JSONSchema PauseOutput where
+    schema = gSchema
diff --git a/Resources/Resolution.hs b/Resources/Resolution.hs
new file mode 100644
--- /dev/null
+++ b/Resources/Resolution.hs
@@ -0,0 +1,98 @@
+{-|
+Module      : Resources.Resolution
+Description : 
+Copyright   : (c) Alexander Vieth, 2015
+Licence     : BSD3
+Maintainer  : aovieth@gmail.com
+Stability   : experimental
+Portability : non-portable (GHC only)
+-}
+
+{-# LANGUAGE AutoDeriveTypeable #-}
+
+module Resources.Resolution (
+
+      resource
+
+    ) where
+
+import Control.Monad
+import Control.Monad.Trans.Class
+import Control.Monad.Trans.Reader
+import Control.Monad.Trans.Except
+import Rest
+import Rest.Resource as R
+import Rest.Dictionary.Types
+import Types.Server
+import Types.GameState
+import Types.GameId
+import Types.Credentials
+import Diplomacy.Game
+import Diplomacy.Turn
+
+resource :: Resource (ReaderT GameId Server) (ReaderT GameId Server) () Void Void
+resource = mkResourceId
+    { R.name = "resolution"
+    , R.schema = singleton () $ unnamedSingle (const ())
+    , R.update = Just get
+    }
+  where
+    get :: Handler (ReaderT GameId Server)
+    get = secureHandler $ mkHandler (addPar roundParam . mkPar turnParam . jsonO . jsonE . jsonI) handler
+
+    turnParam :: Param (Maybe Turn)
+    turnParam = Param ["turn"] $ \xs -> case xs of
+        (Just x : _) -> case reads x :: [(Int, String)] of
+            [(i, [])] -> case turnFromInt i of
+                Just t -> Right (Just t)
+                _ -> Left (ParseError "Could not parse turn")
+            _ -> Left (ParseError "Could not parse turn")
+        [Nothing] -> Right Nothing
+        _ -> Left (ParseError "Could not parse turn")
+
+    roundParam :: Param (Maybe Round)
+    roundParam = Param ["round"] $ \xs -> case xs of
+        (Just x : _) -> case reads x :: [(Int, String)] of
+            [(i, [])] -> let min = minBound :: Round
+                             max = maxBound :: Round
+                         in  if i >= fromEnum min && i <= fromEnum max
+                             then Right (Just (toEnum i))
+                             else Left (ParseError "Could not parse round")
+            _ -> Left (ParseError "Could not parse round")
+        [Nothing] -> Right Nothing
+        _ -> Left (ParseError "Could not parse round")
+
+
+    handler :: Env h (Maybe Round, Maybe Turn) Credentials -> ExceptT (Reason Void) (ReaderT GameId Server) (Maybe GameResolution)
+    handler env =
+        let credentials = input env
+            (maybeRound, maybeTurn) = param env
+        in  lift ask >>= \gameId -> doGet credentials gameId maybeTurn maybeRound
+
+    doGet
+        :: Credentials
+        -> GameId
+        -> Maybe Turn
+        -> Maybe Round
+        -> ExceptT (Reason Void) (ReaderT GameId Server) (Maybe GameResolution)
+    doGet credentials gameId maybeTurn maybeRound = withUserCredentialsForGame credentials gameId f
+      where
+        f gameStateView = return $ do
+            metadata <- gameStateViewMetadata gameStateView
+            let turn' = maybe (metadataTurn metadata) id maybeTurn
+            -- Prevent giving the resolution of the latest (currently ongoing)
+            -- round! This would allow any player to see the orders of other
+            -- players.
+            let (turn, round) = case maybeRound of
+                    Just x -> (turn', x)
+                    Nothing -> if metadataRound metadata /= RoundOne
+                               then (turn', prevRound (metadataRound metadata))
+                               else case prevTurn turn' of
+                                   -- In this case we're at turn 0 round 0; we
+                                   -- give the current turn and round and the
+                                   -- upcoming guard will fail.
+                                   Nothing -> (turn', metadataRound metadata)
+                                   Just t -> (t, prevRound (metadataRound metadata))
+            guard (turn <= metadataTurn metadata)
+            guard (turn < metadataTurn metadata || round < metadataRound metadata)
+            gameStateViewResolution turn round gameStateView
diff --git a/Resources/Start.hs b/Resources/Start.hs
new file mode 100644
--- /dev/null
+++ b/Resources/Start.hs
@@ -0,0 +1,147 @@
+{-|
+Module      : Resources.Start
+Description : 
+Copyright   : (c) Alexander Vieth, 2015
+Licence     : BSD3
+Maintainer  : aovieth@gmail.com
+Stability   : experimental
+Portability : non-portable (GHC only)
+-}
+
+{-# LANGUAGE AutoDeriveTypeable #-}
+{-# LANGUAGE StandaloneDeriving #-}
+{-# LANGUAGE DeriveGeneric #-}
+
+module Resources.Start (
+
+      resource
+
+    ) where
+
+import GHC.Generics
+import Control.Monad.Trans.Class
+import Control.Monad.State.Class as SC
+import Control.Monad.Trans.Reader
+import Control.Monad.Trans.Except
+import Data.Typeable
+import qualified Data.Map as M
+import qualified Data.Set as S
+import Data.Stream as Stream
+import Data.AtLeast
+import Data.TypeNat.Vect
+import Data.Aeson
+import Data.JSON.Schema
+import Data.Functor.Identity
+import Data.Functor.Constant
+import Data.List (sortBy, (\\))
+import Data.Hourglass
+import Rest
+import Rest.Resource as R
+import Diplomacy.GreatPower
+import Diplomacy.Game
+import Types.Server
+import Types.GameId
+import Types.GameState
+import Types.UserData as UD
+import Types.Credentials as Credentials
+
+-- | Use this resource to start a game.
+--
+--     PUT : ask to start the game, passing the administrator credentials
+--           in the request body.
+--
+resource :: Resource (ReaderT GameId Server) (ReaderT GameId Server) () Void Void
+resource = mkResourceId
+    { R.name = "start"
+    , R.schema = singleton () $ unnamedSingle (const ())
+    , R.update = Just update
+    }
+  where
+
+    update :: Handler (ReaderT GameId Server)
+    update = secureHandler $ mkInputHandler (jsonO . jsonE . jsonI) $ doUpdate
+
+    doUpdate :: StartGameInput -> ExceptT (Reason StartGameError) (ReaderT GameId Server) StartGameOutput
+    doUpdate input = withAdminCredentials creds startGame
+      where
+
+        StartGameInput creds = input
+
+        startGame :: ExceptT (Reason StartGameError) (ReaderT GameId Server) StartGameOutput
+        startGame = do
+            state <- SC.get
+            gameId <- lift ask
+            let ds = randomDoubles state
+            let t = currentTime state
+            case M.lookup gameId (games state) of
+                Nothing -> throwE NotFound
+                Just (password, gameState) -> do
+                    case startGameState t ds gameState of
+                        Left e -> throwE (domainReason e)
+                        Right gs -> do
+                            SC.put (state { games = M.alter (const (Just (password, gs))) gameId (games state) })
+                            return Started
+
+-- | We assume the GameState has at most 7 registered users.
+--   This is always true if the GameState is modified by registerUser only.
+--   TODO should enforce this at the type level, but it's not so fragile so
+--   whatever.
+startGameState :: Elapsed -> Stream.Stream Double -> GameState -> Either StartGameError GameState
+startGameState t randomDoubles game = case game of
+    GameNotStarted map duration duration' ->
+        if notEnoughPlayers
+        then Left NotEnoughPlayers
+        else if tooManyPlayers
+        then Left TooManyPlayers
+        else Right (GameStarted map' (AtLeast (VCons (SomeGame newGame) VNil) []) duration duration' t False)
+      where
+        registered :: [(Username, Password)]
+        registered = fmap (\(x, y) -> (x, UD.password y)) (M.toList map)
+        registeredRandomOrder :: [(Username, Password)]
+        registeredRandomOrder = fmap fst (sortBy (\(_, x) (_, y) -> x `compare` y) (Prelude.zip registered (Stream.toList randomDoubles)))
+        controlUnits :: [S.Set GreatPower]
+        controlUnits = case length registered of
+            7 -> fmap S.singleton [minBound..maxBound]
+            6 -> fmap S.singleton ([minBound..maxBound] \\ [Italy])
+            5 -> fmap S.singleton ([minBound..maxBound] \\ [Germany, Italy])
+            4 -> [S.singleton England, S.fromList [Austria, France], S.fromList [Germany, Turkey], S.fromList [Italy, Russia]]
+            3 -> [S.fromList [England, Germany, Austria], S.fromList [Russia, Italy], S.fromList [France, Turkey]]
+            _ -> []
+        notEnoughPlayers = length registered < 3
+        tooManyPlayers = length registered > 7
+        -- We randomly assign each player to a control unit.
+        assignments :: [((Username, Password), S.Set GreatPower)]
+        assignments = Prelude.zip registeredRandomOrder controlUnits
+        makeUserData :: ((Username, Password), S.Set GreatPower) -> (Username, UserData S.Set)
+        makeUserData ((u, p), s) = (u, UserData p s)
+        map' = M.fromList (fmap makeUserData assignments)
+    GameStarted _ _ _ _ _ _ -> Left GameAlreadyStarted
+
+newtype StartGameInput = StartGameInput Credentials
+
+deriving instance Generic StartGameInput
+deriving instance Typeable StartGameInput
+instance FromJSON StartGameInput
+instance ToJSON StartGameInput
+instance JSONSchema StartGameInput where
+    schema = gSchema
+
+data StartGameOutput = Started
+
+deriving instance Generic StartGameOutput
+deriving instance Typeable StartGameOutput
+instance FromJSON StartGameOutput
+instance ToJSON StartGameOutput
+instance JSONSchema StartGameOutput where
+    schema = gSchema
+
+data StartGameError = GameAlreadyStarted | NotEnoughPlayers | TooManyPlayers
+
+deriving instance Generic StartGameError
+deriving instance Typeable StartGameError
+instance ToJSON StartGameError
+instance JSONSchema StartGameError where
+    schema = gSchema
+
+instance ToResponseCode StartGameError where
+    toResponseCode _ = 403
diff --git a/Router.hs b/Router.hs
new file mode 100644
--- /dev/null
+++ b/Router.hs
@@ -0,0 +1,79 @@
+{-|
+Module      : 
+Description : 
+Copyright   : (c) Alexander Vieth, 2015
+Licence     : BSD3
+Maintainer  : aovieth@gmail.com
+Stability   : experimental
+Portability : non-portable (GHC only)
+-}
+
+{-# LANGUAGE AutoDeriveTypeable #-}
+
+module Router (
+
+      router
+
+    ) where
+
+import Control.Monad.Trans.Reader
+import Rest
+import Rest.Api
+import Rest.Resource as R
+import Resources.Game as Game
+import Resources.Join as Join
+import Resources.Start as Start
+import Resources.Order as Order
+import Resources.Advance as Advance
+import Resources.Pause as Pause
+import Resources.Metadata as Metadata
+import Resources.Resolution as Resolution
+import Resources.Client as Client
+import Types.GameId
+import Types.Server
+
+router :: Router Server Server
+router = root -/ client --/ game ---/ gameJoin
+                                 ---/ gameStart
+                                 ---/ gameOrder
+                                 ---/ gameAdvance
+                                 ---/ gamePause
+                                 ---/ gameMetadata
+                                 ---/ gameResolution
+                        --/ admin
+
+game :: Router (Server) (ReaderT GameId Server)
+game = route Game.resource
+
+gameJoin :: Router (ReaderT GameId Server) (ReaderT GameId Server)
+gameJoin = route Join.resource
+
+gameStart :: Router (ReaderT GameId Server) (ReaderT GameId Server)
+gameStart = route Start.resource
+
+gameOrder :: Router (ReaderT GameId Server) (ReaderT GameId Server)
+gameOrder = route Order.resource
+
+gameAdvance :: Router (ReaderT GameId Server) (ReaderT GameId Server)
+gameAdvance = route Advance.resource
+
+gamePause :: Router (ReaderT GameId Server) (ReaderT GameId Server)
+gamePause = route Pause.resource
+
+gameMetadata :: Router (ReaderT GameId Server) (ReaderT GameId Server)
+gameMetadata = route Metadata.resource
+
+gameResolution :: Router (ReaderT GameId Server) (ReaderT GameId Server)
+gameResolution = route Resolution.resource
+
+client :: Router Server Server
+client = route Client.resource
+
+admin :: Router (Server) (ReaderT GameId Server)
+admin = route adminResource
+
+adminResource :: Resource Server (ReaderT GameId Server) GameId Void Void
+adminResource = mkResourceReader
+    { R.name = "admin"
+    , R.schema = noListing $ unnamedSingle GameId
+    }
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,2 @@
+import Distribution.Simple
+main = defaultMain
diff --git a/Types/Credentials.hs b/Types/Credentials.hs
new file mode 100644
--- /dev/null
+++ b/Types/Credentials.hs
@@ -0,0 +1,41 @@
+{-|
+Module      : Types.Credentials
+Description : 
+Copyright   : (c) Alexander Vieth, 2015
+Licence     : BSD3
+Maintainer  : aovieth@gmail.com
+Stability   : experimental
+Portability : non-portable (GHC only)
+-}
+
+{-# LANGUAGE AutoDeriveTypeable #-}
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE StandaloneDeriving #-}
+{-# LANGUAGE FlexibleContexts #-}
+
+module Types.Credentials (
+
+      Username
+    , Password
+    , Credentials(..)
+
+    ) where
+
+import GHC.Generics
+import Data.Typeable
+import Data.Aeson
+import Data.JSON.Schema
+
+type Username = String
+type Password = String
+
+data Credentials = Credentials {
+      username :: Username
+    , password :: Password
+    }
+    deriving (Eq, Generic, Typeable)
+
+instance FromJSON Credentials
+instance ToJSON Credentials
+instance JSONSchema Credentials where
+    schema = gSchema
diff --git a/Types/GameId.hs b/Types/GameId.hs
new file mode 100644
--- /dev/null
+++ b/Types/GameId.hs
@@ -0,0 +1,31 @@
+{-|
+Module      : Types.GameId
+Description : 
+Copyright   : (c) Alexander Vieth, 2015
+Licence     : BSD3
+Maintainer  : aovieth@gmail.com
+Stability   : experimental
+Portability : non-portable (GHC only)
+-}
+
+{-# LANGUAGE AutoDeriveTypeable #-}
+{-# LANGUAGE DeriveGeneric #-}
+
+module Types.GameId (
+
+      GameId(..)
+
+    ) where
+
+import GHC.Generics
+import Data.Typeable
+import Data.Aeson
+import Data.JSON.Schema
+
+newtype GameId = GameId String
+    deriving (Eq, Ord, Show, Generic)
+
+instance FromJSON GameId
+instance ToJSON GameId
+instance JSONSchema GameId where
+    schema = gSchema
diff --git a/Types/GameState.hs b/Types/GameState.hs
new file mode 100644
--- /dev/null
+++ b/Types/GameState.hs
@@ -0,0 +1,389 @@
+{-|
+Module      : Types.GameState
+Description : 
+Copyright   : (c) Alexander Vieth, 2015
+Licence     : BSD3
+Maintainer  : aovieth@gmail.com
+Stability   : experimental
+Portability : non-portable (GHC only)
+-}
+
+{-# LANGUAGE AutoDeriveTypeable #-}
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE DataKinds #-}
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE StandaloneDeriving #-}
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+
+module Types.GameState (
+
+      SomeGame(..)
+    , GameState(..)
+    , GameStateView(..)
+    , GameMetadata(..)
+    , GameData(..)
+    , GameResolution(..)
+
+    , gameStateMatchCredentials
+    , gameStateViewMetadata
+    , gameStateViewData
+    , gameStateViewResolution
+
+    ) where
+
+import GHC.Generics
+import Control.DeepSeq
+import Control.Monad
+import Control.Applicative
+import qualified Data.Map as M
+import qualified Data.Set as S
+import qualified Data.Text as T
+import Data.AtLeast
+import Data.TypeNat.Nat
+import Data.Functor.Identity
+import Data.Functor.Constant
+import Data.Hourglass
+import Data.Aeson
+import Data.JSON.Schema as Schema
+import Diplomacy.Game
+import Diplomacy.Turn
+import Diplomacy.Province
+import Diplomacy.Zone
+import Diplomacy.Aligned
+import Diplomacy.Phase
+import qualified Diplomacy.Order as DO
+import qualified Diplomacy.OrderObject as DOO
+import Diplomacy.OrderResolution
+import qualified Diplomacy.Unit as DU
+import qualified Diplomacy.GreatPower as DGP
+import Diplomacy.Control
+import Diplomacy.Occupation
+import Types.Credentials as Cred
+import Types.UserData as UD
+import Types.Credentials (Username)
+import Types.GreatPower
+import Types.Unit
+import Types.Order
+
+data SomeGame where
+    SomeGame :: Game round RoundUnresolved -> SomeGame
+
+data GameState where
+
+    GameNotStarted
+        :: M.Map Username (UserData (Constant ()))
+        -> Duration
+        -> Duration
+        -> GameState
+
+    GameStarted
+        :: M.Map Username (UserData S.Set)
+        -- ^ we use Set rather than Identity, because one player can control
+        --   more than one great power!
+        -> AtLeast One SomeGame
+        -- ^ All (unresolved) game states seen so far. First entry is the
+        --   latest, and other entires retain the orders which were present
+        --   when the game was advanced.
+        -> Duration
+        -- ^ Period of this game; after this duration, the game advances to the
+        --   next round if it's in typical phase.
+        -> Duration
+        -- ^ Second period of this game; after this duration, the game advances
+        --   to the next round if it's in adjust or retreat phase.
+        -> Elapsed
+        -- ^ Unix epoch time when this game was last advanced or when it was
+        --   started.
+        -> Bool
+        -- ^ True iff the game is paused, meaning it should not advance
+        --   automatically after a set amount of time.
+        -> GameState
+
+deriving instance Generic GameState
+instance NFData GameState
+
+-- | The game state viewed as a certain player, who controls a set of
+--   GreatPowers.
+data GameStateView where
+    GameNotStartedView :: GameStateView
+    GameStartedView
+        :: S.Set DGP.GreatPower
+        -> AtLeast One SomeGame
+        -> Duration
+        -> Duration
+        -> Elapsed
+        -> Bool
+        -> GameStateView
+
+gameStateViewMetadata :: GameStateView -> Maybe GameMetadata
+gameStateViewMetadata view = case view of
+    GameNotStartedView -> Nothing
+    GameStartedView greatPowers games duration secondDuration elapsed paused -> Just $ GameMetadata {
+          metadataGreatPowers = S.map GreatPower greatPowers
+        , metadataTurn = turn
+        , metadataRound = round
+        , metadataDuration = duration
+        , metadataSecondDuration = secondDuration
+        , metadataElapsed = elapsed
+        , metadataPaused = paused
+        }
+      where
+        game = Data.AtLeast.head games
+        turn = case game of
+            SomeGame game -> gameTurn game
+        round = case game of
+            SomeGame game -> gameRound game
+
+-- | Number of places from the back of a list of SomeGame at which the game
+--   for this turn and round is found.
+gameIndex :: Turn -> Round -> Int
+gameIndex turn round = roundToInt round + 5*(turnToInt turn)
+
+findGame :: Turn -> Round -> AtLeast One SomeGame -> Maybe SomeGame
+findGame turn round games =
+    let gamesList = Data.AtLeast.toList games
+        index = gameIndex turn round
+    in  findGame' index (reverse gamesList)
+  where
+    findGame' :: Int -> [SomeGame] -> Maybe SomeGame
+    findGame' n games = case games of
+        [] -> Nothing
+        (x : xs) -> if n == 0 then Just x else findGame' (n-1) xs
+
+-- Careful! This will give the resolved orders for any turn/round pair found,
+-- even if it's the latest (ongoing) game! The client-facing code must be
+-- careful not to fulfill a user's request for the resolutions of the latest
+-- round.
+gameStateViewResolution :: Turn -> Round -> GameStateView -> Maybe GameResolution
+gameStateViewResolution turn round view = case view of
+    GameNotStartedView -> Nothing
+    GameStartedView _ games _ _ _ _ -> case findGame turn round games of
+        Nothing -> Nothing
+        Just (SomeGame game) -> Just $ case resolve game of
+            TypicalGame _ _ _ zonedResolvedOrders control -> GameResolutionTypical (M.map mapper zonedResolvedOrders) (M.map GreatPower control)
+            RetreatGame _ _ _ _ zonedResolvedOrders occupation control -> GameResolutionRetreat (M.map mapper zonedResolvedOrders) (M.map (fmap Unit) occupation) (M.map GreatPower control)
+            AdjustGame _ _ _ zonedResolvedOrders control -> GameResolutionAdjust (M.map mapper zonedResolvedOrders) (M.map GreatPower control)
+  where
+    mapper
+        :: (Aligned DU.Unit, SomeResolved DOO.OrderObject phase)
+        -> (Aligned Unit, SomeOrderObject phase, Bool)
+    mapper (aunit, SomeResolved (object, res)) = (align unit greatPower, someObject, bool)
+      where
+        greatPower = alignedGreatPower aunit
+        unit = Unit (alignedThing aunit)
+        someObject = SomeOrderObject (DOO.SomeOrderObject object)
+        bool = case res of
+            Nothing -> True
+            _ -> False
+
+gameStateViewData :: Turn -> Round -> GameStateView -> Maybe GameData
+gameStateViewData turn round view = case view of
+    GameNotStartedView -> Nothing
+    GameStartedView greatPowers games _ _ _ _ -> case findGame turn round games of
+        Nothing -> Nothing
+        Just (SomeGame game) -> Just $ case game of
+            TypicalGame _ _ _ zonedOrders control -> GameDataTypical (M.map (mapper greatPowers) zonedOrders) (M.map GreatPower control)
+            RetreatGame _ _ _ _ zonedOrders occupation control -> GameDataRetreat (M.map (mapper greatPowers) zonedOrders) (M.map (fmap Unit) occupation) (M.map GreatPower control)
+            AdjustGame _ _ _ zonedOrders control -> GameDataAdjust (M.map (mapper greatPowers) zonedOrders) (M.map GreatPower control)
+  where
+    mapper
+        :: S.Set DGP.GreatPower
+        -> (Aligned DU.Unit, DOO.SomeOrderObject phase)
+        -> (Aligned Unit, Maybe (SomeOrderObject phase))
+    mapper greatPowers (aunit, someOrderObject) = (align unit greatPower, maybeSomeOrderObject)
+      where
+        greatPower = alignedGreatPower aunit
+        unit = Unit (alignedThing aunit)
+        maybeSomeOrderObject = case S.member greatPower greatPowers of
+            True -> Just (SomeOrderObject someOrderObject)
+            False -> Nothing
+
+data GameMetadata = GameMetadata {
+      metadataGreatPowers :: S.Set GreatPower
+    , metadataTurn :: Turn
+    , metadataRound :: Round
+    , metadataDuration :: Duration
+    , metadataSecondDuration :: Duration
+    , metadataElapsed :: Elapsed
+    , metadataPaused :: Bool
+    }
+
+instance ToJSON GameMetadata where
+    toJSON metadata = object [
+          "greatPowers" .= greatPowers
+        , "turn" .= turn
+        , "round" .= round
+        , "duration" .= duration
+        , "secondDuration" .= secondDuration
+        , "elapsed" .= secondsElapsed
+        , "paused" .= paused
+        ]
+      where
+        greatPowers = metadataGreatPowers metadata
+        turn = turnToInt (metadataTurn metadata)
+        round = roundToInt (metadataRound metadata)
+        -- We only show the duration up to the minute.
+        -- These (hours and minutes) are Int64. Can't be bothered to
+        -- import that so I can write the type signature.
+        Hours hours = durationHours (metadataDuration metadata)
+        Minutes minutes = durationMinutes (metadataDuration metadata)
+        Hours secondHours = durationHours (metadataSecondDuration metadata)
+        Minutes secondMinutes = durationMinutes (metadataSecondDuration metadata)
+        -- seconds is an Int64 giving unix time when the game was last
+        -- advanced (or started, in case it's round 1 turn 1).
+        Elapsed (Seconds secondsElapsed) = metadataElapsed metadata
+        duration = object [
+              "hours" .= hours
+            , "minutes" .= minutes
+            ]
+        secondDuration = object [
+              "hours" .= secondHours
+            , "minutes" .= secondMinutes
+            ]
+        paused = metadataPaused metadata
+
+instance JSONSchema GameMetadata where
+    schema _ = Schema.Object [
+          greatPowersField
+        , turnField
+        , roundField
+        , durationField
+        , secondDurationField
+        , elapsedField
+        , pausedField
+        ]
+      where
+        greatPowersField = Field "greatPowers" True (Schema.Array (LengthBound (Just 1) (Just 7)) True (Value unboundedLength))
+        turnField = Field "turn" True (Schema.Number (Bound (Just 0) Nothing))
+        roundField = Field "round" True (Schema.Number (Bound (Just 0) (Just 4)))
+        durationField = Field "duration" True (Schema.Object [hoursField, minutesField])
+        secondDurationField = Field "secondDuration" True (Schema.Object [hoursField, minutesField])
+        elapsedField = Field "elapsed" True (Schema.Number (Bound (Just 0) Nothing))
+        pausedField = Field "paused" True Boolean
+        hoursField = Field "hours" True (Schema.Number (Bound (Just 0) Nothing))
+        minutesField = Field "minutes" True (Schema.Number (Bound (Just 0) (Just 59)))
+
+-- For now we give only a Bool to indicate success or failure (true for
+-- success). In the future we should give the reason. This is not super easy
+-- to do because we use GADTs with phase and order type parameters, so we
+-- can't just derive generic JSON instances.
+data GameResolution where
+    GameResolutionTypical
+        :: M.Map Zone (Aligned Unit, SomeOrderObject Typical, Bool)
+        -> M.Map Province GreatPower
+        -> GameResolution
+    GameResolutionRetreat
+        :: M.Map Zone (Aligned Unit, SomeOrderObject Retreat, Bool)
+        -> M.Map Zone (Aligned Unit)
+        -> M.Map Province GreatPower
+        -> GameResolution
+    GameResolutionAdjust
+        :: M.Map Zone (Aligned Unit, SomeOrderObject Adjust, Bool)
+        -> M.Map Province GreatPower
+        -> GameResolution
+
+instance ToJSON GameResolution where
+    toJSON gameResolution = case gameResolution of
+        GameResolutionTypical zonedResolvedOrders control -> object [
+              "resolved" .= zonedResolvedOrders
+            , "control" .= control
+            ]
+        GameResolutionRetreat zonedResolvedOrders occupation control -> object [
+              "resolved" .= zonedResolvedOrders
+            , "occupation" .= occupation
+            , "control" .= control
+            ]
+        GameResolutionAdjust zonedResolvedOrders control -> object [
+              "resolved" .= zonedResolvedOrders
+            , "control" .= control
+            ]
+
+instance JSONSchema GameResolution where
+    schema _ = Schema.Object []
+
+data GameData where
+    GameDataTypical
+        :: M.Map Zone (Aligned Unit, Maybe (SomeOrderObject Typical))
+        -> M.Map Province GreatPower
+        -> GameData
+    GameDataRetreat
+        :: M.Map Zone (Aligned Unit, Maybe (SomeOrderObject Retreat))
+        -> M.Map Zone (Aligned Unit)
+        -> M.Map Province GreatPower
+        -> GameData
+    GameDataAdjust
+        :: M.Map Zone (Aligned Unit, Maybe (SomeOrderObject Adjust))
+        -> M.Map Province GreatPower
+        -> GameData
+
+instance ToJSON GameData where
+    toJSON gameData = case gameData of
+        GameDataTypical zonedOrders control -> object [
+              "occupation" .= zonedOrders
+            , "control" .= control
+            ]
+        GameDataRetreat zonedOrders occupation control -> object [
+              "dislodgement" .= zonedOrders
+            , "occupation" .= occupation
+            , "control" .= control
+            ]
+        GameDataAdjust zonedOrders control -> object [
+              "occupation" .= zonedOrders
+            , "control" .= control
+            ]
+
+instance JSONSchema GameData where
+    schema _ = Schema.Object []
+
+instance ToJSON t => ToJSON (M.Map Zone t) where
+    toJSON map = toJSON textKeyedMap
+      where
+        textKeyedMap :: M.Map T.Text t
+        textKeyedMap = M.foldWithKey (\z x -> M.insert (zoneToText z) x) M.empty map
+        zoneToText :: Zone -> T.Text
+        zoneToText = printProvinceTarget . zoneProvinceTarget
+
+instance ToJSON t => ToJSON (M.Map Province t) where
+    toJSON map = toJSON textKeyedMap
+      where
+        textKeyedMap :: M.Map T.Text t
+        textKeyedMap = M.foldWithKey(\p x -> M.insert (printProvince p) x) M.empty map
+
+instance ToJSON (Aligned Unit) where
+    toJSON aunit = toJSON (greatPower, unit)
+      where
+        greatPower = GreatPower (alignedGreatPower aunit)
+        unit = alignedThing aunit
+
+instance ToJSON (Aligned Unit, Maybe (SomeOrderObject phase)) where
+    toJSON (aunit, maybeObject) = toJSON (greatPower, unit, maybeObject)
+      where
+        greatPower = GreatPower (alignedGreatPower aunit)
+        unit = alignedThing aunit
+
+instance ToJSON (Aligned Unit, SomeOrderObject phase, Bool) where
+    toJSON (aunit, object, bool) = toJSON (greatPower, unit, object, bool)
+      where
+        greatPower = GreatPower (alignedGreatPower aunit)
+        unit = alignedThing aunit
+
+-- | Get a GameStateView for given Credentials, i.e. the set of GreatPowers
+--   that this user controls, along with a SomeGame if the game is started,
+--   no information if the game is not started, or Nothing in case the
+--   Credentials do not match anyone.
+gameStateMatchCredentials :: Credentials -> GameState -> Maybe GameStateView
+gameStateMatchCredentials creds gameState = case gameState of
+    GameNotStarted map _ _ -> case M.lookup uname map of
+        Nothing -> Nothing
+        Just ud -> case pwd == UD.password ud of
+            False -> Nothing
+            True -> Just GameNotStartedView
+    GameStarted map someGames duration duration' elapsed paused -> case M.lookup uname map of
+        Nothing -> Nothing
+        Just ud -> case pwd == UD.password ud of
+            False -> Nothing
+            True -> Just (GameStartedView (UD.greatPower ud) someGames duration duration' elapsed paused)
+  where
+    uname = Cred.username creds
+    pwd = Cred.password creds
diff --git a/Types/GreatPower.hs b/Types/GreatPower.hs
new file mode 100644
--- /dev/null
+++ b/Types/GreatPower.hs
@@ -0,0 +1,47 @@
+{-|
+Module      : Types.GreatPower
+Description : Wrapper for Diplomacy.GreatPower to make it JSON-able
+Copyright   : (c) Alexander Vieth, 2015
+Licence     : BSD3
+Maintainer  : aovieth@gmail.com
+Stability   : experimental
+Portability : non-portable (GHC only)
+-}
+
+{-# LANGUAGE AutoDeriveTypeable #-}
+{-# LANGUAGE DeriveGeneric #-}
+
+module Types.GreatPower (
+
+      GreatPower(..)
+
+    , outGreatPower
+
+    ) where
+
+import GHC.Generics
+import Control.Applicative
+import Data.String (fromString)
+import Data.Typeable
+import qualified Data.Text as T
+import Data.Aeson
+import Data.JSON.Schema
+import qualified Diplomacy.GreatPower as D
+
+newtype GreatPower = GreatPower D.GreatPower
+    deriving (Eq, Ord, Show, Generic, Typeable)
+
+outGreatPower :: GreatPower -> D.GreatPower
+outGreatPower (GreatPower x) = x
+
+instance ToJSON GreatPower where
+    toJSON (GreatPower p) = String . fromString . show $ p
+
+instance FromJSON GreatPower where
+    parseJSON (String txt) = case reads (T.unpack txt) of
+        (greatPower, _) : _ -> return (GreatPower greatPower)
+        _ -> empty
+    parseJSON _ = empty
+
+instance JSONSchema GreatPower where
+    schema _ = Value (LengthBound Nothing Nothing)
diff --git a/Types/Order.hs b/Types/Order.hs
new file mode 100644
--- /dev/null
+++ b/Types/Order.hs
@@ -0,0 +1,243 @@
+{-|
+Module      : Types.Order
+Description : Wrapper for Diplomacy.Order making to JSON-able.
+Copyright   : (c) Alexander Vieth, 2015
+Licence     : BSD3
+Maintainer  : aovieth@gmail.com
+Stability   : experimental
+Portability : non-portable (GHC only)
+-}
+
+{-# LANGUAGE AutoDeriveTypeable #-}
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE DataKinds #-}
+{-# LANGUAGE KindSignatures #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE StandaloneDeriving #-}
+
+module Types.Order (
+
+      SomeOrder(..)
+    , SomeOrderObject(..)
+
+    , parseSomeOrderTypical
+    , parseSomeOrderRetreat
+    , parseSomeOrderAdjust
+
+    , printSomeOrder
+    , printObject
+
+    ) where
+
+import Control.Applicative
+import qualified Data.Text as T
+import Data.Aeson
+import Data.Aeson.Parser
+import Data.JSON.Schema
+import Text.Parsec hiding ((<|>))
+import Text.Parsec.Text
+import Diplomacy.Phase
+import Diplomacy.OrderType
+import Diplomacy.Order hiding (Order(..), SomeOrder(..))
+import qualified Diplomacy.Order as DO
+import qualified Diplomacy.OrderObject as DOO
+import Diplomacy.Subject
+import Diplomacy.Province
+import Diplomacy.Unit
+
+newtype SomeOrderObject (phase :: Phase) = SomeOrderObject {
+      outSomeOrderObject :: DOO.SomeOrderObject phase
+    }
+
+deriving instance Show (SomeOrderObject phase)
+
+instance JSONSchema (SomeOrderObject phase) where
+    schema _ = Value (LengthBound Nothing Nothing)
+
+instance ToJSON (SomeOrderObject phase) where
+    toJSON (SomeOrderObject (DOO.SomeOrderObject object)) = String (printObject object)
+
+instance FromJSON (SomeOrderObject Typical) where
+    parseJSON (String txt) = case parse parseObjectTypical "" txt of
+        Left _ -> empty
+        Right x -> return (SomeOrderObject x)
+    parseJSON _ = empty
+
+instance FromJSON (SomeOrderObject Retreat) where
+    parseJSON (String txt) = case parse parseObjectRetreat "" txt of
+        Left _ -> empty
+        Right x -> return (SomeOrderObject x)
+    parseJSON _ = empty
+
+instance FromJSON (SomeOrderObject Adjust) where
+    parseJSON (String txt) = case parse parseObjectAdjust "" txt of
+        Left _ -> empty
+        Right x -> return (SomeOrderObject x)
+    parseJSON _ = empty
+
+newtype SomeOrder (phase :: Phase) = SomeOrder {
+      outSomeOrder :: DO.SomeOrder phase
+    }
+
+deriving instance Show (SomeOrder phase)
+deriving instance Eq (SomeOrder phase)
+deriving instance Ord (SomeOrder phase)
+
+instance JSONSchema (SomeOrder phase) where
+    schema _ = Value (LengthBound Nothing Nothing)
+
+instance ToJSON (SomeOrder phase) where
+    toJSON = String . printSomeOrder
+
+instance FromJSON (SomeOrder Typical) where
+    parseJSON (String txt) = case parse parseSomeOrderTypical "" txt of
+        Left _ -> empty
+        Right x -> return x
+    parseJSON _ = empty
+
+instance FromJSON (SomeOrder Retreat) where
+    parseJSON (String txt) = case parse parseSomeOrderRetreat "" txt of
+        Left _ -> empty
+        Right x -> return x
+    parseJSON _ = empty
+
+instance FromJSON (SomeOrder Adjust) where
+    parseJSON (String txt) = case parse parseSomeOrderAdjust "" txt of
+        Left _ -> empty
+        Right x -> return x
+    parseJSON _ = empty
+
+printSomeOrder :: SomeOrder phase -> T.Text
+printSomeOrder (SomeOrder (DO.SomeOrder order)) = T.concat [
+      printSubject (orderSubject order)
+    , " "
+    , printObject (orderObject order)
+    ]
+
+printSubject :: Subject -> T.Text
+printSubject (unit, pt) = T.concat [
+      printUnit unit
+    , " "
+    , printProvinceTarget pt
+    ]
+
+printObject :: DOO.OrderObject phase order -> T.Text
+printObject object = case object of
+    DOO.MoveObject pt -> T.concat ["- ", printProvinceTarget pt]
+    DOO.SupportObject subj pt ->
+        if subjectProvinceTarget subj == pt
+        then T.concat ["S ", printSubject subj]
+        else T.concat ["S ", printSubject subj, " - ", printProvinceTarget pt]
+    DOO.ConvoyObject subj pt ->
+        T.concat ["C ", printSubject subj, " - ", printProvinceTarget pt]
+    DOO.SurrenderObject -> "Surrender"
+    DOO.WithdrawObject pt ->
+        T.concat ["- ", printProvinceTarget pt]
+    DOO.DisbandObject -> "Disband"
+    DOO.BuildObject -> "Build"
+    DOO.ContinueObject -> "Continue"
+
+parseSomeOrderTypical :: Parser (SomeOrder Typical)
+parseSomeOrderTypical = do
+    subject <- parseSubject
+    spaces
+    DOO.SomeOrderObject object <- parseObjectTypical
+    return $ SomeOrder (DO.SomeOrder (DO.Order (subject, object)))
+
+parseSomeOrderRetreat :: Parser (SomeOrder Retreat)
+parseSomeOrderRetreat = do
+    subject <- parseSubject
+    spaces
+    DOO.SomeOrderObject object <- parseObjectRetreat
+    return $ SomeOrder (DO.SomeOrder (DO.Order (subject, object)))
+
+parseSomeOrderAdjust :: Parser (SomeOrder Adjust)
+parseSomeOrderAdjust = do
+    subject <- parseSubject
+    spaces
+    DOO.SomeOrderObject object <- parseObjectAdjust
+    return $ SomeOrder (DO.SomeOrder (DO.Order (subject, object)))
+
+parseSubject :: Parser Subject
+parseSubject = do
+    unit <- parseUnit
+    spaces
+    pt <- parseProvinceTarget
+    return (unit, pt)
+
+parseObjectTypical :: Parser (DOO.SomeOrderObject Typical)
+parseObjectTypical =
+        (DOO.SomeOrderObject <$> try parseMove)
+    <|> (DOO.SomeOrderObject <$> try parseSupport)
+    <|> (DOO.SomeOrderObject <$> try parseConvoy)
+
+parseObjectRetreat :: Parser (DOO.SomeOrderObject Retreat)
+parseObjectRetreat =
+        (DOO.SomeOrderObject <$> try parseSurrender)
+    <|> (DOO.SomeOrderObject <$> try parseWithdraw)
+
+parseObjectAdjust :: Parser (DOO.SomeOrderObject Adjust)
+parseObjectAdjust =
+        (DOO.SomeOrderObject <$> try parseDisband)
+    <|> (DOO.SomeOrderObject <$> try parseBuild)
+    <|> (DOO.SomeOrderObject <$> try parseContinue)
+
+parseMove :: Parser (DOO.OrderObject Typical Move)
+parseMove = do
+    char '-'
+    spaces
+    pt <- parseProvinceTarget
+    return $ DOO.MoveObject pt
+
+parseSupport :: Parser (DOO.OrderObject Typical Support)
+parseSupport = do
+    char 'S'
+    spaces
+    subject <- parseSubject
+    target <- Text.Parsec.option (subjectProvinceTarget subject) (try rest)
+    return $ DOO.SupportObject subject target
+  where
+    rest = do
+        spaces
+        char '-'
+        spaces
+        parseProvinceTarget
+
+parseConvoy :: Parser (DOO.OrderObject Typical Convoy)
+parseConvoy = do
+    char 'C'
+    spaces
+    subject <- parseSubject
+    spaces
+    char '-'
+    spaces
+    target <- parseProvinceTarget
+    return $ DOO.ConvoyObject subject target
+
+parseSurrender :: Parser (DOO.OrderObject Retreat Surrender)
+parseSurrender = do
+    string "Surrender"
+    return $ DOO.SurrenderObject
+
+parseWithdraw :: Parser (DOO.OrderObject Retreat Withdraw)
+parseWithdraw = do
+    char '-'
+    spaces
+    pt <- parseProvinceTarget
+    return $ DOO.WithdrawObject pt
+
+parseDisband :: Parser (DOO.OrderObject Adjust Disband)
+parseDisband = do
+    string "Disband"
+    return $ DOO.DisbandObject
+
+parseBuild :: Parser (DOO.OrderObject Adjust Build)
+parseBuild = do
+    string "Build"
+    return $ DOO.BuildObject
+
+parseContinue :: Parser (DOO.OrderObject Adjust Continue)
+parseContinue = do
+    string "Continue"
+    return $ DOO.ContinueObject
diff --git a/Types/Server.hs b/Types/Server.hs
new file mode 100644
--- /dev/null
+++ b/Types/Server.hs
@@ -0,0 +1,122 @@
+{-|
+Module      : 
+Description : 
+Copyright   : (c) Alexander Vieth, 2015
+Licence     : BSD3
+Maintainer  : aovieth@gmail.com
+Stability   : experimental
+Portability : non-portable (GHC only)
+-}
+
+{-# LANGUAGE AutoDeriveTypeable #-}
+{-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE RankNTypes #-}
+
+module Types.Server (
+
+      ServerState(..)
+    , Server
+
+    , withAdminCredentials
+    , withUserCredentialsForGame
+    , modifyGameState
+
+    , serverState
+    , runDiplomacyServer
+
+    ) where
+
+import Control.DeepSeq
+import Control.Monad.Trans.Except
+import Control.Monad.Trans.State as S
+import Control.Monad.State.Class as SC
+import Control.Concurrent.STM
+import Control.Concurrent.STM.TVar
+import Data.Stream as Stream
+import Data.ByteString as BS
+import Data.ByteString.Lazy as BL
+import qualified Data.Map as M
+import Data.Functor.Identity
+import Data.Hourglass
+import System.Hourglass
+import System.Random
+import Rest.Error
+import Types.GameId
+import Types.Credentials
+import Types.GameState
+
+data ServerState = ServerState {
+      adminCredentials :: Credentials
+    , games :: M.Map GameId (Password, GameState)
+    , currentTime :: Elapsed
+    , randomDoubles :: Stream.Stream Double
+    , clientHtml :: BL.ByteString
+    }
+
+type Server = StateT ServerState Identity
+
+withAdminCredentials
+    :: MonadState ServerState m
+    => Credentials
+    -> ExceptT (Reason e) m t
+    -> ExceptT (Reason e) m t
+withAdminCredentials creds term = do
+    state <- SC.get
+    if adminCredentials state == creds
+    then term
+    else throwE AuthenticationFailed
+
+withUserCredentialsForGame
+    :: MonadState ServerState m
+    => Credentials
+    -> GameId
+    -> (GameStateView -> ExceptT (Reason e) m t)
+    -> ExceptT (Reason e) m t
+withUserCredentialsForGame creds gameId term = do
+    state <- SC.get
+    let game = M.lookup gameId (games state)
+    case game of
+        Nothing -> throwE NotFound
+        Just (_, gameState) -> case gameStateMatchCredentials creds gameState of
+            Nothing -> throwE AuthenticationFailed
+            Just gameStateView -> term gameStateView
+
+modifyGameState
+    :: MonadState ServerState m
+    => GameId
+    -> (GameState -> ExceptT (Reason e) m GameState)
+    -> ExceptT (Reason e) m ()
+modifyGameState gameId f = do
+    state <- SC.get
+    let game = M.lookup gameId (games state)
+    case game of
+        Nothing -> throwE NotFound
+        Just (password, gameState) -> do
+            modified <- f gameState
+            SC.put (state { games = M.alter (const (Just (password, force modified))) gameId (games state) })
+            return ()
+
+serverState :: Username -> Password -> IO ServerState
+serverState adminUsername adminPassword = do
+    let games = M.empty
+    g <- getStdGen
+    let ds = unfold random g :: Stream.Stream Double
+    t <- timeCurrent
+    bs <- BL.readFile "client.html"
+    return $ ServerState (Credentials adminUsername adminPassword) games t ds bs
+
+runDiplomacyServer :: TVar ServerState -> (forall a . Server a -> IO a)
+runDiplomacyServer tvar m = do
+    -- Always refurnish the state with random doubles.
+    g <- getStdGen
+    let ds = unfold random g :: Stream.Stream Double
+    t <- timeCurrent
+    bs <- BL.readFile "client.html"
+    atomically $ do
+        state <- readTVar tvar
+        -- It's important to update the time and random doubles before we
+        -- run the term @m@.
+        let state' = state { currentTime = t, randomDoubles = ds, clientHtml = bs }
+        let (x, nextState) = runIdentity (runStateT m state')
+        writeTVar tvar nextState
+        return x
diff --git a/Types/ServerOptions.hs b/Types/ServerOptions.hs
new file mode 100644
--- /dev/null
+++ b/Types/ServerOptions.hs
@@ -0,0 +1,49 @@
+{-|
+Module      : 
+Description : 
+Copyright   : (c) Alexander Vieth, 2015
+Licence     : BSD3
+Maintainer  : aovieth@gmail.com
+Stability   : experimental
+Portability : non-portable (GHC only)
+-}
+
+{-# LANGUAGE AutoDeriveTypeable #-}
+
+module Types.ServerOptions (
+
+      ServerOptions(..)
+    , parser
+
+    ) where
+
+import System.FilePath
+import Types.Credentials
+import Options.Applicative
+import Options.Applicative.Builder
+
+data ServerOptions = ServerOptions {
+      adminUsername :: Username
+    , adminPassword :: Password
+    , certificateFile :: FilePath
+    , keyFile :: FilePath
+    , port :: Int
+    }
+    deriving (Show)
+
+parser :: Parser ServerOptions
+parser =
+        ServerOptions
+    <$> adminUsernameParser
+    <*> adminPasswordParser
+    <*> certificateFileParser
+    <*> keyFileParser
+    <*> port
+  where
+    adminUsernameParser = strOption (long "username" <> short 'u' <> help "Username for administration")
+    adminPasswordParser = strOption (long "password" <> short 'p' <> help "Password for administration")
+    certificateFileParser = strOption (long "certificate" <> short 'c' <> value "certificate.pem" <> help "Certificate for TLS")
+    keyFileParser = strOption (long "key" <> short 'k' <> value "key.pem" <> help "Private key for TLS")
+    port = option auto (long "port" <> value 4347 <> help "Port on which to run the server")
+
+
diff --git a/Types/Unit.hs b/Types/Unit.hs
new file mode 100644
--- /dev/null
+++ b/Types/Unit.hs
@@ -0,0 +1,45 @@
+{-|
+Module      : Types.Unit
+Description : Wrapper for Diplomacy.Unit to make it JSON-able
+Copyright   : (c) Alexander Vieth, 2015
+Licence     : BSD3
+Maintainer  : aovieth@gmail.com
+Stability   : experimental
+Portability : non-portable (GHC only)
+-}
+
+{-# LANGUAGE AutoDeriveTypeable #-}
+{-# LANGUAGE DeriveGeneric #-}
+
+module Types.Unit (
+
+      Unit(..)
+
+    ) where
+
+import GHC.Generics
+import Control.Applicative
+import Data.String (fromString)
+import Data.Typeable
+import qualified Data.Text as T
+import Data.Aeson
+import Data.JSON.Schema
+import qualified Diplomacy.Unit as D
+import Text.Parsec
+
+newtype Unit = Unit {
+      outUnit :: D.Unit
+    }
+    deriving (Eq, Ord, Show, Generic, Typeable)
+
+instance ToJSON Unit where
+    toJSON (Unit unit) = String . fromString . D.printUnit $ unit
+
+instance FromJSON Unit where
+    parseJSON (String txt) = case parse D.parseUnit "" txt of
+        Left _ -> empty
+        Right x -> return (Unit x)
+    parseJSON _ = empty
+
+instance JSONSchema Unit where
+    schema _ = Value (LengthBound (Just 1) (Just 1))
diff --git a/Types/UserData.hs b/Types/UserData.hs
new file mode 100644
--- /dev/null
+++ b/Types/UserData.hs
@@ -0,0 +1,25 @@
+{-|
+Module      : Types.UserData
+Description : 
+Copyright   : (c) Alexander Vieth, 2015
+Licence     : BSD3
+Maintainer  : aovieth@gmail.com
+Stability   : experimental
+Portability : non-portable (GHC only)
+-}
+
+{-# LANGUAGE AutoDeriveTypeable #-}
+
+module Types.UserData (
+
+      UserData(..)
+
+    ) where
+
+import Types.Credentials (Password)
+import Diplomacy.GreatPower
+
+data UserData f = UserData {
+      password :: Password
+    , greatPower :: f GreatPower
+    }
diff --git a/client.html b/client.html
new file mode 100644
--- /dev/null
+++ b/client.html
@@ -0,0 +1,2031 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+  </head>
+
+  <body>
+
+    <script>
+
+      var animationFrameCallbacks = [];
+
+      var animate = function () {
+        for (var i = 0; i < animationFrameCallbacks.length; ++i) {
+          animationFrameCallbacks[i]();
+        }
+        window.requestAnimationFrame(animate);
+      };
+
+      var clearContainer = function (in_container) {
+        while (in_container.firstChild) {
+          in_container.removeChild(in_container.firstChild);
+        }
+        animationFrameCallbacks = [];
+      };
+
+      var uiGameId = function (in_container, in_callback) {
+        uiInput(in_container, 'Game name', 'text', in_callback);
+      };
+
+      var uiGamePassword = function (in_container, in_callback) {
+        uiInput(in_container, 'Game password', 'password', in_callback);
+      };
+
+      var uiGameDuration = function (in_container, in_callback) {
+        uiInput(in_container, 'Length of typical phase (in minutes)', 'text', in_callback);
+      };
+
+      var uiGameSecondDuration = function (in_container, in_callback) {
+        uiInput(in_container, 'Length of other phases (in minutes)', 'text', in_callback);
+      };
+
+      var uiPassword = function (in_container, in_callback) {
+        uiInput(in_container, 'Password', 'password', in_callback);
+      };
+
+      var uiUsername = function (in_container, in_callback) {
+        uiInput(in_container, 'Username', 'text', in_callback);
+      };
+
+      var uiInitial = function (in_container) {
+        var element = buttons([
+          {
+            text : 'Play',
+            click : function () {
+              uiPlay(in_container);
+            }
+          },
+          { 
+            text : 'Administrate',
+            click : function () {
+              uiAdmin(in_container);
+            }
+          }
+        ]);
+        clearContainer(in_container);
+        in_container.appendChild(element);
+      };
+
+      var uiAdmin = function (in_container) {
+        var element = buttons([
+          {
+            text : 'Create',
+            click : function () {
+              uiGameId(in_container, function (in_gameId) {
+                uiGamePassword(in_container, function (in_gamePassword) {
+                  uiGameDuration(in_container, function (in_gameDuration) {
+                    uiGameSecondDuration(in_container, function (in_gameSecondDuration) {
+                      uiUsername(in_container, function (in_username) {
+                        uiPassword(in_container, function (in_password) {
+                          createGame(in_container, in_gameId, in_gamePassword, Number(in_gameDuration), Number(in_gameSecondDuration), in_username, in_password, function (in_error) {
+                            if (in_error) {
+                              uiInitial(in_container);
+                            } else {
+                              uiInitial(in_container);
+                            }
+                          });
+                        });
+                      });
+                    });
+                  });
+                });
+              });
+            }
+          },
+          {
+            text : 'Start',
+            click : function () {
+              uiGameId(in_container, function (in_gameId) {
+                uiUsername(in_container, function (in_username) {
+                  uiPassword(in_container, function (in_password) {
+                    startGame(in_container, in_gameId, in_username, in_password, function (in_error) {
+                      if (in_error) {
+                        uiInitial(in_container);
+                      } else {
+                        uiInitial(in_container);
+                      }
+                    });
+                  });
+                });
+              });
+            }
+          },
+          {
+            text : 'Advance',
+            click : function () {
+              uiGameId(in_container, function (in_gameId) {
+                uiUsername(in_container, function (in_username) {
+                  uiPassword(in_container, function (in_password) {
+                    advanceGame(in_container, in_gameId, in_username, in_password, function (in_error) {
+                      if (in_error) {
+                        uiInitial(in_container);
+                      } else {
+                        uiInitial(in_container);
+                      }
+                    });
+                  });
+                });
+              });
+            }
+          },
+          {
+            text : 'Pause',
+            click : function () {
+              uiGameId(in_container, function (in_gameId) {
+                uiUsername(in_container, function (in_username) {
+                  uiPassword(in_container, function (in_password) {
+                    pauseGame(in_container, in_gameId, in_username, in_password, function (in_error) {
+                      if (in_error) {
+                        uiInitial(in_container);
+                      } else {
+                        uiInitial(in_container);
+                      }
+                    });
+                  });
+                });
+              });
+            }
+          },
+
+          {
+            text : 'Destroy',
+            click : function () {
+              uiGameId(in_container, function (in_gameId) {
+                uiUsername(in_container, function (in_username) {
+                  uiPassword(in_container, function (in_password) {
+                    destroyGame(in_container, in_gameId, in_username, in_password, function (in_error) {
+                      if (in_error) {
+                        uiInitial(in_container);
+                      } else {
+                        uiInitial(in_container);
+                      }
+                    });
+                  });
+                });
+              });
+            }
+          }
+        ]);
+        clearContainer(in_container);
+        in_container.appendChild(element);
+      };
+
+      var uiPlay = function (in_container) {
+        var element = buttons([
+          {
+            text : 'Continue existing game',
+            click : function () {
+              uiGameId(in_container, function (in_gameId) {
+                uiUsername(in_container, function (in_username) {
+                  uiPassword(in_container, function (in_password) {
+                    fetchMetadata(in_gameId, in_username, in_password, function (in_error, in_metadata) {
+                      if (in_error) {
+                        uiPlay(in_container);
+                      } else {
+                        uiPlayGame(in_container, in_gameId, in_username, in_password, in_metadata);
+                      }
+                    });
+                  });
+                });
+              });
+            }
+          },
+          {
+            text : 'Join game',
+            click : function () {
+              uiGameId(in_container, function (in_gameId) {
+                uiGamePassword(in_container, function (in_gamePassword) {
+                  uiUsername(in_container, function (in_username) {
+                    uiPassword(in_container, function (in_password) {
+                      joinGame(in_container, in_gameId, in_gamePassword, in_username, in_password, function (in_error) {
+                        if (in_error) {
+                          uiError(in_container, 'Could not join game.', function () {
+                            uiInitial(in_container);
+                          });
+                        } else {
+                          uiInitial(in_container);
+                        }
+                      });
+                    });
+                  });
+                });
+              });
+            }
+          }
+        ]);
+        clearContainer(in_container);
+        in_container.appendChild(element);
+      };
+
+      var uiPlayGame = function (in_container, in_gameId, in_username, in_password, in_metadata, in_maybeTurn, in_maybeRound) {
+        clearContainer(in_container);
+        if (in_metadata === null) {
+          // Game has not yet begun.
+          var divLabel = document.createElement('div');
+          divLabel.innerHTML = 'Game not started';
+          var liLabel = document.createElement('li');
+          liLabel.appendChild(divLabel);
+          liLabel.style.display = 'table-row';
+          liLabel.style.textAlign = 'center';
+          ul.appendChild(liLabel);
+        } else {
+          // Show the game at the latest round.
+          var turn = in_maybeTurn;
+          if (typeof turn !== 'number') {
+            turn = in_metadata.turn;
+          }
+          var round = in_maybeRound;
+          if (typeof round !== 'number') {
+            round = in_metadata.round;
+          }
+          var f = function (in_callback) {
+            fetchResolution(in_gameId, in_username, in_password, turn, round, in_callback);
+          };
+          if (turn === in_metadata.turn && round === in_metadata.round) {
+            var f = function (in_callback) {
+              fetchGame(in_gameId, in_username, in_password, in_callback);
+            };
+          }
+          f(function (in_error, in_game) {
+            if (in_error) {
+              uiError(in_container, 'Could not fetch game.', function () {
+                uiPlay(in_container);
+              });
+            } else {
+              uiGameView(in_container, in_gameId, in_username, in_password, in_metadata, turn, round, in_game);
+            }
+          });
+        }
+      };
+
+      var uiMetadata = function (in_container, in_gameId, in_username, in_password, in_metadata, in_turn, in_round) {
+
+        var turn = in_turn + 1900;
+        var phase = 'Typical';
+        var season = 'Spring';
+        if (in_round === 1 || in_round === 3) {
+          phase = 'Retreat';
+        } else if (in_round === 4) {
+          phase = 'Adjust';
+        }
+        if (in_round > 1 && in_round < 4) {
+          season = 'Fall';
+        } else if (in_round === 4) {
+          season = 'Winter';
+        }
+        var latestPhase = 'Typical';
+        if (in_metadata.round === 1 || in_metadata.round === 3) {
+          latestPhase = 'Retreat';
+        } else if (in_metadata.round === 4) {
+          latestPhase = 'Adjust';
+        }
+
+        var tdPreviousButton = document.createElement('td');
+        var previousButton = document.createElement('input');
+        previousButton.type = 'button';
+        previousButton.value = 'Previous';
+        tdPreviousButton.appendChild(previousButton);
+        if (in_turn === 0 && in_round === 0) {
+          previousButton.disabled = true;
+        }
+        tdPreviousButton.addEventListener('click', function (in_event) {
+          // Here we must reload the metadata and the game but stick to a
+          // particular round!
+          if (in_turn === 0 && in_round === 0) {
+          } else {
+            var previousTurn = in_turn;
+            // JavaScript modular arithmetic at negative numbers isn't
+            // defined such that we can use (in_round - 1) % 5.
+            var previousRound = in_round - 1;
+            if (previousRound === -1) {
+              previousRound = 4;
+              previousTurn = previousTurn - 1;
+            }
+            uiPlayGame(in_container, in_gameId, in_username, in_password, in_metadata, previousTurn, previousRound);
+          }
+        });
+
+
+        var tdPhase = document.createElement('td');
+        tdPhase.innerHTML = season + ' ' + String(turn) + ' ' + phase;
+        tdPhase.style.width = '100%';
+
+        var tdCounter = document.createElement('td');
+        var updateCounter = function () {
+          var paused = !!in_metadata.paused;
+          if (paused) {
+            tdCounter.innerHTML = 'Paused';
+          } else {
+            var secondsElapsed = ((new Date().getTime() / 1000) - in_metadata.elapsed)
+            var duration = {
+              hours : (latestPhase === 'Typical') ? in_metadata.duration.hours : in_metadata.secondDuration.hours,
+              minutes : (latestPhase === 'Typical') ? in_metadata.duration.minutes : in_metadata.secondDuration.minutes
+            };
+            var durationSeconds = duration.hours * 60 * 60 + duration.minutes * 60;
+            var secondsRemaining = Math.max(0, durationSeconds - secondsElapsed);
+            var minutesRemaining = secondsRemaining / 60;
+            var hoursRemaining = minutesRemaining / 60;
+            var hoursRemainingText = String(Math.floor(hoursRemaining));
+            var minutesRemainingText = String(Math.floor(minutesRemaining) % 60);
+            var secondsRemainingText = String(Math.floor(secondsRemaining) % 60);
+            if (minutesRemainingText.length === 1) {
+              minutesRemainingText = '0' + minutesRemainingText;
+            }
+            if (secondsRemainingText.length === 1) {
+              secondsRemainingText = '0' + secondsRemainingText;
+            }
+            tdCounter.innerHTML = [
+              hoursRemainingText,
+              minutesRemainingText,
+              secondsRemainingText
+            ].join(':');
+          }
+        };
+        updateCounter();
+        animationFrameCallbacks.push(updateCounter);
+
+        var tdGreatPowers = document.createElement('td');
+        tdGreatPowers.innerHTML = in_metadata.greatPowers.join(', ');
+
+        var tdNextButton = document.createElement('td');
+        var nextButton = document.createElement('input');
+        nextButton.type = 'button';
+        if (in_turn === in_metadata.turn && in_round === in_metadata.round) {
+          nextButton.value = 'Refresh';
+        } else {
+          nextButton.value = 'Next';
+          var latestButton = document.createElement('input');
+          latestButton.type = 'button';
+          latestButton.value = 'Latest';
+          tdNextButton.appendChild(latestButton);
+          latestButton.addEventListener('click', function (in_event) {
+            fetchMetadata(in_gameId, in_username, in_password, function (in_error, in_metadata) {
+              if (in_error) {
+                uiError(in_container, 'Could not fetch metadata.', function () {
+                  uiInitial(in_container);
+                });
+              } else {
+                uiPlayGame(in_container, in_gameId, in_username, in_password, in_metadata);
+              }
+            });
+          });
+        }
+        tdNextButton.appendChild(nextButton);
+        tdNextButton.addEventListener('click', function (in_event) {
+          if (in_turn === in_metadata.turn && in_round === in_metadata.round) {
+            // Here we must reload the metadata and the game but stick to a
+            // particular round!
+            fetchMetadata(in_gameId, in_username, in_password, function (in_error, in_metadata) {
+              if (in_error) {
+                uiError(in_container, 'Could not fetch metadata.', function () {
+                  uiInitial(in_container);
+                });
+              } else {
+                uiPlayGame(in_container, in_gameId, in_username, in_password, in_metadata);
+              }
+            });
+          } else {
+            var nextTurn = in_turn;
+            var nextRound = (in_round + 1) % 5;
+            if (nextRound === 0) {
+              nextTurn = nextTurn + 1;
+            }
+            uiPlayGame(in_container, in_gameId, in_username, in_password, in_metadata, nextTurn, nextRound);
+          }
+        });
+
+        var table = document.createElement('table');
+        var tr = document.createElement('tr');
+        tr.appendChild(tdPreviousButton);
+        tr.appendChild(tdPhase);
+        tr.appendChild(tdCounter);
+        tr.appendChild(tdGreatPowers);
+        tr.appendChild(tdNextButton);
+        tr.style.whiteSpace = 'nowrap';
+        table.appendChild(tr);
+        return table;
+      };
+
+      var uiGameData = function (in_container, in_gameId, in_username, in_password, in_metadata, in_round, in_game) {
+        // in_game could be the current game in any phase, or a resolution
+        // from any phase.
+        // No matter the case, we have enough data to show the map.
+
+        var table = document.createElement('table');
+        var tr = document.createElement('tr');
+        var tdMap = document.createElement('td');
+        var tdOrders = document.createElement('td');
+        tdMap.style.width = '50%';
+        tdOrders.style.width = '50%';
+        tdOrders.style.verticalAlign = 'text-top';
+        tr.appendChild(tdMap);
+        tr.appendChild(tdOrders);
+        table.appendChild(tr);
+
+        var occupation;
+        var dislodgement;
+        var control = in_game.control;
+
+        if (in_game.resolved) {
+          if (in_game.occupation) {
+            // It's a retreat phase resolution.
+            occupation = in_game.occupation;
+          } else {
+            occupation = in_game.resolved;
+          }
+        } else {
+          occupation = in_game.occupation;
+          dislodgement = in_game.dislodgement || {};
+        }
+
+        var svgMap = window.svgMap
+        for (var i in svgMap.fleets) {
+          svgMap.removeChild(svgMap.fleets[i]);
+        }
+        for (var i in svgMap.armies) {
+          svgMap.removeChild(svgMap.armies[i]);
+        }
+        svgMap.fleets = [];
+        svgMap.armies = [];
+        for (var i in occupation) {
+          // Here we construct SVG elements for each unit on the board, and
+          // throw them into the map.
+          if (occupation[i][2] === 'Build') {
+          } else if (occupation[i][1] === 'F') {
+            svgMap.fleets.push(svgFleet(occupation[i][0], unitOccupationLocation(i)));
+          } else {
+            svgMap.armies.push(svgArmy(occupation[i][0], unitOccupationLocation(i)));
+          }
+        }
+        for (var i in dislodgement) {
+          // Just like occupation, except the units are offset slightly and
+          // drawn atop the occupier.
+          if (dislodgement[i][1] === 'F') {
+            svgMap.fleets.push(svgFleet(dislodgement[i][0], unitDislodgementLocation(i)));
+          } else {
+            svgMap.armies.push(svgArmy(dislodgement[i][0], unitDislodgementLocation(i)));
+          }
+        }
+        for (var i in svgMap.fleets) {
+          svgMap.appendChild(svgMap.fleets[i]);
+        }
+        for (var i in svgMap.armies) {
+          svgMap.appendChild(svgMap.armies[i]);
+        }
+        for (var i in control) {
+          // For control, we can just set all supply centre classes to their
+          // controlling nation, since control never reverts to Unowned.
+          // It's OK if there's no element; control is for all provinces, but
+          // we only SHOW control for supply centres.
+          var element = svgMap.getElementById('SC' + i);
+          if (element && element.children && element.children[0]) {
+            element.children[0].setAttribute('class', control[i]);
+          }
+        }
+        svgMap.style.display = 'inline-block';
+        tdMap.appendChild(svgMap);
+
+        if (!in_game.resolved) {
+          // Inputs for orders.
+          var greatPowers = in_metadata.greatPowers;
+          var phase = 'Typical';
+          if (in_round === 1 || in_round === 3) {
+            phase = 'Retreat';
+          } else if (in_round === 4) {
+            phase = 'Adjust';
+          }
+          var orderForm = document.createElement('form');
+          orderForm.id = 'orderForm';
+          var orderTable = document.createElement('table');
+          orderForm.appendChild(orderTable);
+          // Identifying when we can give an order... for lack of any proper
+          // design in this client, we just check that the third component of
+          // an entry is not null, in which case its the current order, and we
+          // assume that this unit is under the user's control.
+          var orderEntries = [];
+          for (var k in greatPowers) {
+            var tdGreatPowerLabel = document.createElement('td');
+            tdGreatPowerLabel.style.fontStyle = 'italic';
+            tdGreatPowerLabel.innerHTML = greatPowers[k];
+            tdGreatPowerLabel.setAttribute('colspan', 2);
+            trGreatPowerLabel = document.createElement('tr');
+            trGreatPowerLabel.appendChild(tdGreatPowerLabel);
+            orderTable.appendChild(trGreatPowerLabel);
+            if (phase === 'Typical') {
+              var atLeastOneOrder = false;
+              for (var i in occupation) {
+                if (greatPowers[k] === occupation[i][0] && occupation[i][2]) {
+                  var atLeastOneOrder = true;
+                  // Above test passes only if this client has control over the
+                  // great power who owns this entry.
+                  var orderEntry = makeOrderEntry({
+                    provinceTarget : i,
+                    greatPower : occupation[i][0],
+                    unit : occupation[i][1],
+                    orderObject : occupation[i][2]
+                  });
+                  orderEntries.push(orderEntry);
+                  var tr = document.createElement('tr');
+                  var tdl = document.createElement('td');
+                  tdl.appendChild(orderEntry.left);
+                  var tdr = document.createElement('td');
+                  tdr.appendChild(orderEntry.right);
+                  tr.appendChild(tdl);
+                  tr.appendChild(tdr);
+                  orderTable.appendChild(tr);
+                }
+              }
+              if (!atLeastOneOrder) {
+                orderTable.removeChild(trGreatPowerLabel);
+              }
+            } else if (phase === 'Retreat') {
+              var atLeastOneOrder = false;
+              for (var i in dislodgement) {
+                if (greatPowers[k] === dislodgement[i][0] && dislodgement[i][2]) {
+                  var atLeastOneOrder = true;
+                  var orderEntry = makeOrderEntry({
+                    provinceTarget : i,
+                    greatPower : dislodgement[i][0],
+                    unit : dislodgement[i][1],
+                    orderObject : dislodgement[i][2]
+                  });
+                  orderEntries.push(orderEntry);
+                  var tr = document.createElement('tr');
+                  var tdl = document.createElement('td');
+                  tdl.appendChild(orderEntry.left);
+                  var tdr = document.createElement('td');
+                  tdr.appendChild(orderEntry.right);
+                  tr.appendChild(tdl);
+                  tr.appendChild(tdr);
+                  orderTable.appendChild(tr);
+                }
+              }
+              if (!atLeastOneOrder) {
+                orderTable.removeChild(trGreatPowerLabel);
+              }
+            } else if (phase === 'Adjust') {
+              var controlledSupplyCentres = [];
+              for (var i in control) {
+                if (supplyCentres[i] === true && greatPowers[k] === control[i]) {
+                  controlledSupplyCentres.push(i);
+                }
+              }
+              var controlledUnits = [];
+              for (var i in occupation) {
+                if (greatPowers[k] === occupation[i][0]
+                  && occupation[i][2] !== 'Build') {
+                  controlledUnits.push([i, occupation[i][1]]);
+                }
+              }
+              var deficit = controlledUnits.length - controlledSupplyCentres.length;
+              var atLeastOneOrder = false;
+              if (deficit === 0) {
+                // Everybody must continue; no need to show any controls.
+              } else if (deficit > 0) {
+                // Show disband options for all units; server should not accept
+                // more disbands than the difference.
+                // This is done via checkboxes for each unit.
+                var span = document.createElement('span');
+                var unitString = (deficit === 1) ? 'unit.' : 'units.';
+                tdGreatPowerLabel.innerHTML = greatPowers[k] + ' must disband ' + String(deficit) + ' ' + unitString;
+                for (var i in occupation) {
+                  if (greatPowers[k] === occupation[i][0]) {
+                    atLeastOneOrder = true;
+                    var orderEntry = makeOrderEntry({
+                      phase : 'Adjust',
+                      provinceTarget : i,
+                      greatPower : occupation[i][0],
+                      unit : occupation[i][1],
+                      deficit : deficit,
+                      orderObject : occupation[i][2]
+                    });
+                    orderEntries.push(orderEntry);
+                    var tr = document.createElement('tr');
+                    var tdl = document.createElement('td');
+                    tdl.appendChild(orderEntry.left);
+                    var tdr = document.createElement('td');
+                    tdr.appendChild(orderEntry.right);
+                    tr.appendChild(tdl);
+                    tr.appendChild(tdr);
+                    orderTable.appendChild(tr);
+                  }
+                }
+              } else {
+                // Show build options for all home supply centres.
+                // This is done via dropdowns for each unoccupied
+                // home supply centre.
+                // We take special care for St. Petersburg, in which we must
+                // show options for itself and for both coasts.
+                var unitString = (deficit === -1) ? 'unit.' : 'units.';
+                tdGreatPowerLabel.innerHTML += ' may build at most ' + String(-deficit) + ' ' + unitString;
+                for (var i in supplyCentres) {
+                  if ( ownership[i] === greatPowers[k]
+                    && control[i] === greatPowers[k]
+                    && (!occupation[i] || occupation[i][2] === 'Build')) {
+                    var orderEntry;
+                    if (i === 'St. Petersburg') {
+                      if ( (occupation['St. Petersburg NC'] && occupation['St. Petersburg NC'][2] !== 'Build')
+                        || (occupation['St. Petersburg SC'] && occupation['St. Petersburg SC'][2] !== 'Build')) {
+                        continue;
+                      } else {
+                        var atLeastOneOrder = true;
+                        // Special controls for the multi-coast home supply
+                        // centre: a 4-option dropdown
+                        //   -
+                        //   A
+                        //   F (NC)
+                        //   F (SC)
+                        var buildSubjectValue = '-';
+                        if (occupation['St. Petersburg NC'] && occupation['St. Petersburg NC'][2] === 'Build') {
+                          buildSubjectValue = 'F (NC)';
+                        } else if (occupation['St. Petersburg SC'] && occupation['St. Petersburg SC'][2] === 'Build') {
+                          buildSubjectValue = 'F (SC)';
+                        } else if (occupation['St. Petersburg'] && occupation['St. Petersburg'][2] === 'Build') {
+                          buildSubjectValue = 'A';
+                        }
+                        orderEntry = makeOrderEntry({
+                          phase : 'Adjust',
+                          provinceTarget : i,
+                          greatPower : greatPowers[k],
+                          buildSubjectValue : buildSubjectValue
+                        });
+                      }
+                    } else {
+                      atLeastOneOrder = true;
+                      var buildSubjectValue = '-';
+                      if (occupation[i] && occupation[i][2] === 'Build') {
+                        buildSubjectValue = occupation[i][1];
+                      }
+                      orderEntry = makeOrderEntry({
+                        phase : 'Adjust',
+                        provinceTarget : i,
+                        greatPower : greatPowers[k],
+                        buildSubjectValue : buildSubjectValue
+                      });
+                    }
+                    orderEntries.push(orderEntry);
+                    var tr = document.createElement('tr');
+                    var tdl = document.createElement('td');
+                    tdl.appendChild(orderEntry.left);
+                    var tdr = document.createElement('td');
+                    tdr.appendChild(orderEntry.right);
+                    tr.appendChild(tdl);
+                    tr.appendChild(tdr);
+                    orderTable.appendChild(tr);
+                  }
+                }
+              }
+              if (!atLeastOneOrder) {
+                orderTable.removeChild(trGreatPowerLabel);
+              }
+            }
+          }
+          orderForm.addEventListener('submit', function (in_event) {
+            var orders = [];
+            var order;
+            for (var i in orderEntries) {
+              order = orderEntries[i].getOrder();
+              if (order === null) {
+              } else {
+                orders.push([order.greatPower, order.unit + ' ' + order.provinceTarget + ' ' + order.orderObject]);
+              }
+            }
+            submitOrders(in_container, in_gameId, in_username, in_password, phase, orders, function () {
+              fetchMetadata(in_gameId, in_username, in_password, function (in_error, in_metadata) {
+                if (in_error) {
+                  uiPlay(in_container);
+                } else {
+                  uiPlayGame(in_container, in_gameId, in_username, in_password, in_metadata);
+                }
+              });
+            });
+            in_event.preventDefault();
+            in_event.stopPropagation();
+            return false;
+          });
+          var submitButton = document.createElement('input');
+          submitButton.type = 'submit';
+          submitButton.value = 'Issue orders'
+          orderForm.appendChild(submitButton);
+          if (orderEntries.length > 0) {
+            tdOrders.appendChild(orderForm);
+          } else {
+            var span = document.createElement('span');
+            span.innerHTML = 'There\'s nothing to do.';
+            tdOrders.appendChild(span);
+          }
+        } else {
+          // A cheap visualization of the resolved orders.
+          var ordersByGreatPower = {};
+          for (var i in in_game.resolved) {
+            ordersByGreatPower[in_game.resolved[i][0]] = ordersByGreatPower[in_game.resolved[i][0]] || [];
+            var orderText = in_game.resolved[i][1] + ' ' + i + ' ' + in_game.resolved[i][2];
+            ordersByGreatPower[in_game.resolved[i][0]].push({
+              order : orderText,
+              succeeded : in_game.resolved[i][3]
+            });
+          }
+          var orderList = document.createElement('div');
+          var index = 0;
+          for (var i in ordersByGreatPower) {
+            var div = document.createElement('div');
+            if (index++ % 2 === 0) {
+              div.style.backgroundColor = 'rgba(0,0,0,0)';
+            } else {
+              div.style.backgroundColor = 'rgba(0,0,0,0.0.05)';
+            }
+            var span = document.createElement('span');
+            span.innerHTML = i;
+            span.style.fontStyle = 'italic';
+            div.appendChild(span);
+            var orderTable = document.createElement('table');
+            for (var j in ordersByGreatPower[i]) {
+              var tr = document.createElement('tr');
+              var tdl = document.createElement('td');
+              var tdr = document.createElement('td');
+              tdr.innerHTML = ordersByGreatPower[i][j].order;
+              if (ordersByGreatPower[i][j].succeeded) {
+                tdl.innerHTML = '✓';
+                tr.style.color = 'green';
+              } else {
+                tdl.innerHTML = '✗';
+                tr.style.color = 'red';
+              }
+              tr.appendChild(tdl);
+              tr.appendChild(tdr);
+              orderTable.appendChild(tr);
+            }
+            div.appendChild(orderTable);
+            orderList.appendChild(div);
+          }
+          var somethingHappened = index > 0;
+          if (somethingHappened) {
+            tdOrders.appendChild(orderList);
+          } else {
+            var span = document.createElement('span');
+            span.innerHTML = 'Nothing happened.';
+            tdOrders.appendChild(span);
+          }
+        }
+
+        return table;
+      };
+
+      var uiGameView = function (in_container, in_gameId, in_username, in_password, in_metadata, in_turn, in_round, in_game) {
+        clearContainer(in_container);
+        var metadataContainer = uiMetadata(in_container, in_gameId, in_username, in_password, in_metadata, in_turn, in_round);
+        var gameDataContainer = uiGameData(in_container, in_gameId, in_username, in_password, in_metadata, in_round, in_game);
+        metadataContainer.style.width = '100%';
+        gameDataContainer.style.height = '100%';
+        gameDataContainer.style.width = '100%';
+        in_container.appendChild(metadataContainer);
+        in_container.appendChild(gameDataContainer);
+      };
+
+      var joinGame = function (in_container, in_gameId, in_gamePassword, in_username, in_password, in_callback) {
+        var xhr = new XMLHttpRequest();
+        xhr.open('PUT', window.location + '/game/' + in_gameId + '/join');
+        xhr.addEventListener('load', function (in_event) {
+          if (xhr.status === 200) {
+            in_callback(false);
+          } else if (xhr.status === 404) {
+            in_callback('Game not found');
+          } else if (xhr.status === 403) {
+            in_callback('Unauthorized');
+          } else if (xhr.status === 401) {
+            in_callback('Unauthorized');
+          } else {
+            in_callback('Unexpected error is unexpected');
+          }
+        });
+        xhr.addEventListener('error', function (in_event) {
+          in_callback('Unexpected error is unexpected');
+        });
+        xhr.addEventListener('abort', function (in_event) {
+          in_callback('Unexpected error is unexpected');
+        });
+        xhr.setRequestHeader('Content-Type', 'application/json');
+        var credentials = {
+          username : in_username,
+          password : in_password
+        };
+        var data = {
+          gameId : in_gameId,
+          password : in_gamePassword,
+          credentials : credentials
+        }
+        xhr.send(JSON.stringify(data));
+      };
+
+      var fetch = function (in_method, in_route, in_data, in_callback) {
+        var xhr = new XMLHttpRequest();
+        xhr.open(in_method, in_route);
+        xhr.addEventListener('load', function (in_event) {
+          if (xhr.status === 200) {
+            var response = xhr.response;
+            if (xhr.response === undefined) {
+              // Some browser don't define response...
+              response = xhr.responseText;
+            }
+            in_callback(false, JSON.parse(response));
+          } else {
+            in_callback(true);
+          }
+        });
+        xhr.addEventListener('error', function (in_event) {
+          in_callback(ture);
+        });
+        xhr.addEventListener('abort', function (in_event) {
+          in_callback(ture);
+        });
+        xhr.setRequestHeader('Content-Type', 'application/json');
+        xhr.send(JSON.stringify(in_data));
+      };
+
+      var fetchMetadata = function (in_gameId, in_username, in_password, in_callback) {
+        var route = window.location + '/game/' + in_gameId + '/metadata';
+        var data = {
+          username : in_username,
+          password : in_password
+        };
+        fetch('PUT', route, data, in_callback);
+      };
+
+      var fetchResolution = function (in_gameId, in_username, in_password, in_turn, in_round, in_callback) {
+        var route = window.location + '/game/' + in_gameId + '/resolution?turn=' + in_turn + '&round=' + in_round;
+        var data = {
+          username : in_username,
+          password : in_password
+        };
+        fetch('PUT', route, data, in_callback);
+      };
+
+      var fetchGame = function (in_gameId, in_username, in_password, in_callback) {
+        var route = window.location + '/game/' + in_gameId;
+        var data = {
+          username : in_username,
+          password : in_password
+        };
+        fetch('PUT', route, data, in_callback);
+      };
+
+      var createGame = function (in_container, in_gameId, in_gamePassword, in_gameDuration, in_gameSecondDuration, in_username, in_password, in_callback) {
+        var xhr = new XMLHttpRequest();
+        xhr.open('POST', window.location + '/game/');
+        xhr.addEventListener('load', function (in_event) {
+          if (xhr.status === 200) {
+            in_callback(false);
+          } else if (xhr.status === 403) {
+            uiError(in_container, 'Unauthorized', function () {
+              in_callback(true);
+            });
+          } else if (xhr.status === 401) {
+            uiError(in_container, 'Unauthorized', function () {
+              in_callback(true);
+            });
+          } else {
+            uiError(in_container, 'Unexpected error is unexpected', function () {
+              in_callback(true);
+            });
+          }
+        });
+        xhr.addEventListener('error', function (in_event) {
+          uiError(in_container, 'Unexpected error is unexpected', function () {
+            in_callback(true);
+          });
+        });
+        xhr.addEventListener('abort', function (in_event) {
+          uiError(in_container, 'Unexpected error is unexpected', function () {
+            in_callback(true);
+          });
+        });
+        xhr.setRequestHeader('Content-Type', 'application/json');
+        var credentials = {
+          username : in_username,
+          password : in_password
+        };
+        var data = {
+          gameId : in_gameId,
+          gamePassword : in_gamePassword,
+          gameDuration : in_gameDuration,
+          gameSecondDuration : in_gameSecondDuration,
+          credentials : credentials
+        };
+        xhr.send(JSON.stringify(data));
+      };
+
+      var startGame = function (in_container, in_gameId, in_username, in_password, in_callback) {
+        var xhr = new XMLHttpRequest();
+        xhr.open('PUT', window.location + '/game/' + in_gameId + '/start');
+        xhr.addEventListener('load', function (in_event) {
+          if (xhr.status === 200) {
+            in_callback(false);
+          } else if (xhr.status === 404) {
+            uiError(in_container, 'Game not found', function () {
+              in_callback(true);
+            });
+          } else if (xhr.status === 403) {
+            uiError(in_container, 'Unauthorized', function () {
+              in_callback(true);
+            });
+          } else if (xhr.status === 401) {
+            uiError(in_container, 'Unauthorized', function () {
+              in_callback(true);
+            });
+          } else {
+            uiError(in_container, 'Expected error is unexpected', function () {
+              in_callback(true);
+            });
+          }
+        });
+        xhr.addEventListener('error', function (in_event) {
+          uiError(in_container, 'Expected error is unexpected', function () {
+            in_callback(true);
+          });
+        });
+        xhr.addEventListener('abort', function (in_event) {
+          uiError(in_container, 'Expected error is unexpected', function () {
+            in_callback(true);
+          });
+        });
+        xhr.setRequestHeader('Content-Type', 'application/json');
+        var credentials = {
+          username : in_username,
+          password : in_password
+        };
+        xhr.send(JSON.stringify(credentials));
+      };
+
+      var advanceGame = function (in_container, in_gameId, in_username, in_password, in_callback) {
+        var xhr = new XMLHttpRequest();
+        xhr.open('PUT', window.location + '/game/' + in_gameId + '/advance');
+        xhr.addEventListener('load', function (in_event) {
+          if (xhr.status === 200) {
+            in_callback(false);
+          } else if (xhr.status === 404) {
+            uiError(in_container, 'Game not found', function () {
+              in_callback(true);
+            });
+          } else if (xhr.status === 403) {
+            uiError(in_container, 'Unauthorized', function () {
+              in_callback(true);
+            });
+          } else if (xhr.status === 401) {
+            uiError(in_container, 'Unauthorized', function () {
+              in_callback(true);
+            });
+          } else {
+            uiError(in_container, 'Expected error is unexpected', function () {
+              in_callback(true);
+            });
+          }
+        });
+        xhr.addEventListener('error', function (in_event) {
+          uiError(in_container, 'Expected error is unexpected', function () {
+            in_callback(true);
+          });
+        });
+        xhr.addEventListener('abort', function (in_event) {
+          uiError(in_container, 'Expected error is unexpected', function () {
+            in_callback(true);
+          });
+        });
+        xhr.setRequestHeader('Content-Type', 'application/json');
+        var credentials = {
+          username : in_username,
+          password : in_password
+        };
+        xhr.send(JSON.stringify(credentials));
+      };
+
+      var pauseGame = function (in_container, in_gameId, in_username, in_password, in_callback) {
+        var route = window.location + '/game/' + in_gameId + '/pause';
+        var data = {
+          username : in_username,
+          password : in_password
+        };
+        fetch('PUT', route, data, in_callback);
+      };
+
+      var destroyGame = function (in_container, in_gameId, in_username, in_password, in_callback) {
+        var xhr = new XMLHttpRequest();
+        xhr.open('DELETE', window.location + '/game/' + in_gameId);
+        xhr.addEventListener('load', function (in_event) {
+          if (xhr.status === 200) {
+            in_callback(false);
+          } else if (xhr.status === 404) {
+            uiError(in_container, 'Game not found', function () {
+              in_callback(true);
+            });
+          } else if (xhr.status === 403) {
+            uiError(in_container, 'Unauthorized', function () {
+              in_callback(true);
+            });
+          } else if (xhr.status === 401) {
+            uiError(in_container, 'Unauthorized', function () {
+              in_callback(true);
+            });
+          } else {
+            uiError(in_container, 'Unexpected error is unexpected', function () {
+              in_callback(true);
+            });
+          }
+        });
+        xhr.addEventListener('error', function (in_event) {
+          in_callback('Unexpected error is unexpected');
+        });
+        xhr.addEventListener('abort', function (in_event) {
+          in_callback('Unexpected error is unexpected');
+        });
+        xhr.setRequestHeader('Content-Type', 'application/json');
+        var credentials = {
+          username : in_username,
+          password : in_password
+        };
+        xhr.send(JSON.stringify(credentials));
+      };
+
+      var submitOrders = function (in_container, in_gameId, in_username, in_password, in_phase, in_orders, in_callback) {
+        var xhr = new XMLHttpRequest();
+        xhr.open('PUT', window.location + '/game/' + in_gameId + '/order');
+        xhr.addEventListener('load', function (in_event) {
+          if (xhr.status === 200) {
+            in_callback(false);
+          } else if (xhr.status === 404) {
+            uiError(in_container, 'Game not found', function () {
+              in_callback(true);
+            });
+          } else if (xhr.status === 403) {
+            uiError(in_container, 'Unauthorized', function () {
+              in_callback(true);
+            });
+          } else if (xhr.status === 401) {
+            uiError(in_container, 'Unauthorized', function () {
+              in_callback(true);
+            });
+          } else {
+            uiError(in_container, 'Expected error is unexpected', function () {
+              in_callback(true);
+            });
+          }
+        });
+        xhr.addEventListener('error', function (in_event) {
+          uiError(in_container, 'Expected error is unexpected', function () {
+            in_callback(true);
+          });
+        });
+        xhr.addEventListener('abort', function (in_event) {
+          uiError(in_container, 'Expected error is unexpected', function () {
+            in_callback(true);
+          });
+        });
+        xhr.setRequestHeader('Content-Type', 'application/json');
+        var credentials = {
+          username : in_username,
+          password : in_password
+        };
+        var orders = {
+          tag : in_phase,
+          contents : in_orders
+        };
+        var data = {
+          orders : orders,
+          credentials : credentials
+        };
+        xhr.send(JSON.stringify(data));
+      };
+
+      var makeOrderEntry = function (in_data) {
+        var ret = {
+          left : undefined,
+          right : undefined,
+          getOrder : undefined
+        };
+        if (in_data.phase === 'Adjust') {
+          if (in_data.deficit === 0) {
+          } else if (in_data.deficit > 0) {
+            var orderSubjectLabel = document.createElement('span');
+            orderSubjectLabel.innerHTML = in_data.unit + ' ' + in_data.provinceTarget;
+            var orderObjectInput = document.createElement('input');
+            orderObjectInput.type = 'checkbox';
+            if (in_data.orderObject === 'Disband') {
+              orderObjectInput.checked = true;
+            } else {
+              orderObjectInput.checked = false;
+            }
+            ret.left = orderSubjectLabel;
+            ret.right = orderObjectInput;
+            ret.getOrder = function () {
+              return {
+                greatPower : in_data.greatPower,
+                unit : in_data.unit,
+                provinceTarget : in_data.provinceTarget,
+                orderObject : (orderObjectInput.checked) ? 'Disband' : 'Continue'
+              };
+            };
+          } else {
+            var orderObjectLabel = document.createElement('span');
+            orderObjectLabel.innerHTML = in_data.provinceTarget + ' Build';
+            var orderSubjectInput = document.createElement('select');
+            var selectionNone = document.createElement('option');
+            selectionNone.innerHTML = '-';
+            selectionNone.value = '-';
+            orderSubjectInput.appendChild(selectionNone);
+            var selectionArmy = document.createElement('option');
+            selectionArmy.innerHTML = 'A';
+            selectionArmy.value = 'A';
+            orderSubjectInput.appendChild(selectionArmy);
+            if (in_data.provinceTarget === 'St. Petersburg') {
+              var selectionFleetNC = document.createElement('option');
+              selectionFleetNC.innerHTML = 'F (NC)';
+              selectionFleetNC.value = 'F (NC)';
+              orderSubjectInput.appendChild(selectionFleetNC);
+              var selectionFleetSC = document.createElement('option');
+              selectionFleetSC.innerHTML = 'F (SC)';
+              selectionFleetSC.value = 'F (SC)';
+              orderSubjectInput.appendChild(selectionFleetSC);
+            } else if (coastalHomeSupplyCentres[in_data.provinceTarget]) {
+              var selectionFleet = document.createElement('option');
+              selectionFleet.innerHTML = 'F';
+              selectionFleet.value = 'F';
+              orderSubjectInput.appendChild(selectionFleet);
+            }
+            orderSubjectInput.value = in_data.buildSubjectValue;
+            ret.left = orderSubjectInput;
+            ret.right = orderObjectLabel;
+            ret.getOrder = function () {
+              if (orderSubjectInput.value === 'A' || orderSubjectInput.value === 'F') {
+                return {
+                  greatPower : in_data.greatPower,
+                  unit : orderSubjectInput.value,
+                  provinceTarget : in_data.provinceTarget,
+                  orderObject : 'Build'
+                };
+              } else if (orderSubjectInput.value === 'F (NC)') {
+                return {
+                  greatPower : in_data.greatPower,
+                  unit : 'F',
+                  provinceTarget : in_data.provinceTarget + ' NC',
+                  orderObject : 'Build'
+                };
+              } else if (orderSubjectInput.value === 'F (SC)') {
+                return {
+                  greatPower : in_data.greatPower,
+                  unit : 'F',
+                  provinceTarget : in_data.provinceTarget + ' SC',
+                  orderObject : 'Build'
+                };
+              } else {
+                return null;
+              }
+            };
+          }
+        } else {
+          var orderSubjectLabel = document.createElement('span');
+          orderSubjectLabel.innerHTML = in_data.unit + ' ' + in_data.provinceTarget;
+          var orderObjectInput = document.createElement('input');
+          if (in_data.orderObject) {
+            orderObjectInput.value = in_data.orderObject;
+          }
+          orderObjectInput.addEventListener('click', function () {
+            this.select();
+          });
+          ret.left = orderSubjectLabel;
+          ret.right = orderObjectInput;
+          ret.getOrder = function () {
+            return {
+              greatPower : in_data.greatPower,
+              unit : in_data.unit,
+              provinceTarget : in_data.provinceTarget,
+              orderObject : orderObjectInput.value
+            };
+          };
+        }
+        return ret;
+      };
+
+      var button = function (in_text, in_click) {
+        var div = document.createElement('div');
+        div.innerHTML = in_text;
+        div.style.width = '100%';
+        div.style.backgroundColor = 'rgba(255,255,255,1)';
+        div.style.padding = '10px 10px 10px 10px';
+        div.addEventListener('mouseenter', function () {
+          div.style.backgroundColor = 'rgba(0,0,0,0.2)';
+        });
+        div.addEventListener('mouseleave', function () {
+          div.style.backgroundColor = 'rgba(255,255,255,1)';
+        });
+        div.addEventListener('click', function () {
+          in_click();
+        });
+        return div;
+      };
+
+      var buttons = function (in_buttons) {
+        var ul = document.createElement('ul');
+        ul.style.display = 'table';
+        ul.style.tableLayout = 'fixed';
+        ul.style.width = '100%';
+        ul.style.height = '100%';
+        ul.style.padding = 0;
+        ul.style.margin = 0;
+        for (var i = 0; i < in_buttons.length; ++i) {
+          var thisButton = button(in_buttons[i].text, in_buttons[i].click);
+          thisButton.style.display = 'table-cell';
+          thisButton.style.verticalAlign = 'middle';
+          var li = document.createElement('li');
+          li.style.display = 'table-row';
+          li.style.textAlign = 'center';
+          li.appendChild(thisButton);
+          ul.appendChild(li);
+        }
+        return ul;
+      };
+
+      var uiInput = function (in_container, in_label, in_type, in_callback) {
+        var ul = document.createElement('ul');
+        ul.style.display = 'table';
+        ul.style.tableLayout = 'fixed';
+        ul.style.width = '100%';
+        ul.style.height = '100%';
+        ul.style.padding = 0;
+        ul.style.margin = 0;
+        var label = document.createElement('div');
+        label.innerHTML = in_label;
+        label.style.display = 'table-cell';
+        label.style.verticalAlign = 'middle';
+        var li1 = document.createElement('li');
+        li1.style.display = 'table-row';
+        li1.style.textAlign = 'center';
+        li1.appendChild(label);
+        ul.appendChild(li1);
+        var input = document.createElement('input');
+        input.type = in_type;
+        input.style.display = 'table-cell';
+        input.style.verticalAlign = 'middle';
+        var li2 = document.createElement('li');
+        li2.style.display = 'table-row';
+        li2.style.textAlign = 'center';
+        li2.appendChild(input);
+        ul.appendChild(li2);
+        var form = document.createElement('form');
+        form.appendChild(ul);
+        form.addEventListener('submit', function (in_event) {
+          in_callback(input.value);
+          in_event.preventDefault();
+          in_event.stopPropagation();
+          return false;
+        });
+        clearContainer(in_container);
+        in_container.appendChild(form);
+        input.focus();
+      };
+
+      var uiError = function (in_container, in_description, in_callback) {
+        var ul = document.createElement('ul');
+        ul.style.display = 'table';
+        ul.style.tableLayout = 'fixed';
+        ul.style.width = '100%';
+        ul.style.height = '100%';
+        ul.style.padding = 0;
+        ul.style.margin = 0;
+        var li1 = document.createElement('li');
+        li1.style.display = 'table-row';
+        li1.style.textAlign = 'center';
+        var li2 = document.createElement('li');
+        li2.style.display = 'table-row';
+        li2.style.textAlign = 'center';
+        var li3 = document.createElement('li');
+        li3.style.display = 'table-row';
+        li3.style.textAlign = 'center';
+        var divError = document.createElement('div');
+        divError.innerHTML = 'A diplomatic error!';
+        divError.style.color = 'red';
+        divError.style.display = 'table-cell';
+        divError.style.verticalAlign = 'middle';
+        var divDescription = document.createElement('div');
+        divDescription.innerHTML = in_description;
+        divDescription.style.display = 'table-cell';
+        divDescription.style.verticalAlign = 'middle';
+        var divContinue = button('I understand ...', in_callback);
+        divContinue.style.display = 'table-cell';
+        divContinue.style.verticalAlign = 'middle';
+        li1.appendChild(divError);
+        li2.appendChild(divDescription);
+        li3.appendChild(divContinue);
+        ul.appendChild(li1);
+        ul.appendChild(li2);
+        ul.appendChild(li3);
+        clearContainer(in_container);
+        in_container.appendChild(ul);
+      };
+
+      var svgArmy = function (in_greatPower, in_location) {
+        var g = document.createElementNS('http://www.w3.org/2000/svg', 'g');
+        var path1 = document.createElementNS('http://www.w3.org/2000/svg', 'path');
+        var path2 = document.createElementNS('http://www.w3.org/2000/svg', 'path');
+        var circle = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
+        path1.setAttribute('d', 'M9,-6 L2,0 M9,6 L0,0');
+        path2.setAttribute('d', 'M-11,-6 v4 h17 a2,2 0,0 0 0,-4z');
+        circle.setAttribute('r', '6');
+        g.appendChild(path1);
+        g.appendChild(path2);
+        g.appendChild(circle);
+        g.setAttributeNS(null, 'class', in_greatPower);
+        var translation = 'translate(' + in_location.x + ',' + in_location.y + ')';
+        g.setAttribute('transform', translation);
+        return g;
+      };
+
+      var svgFleet = function (in_greatPower, in_location) {
+        var g = document.createElementNS('http://www.w3.org/2000/svg', 'g');
+        var polygon1 = document.createElementNS('http://www.w3.org/2000/svg', 'polygon');
+        var polygon2 = document.createElementNS('http://www.w3.org/2000/svg', 'polygon');
+        polygon1.setAttributeNS(null, 'points', '-2,-3 10,-3 -2,-13');
+        polygon2.setAttributeNS(null, 'points', '-12,-1 -6,5 6,5 12,-1');
+        g.appendChild(polygon1);
+        g.appendChild(polygon2);
+        g.setAttributeNS(null, 'class', in_greatPower);
+        var translation = 'translate(' + in_location.x + ',' + in_location.y + ')';
+        g.setAttributeNS(null, 'transform', translation);
+        return g;
+      };
+
+      var supplyCentres = {
+        'Norway' : true,
+        'Sweden' : true,
+        'Denmark' : true,
+        'St. Petersburg' : true,
+        'Moscow' : true,
+        'Sevastopol' : true,
+        'Ankara' : true,
+        'Smyrna' : true,
+        'Constantinople' : true,
+        'Rumania' : true,
+        'Bulgaria' : true,
+        'Greece' : true,
+        'Serbia' : true,
+        'Warsaw' : true,
+        'Budapest' : true,
+        'Vienna' : true,
+        'Trieste' : true,
+        'Berlin' : true,
+        'Kiel' : true,
+        'Munich' : true,
+        'Venice' : true,
+        'Rome' : true,
+        'Naples' : true,
+        'Tunis' : true,
+        'Spain' : true,
+        'Portugal' : true,
+        'Marseilles' : true,
+        'Paris' : true,
+        'Brest' : true,
+        'Belgium' : true,
+        'Holland' : true,
+        'London' : true,
+        'Liverpool' : true,
+        'Edinburgh' : true
+      };
+
+      var coastalHomeSupplyCentres = {
+        // We don't include st. petersburg, since it has two coasts where
+        // fleets must go, and we handle it in a special case elsewhere.
+        //'St. Petersburg' : true,
+        'Sevastopol' : true,
+        'Ankara' : true,
+        'Smyrna' : true,
+        'Constantinople' : true,
+        'Trieste' : true,
+        'Venice' : true,
+        'Rome' : true,
+        'Naples' : true,
+        'Berlin' : true,
+        'Kiel' : true,
+        'Marseilles' : true,
+        'Brest' : true,
+        'Liverpool' : true,
+        'London' : true,
+        'Edinburgh' : true
+      };
+
+      var ownership = {
+        'Bohemia' : 'Austria',
+        'Budapest' : 'Austria',
+        'Galicia' : 'Austria',
+        'Trieste' : 'Austria',
+        'Tyrolia' : 'Austria',
+        'Vienna' : 'Austria',
+        'Clyde' : 'Austria',
+        'Edinburgh' : 'England',
+        'Liverpool' : 'England',
+        'London' : 'England',
+        'Wales' : 'England',
+        'Yorkshire' : 'England',
+        'Brest' : 'France',
+        'Burgundy' : 'France',
+        'Gascony' : 'France',
+        'Marseilles' : 'France',
+        'Paris' : 'France',
+        'Picardy' : 'France',
+        'Berlin' : 'Germany',
+        'Kiel' : 'Germany',
+        'Munich' : 'Germany',
+        'Prussia' : 'Germany',
+        'Ruhr' : 'Germany',
+        'Silesia' : 'Germany',
+        'Apulia' : 'Italy',
+        'Naples' : 'Italy',
+        'Piedmont' : 'Italy',
+        'Rome' : 'Italy',
+        'Tuscany' : 'Italy',
+        'Venice' : 'Italy',
+        'Livonia' : 'Russia',
+        'Moscow' : 'Russia',
+        'Sevastopol' : 'Russia',
+        'St. Petersburg' : 'Russia',
+        'Ukraine' : 'Russia',
+        'Warsaw' : 'Russia',
+        'Ankara' : 'Turkey',
+        'Armenia' : 'Turkey',
+        'Constantinople' : 'Turkey',
+        'Smyrna' : 'Turkey',
+        'Syria' : 'Turkey'
+      };
+
+      var unitOccupationLocationMap = {
+        'Adriatic Sea' : { x : 296, y : 441 },
+        'Aegean Sea' : { x : 403, y : 524 },
+        'Albania' : { x : 339, y : 469 },
+        'Ankara' : { x : 500, y : 460 },
+        'Apulia' : { x : 302, y : 472 },
+        'Armenia' : { x : 576, y : 456 },
+        'Baltic Sea' : { x : 323, y : 250 },
+        'Barents Sea' : { x : 445, y : 41 },
+        'Belgium' : { x : 197, y : 317 },
+        'Berlin' : { x : 279, y : 283 },
+        'Black Sea' : { x : 484, y : 420 },
+        'Bohemia' : { x : 289, y : 336 },
+        'Brest' : { x : 125, y : 334 },
+        'Budapest' : { x : 353, y : 378 },
+        'Bulgaria' : { x : 395, y : 443 },
+        'Bulgaria EC' : { x : 410, y : 440 },
+        'Bulgaria SC' : { x : 399, y : 462 },
+        'Burgundy' : { x : 191, y : 360 },
+        'Clyde' : { x : 139, y : 188 },
+        'Constantinople' : { x : 439, y : 473 },
+        'Denmark' : { x : 256, y : 245 },
+        'Eastern Mediterranean' : { x : 474, y : 546 },
+        'Edinburgh' : { x : 157, y : 210 },
+        'English Channel' : { x : 119, y : 307 },
+        'Finland' : { x : 385, y : 143 },
+        'Galicia' : { x : 377, y : 343 },
+        'Gascony' : { x : 137, y : 388 },
+        'Greece' : { x : 366, y : 515 },
+        'Gulf of Lyon' : { x : 180, y : 444 },
+        'Gulf of Bothnia' : { x : 348, y : 199 },
+        'Heligoland Bight' : { x : 226, y : 252 },
+        'Holland' : { x : 205, y : 297 },
+        'Ionian Sea' : { x : 324, y : 540 },
+        'Irish Sea' : { x : 90, y : 276 },
+        'Kiel' : { x : 243, y : 295 },
+        'Liverpool' : { x : 142, y : 241 },
+        'Livonia' : { x : 382, y : 254 },
+        'London' : { x : 162, y : 281 },
+        'Marseilles' : { x : 184, y : 402 },
+        'Mid-Atlantic Ocean' : { x : 23, y : 355 },
+        'Moscow' : { x : 505, y : 226 },
+        'Munich' : { x : 243, y : 347 },
+        'Naples' : { x : 299, y : 505 },
+        'North Atlantic Ocean' : { x : 65, y : 140 },
+        'North Africa' : { x : 100, y : 536 },
+        'North Sea' : { x : 204, y : 215 },
+        'Norway' : { x : 264, y : 160 },
+        'Norwegian Sea' : { x : 220, y : 90 },
+        'Paris' : { x : 162, y : 346 },
+        'Picardy' : { x : 168, y : 319 },
+        'Piedmont' : { x : 220, y : 399 },
+        'Portugal' : { x : 34, y : 417 },
+        'Prussia' : { x : 315, y : 283 },
+        'Rome' : { x : 264, y : 452 },
+        'Ruhr' : { x : 223, y : 320 },
+        'Rumania' : { x : 415, y : 405 },
+        'Serbia' : { x : 351, y : 438 },
+        'Sevastopol' : { x : 515, y : 330 },
+        'Silesia' : { x : 304, y : 314 },
+        'Skagerrak' : { x : 260, y : 212 },
+        'Smyrna' : { x : 490, y : 505 },
+        'Spain' : { x : 64, y : 439 },
+        'Spain NC' : { x : 80, y : 404 },
+        'Spain SC' : { x : 52, y : 475 },
+        'St. Petersburg' : { x : 500, y : 140 },
+        'St. Petersburg NC' : { x : 472, y : 122 },
+        'St. Petersburg SC' : { x : 418, y : 205 },
+        'Sweden' : { x : 315, y : 140 },
+        'Syria' : { x : 570, y : 520 },
+        'Trieste' : { x : 305, y : 412 },
+        'Tunis' : { x : 212, y : 542 },
+        'Tuscany' : { x : 247, y : 430 },
+        'Tyrolia' : { x : 277, y : 378 },
+        'Tyrrhenian Sea' : { x : 246, y : 483 },
+        'Ukraine' : { x : 427, y : 327 },
+        'Venice' : { x : 250, y : 408 },
+        'Vienna' : { x : 314, y : 360 },
+        'Wales' : { x : 125, y : 285 },
+        'Warsaw' : { x : 361, y : 315 },
+        'Western Mediterranean' : { x : 140, y : 492 },
+        'Yorkshire' : { x : 161, y : 254 }
+      };
+
+      var unitOccupationLocation = function (in_provinceTarget) {
+        return unitOccupationLocationMap[in_provinceTarget];
+      };
+
+      var unitDislodgementLocation = function (in_provinceTarget) {
+        var loc = unitOccupationLocation(in_provinceTarget);
+        return {
+          x : loc.x - 5,
+          y : loc.y - 5
+        };
+      };
+
+      window.onload = function () {
+        window.svgMap = document.getElementById('map');
+        window.svgMap.style.display = 'none';
+        window.svgMap.armies = [];
+        window.svgMap.fleets = [];
+        document.body.innerWidth = window.innerWidth;
+        document.body.innerHeight = window.innerHeight;
+        uiInitial(document.body);
+        window.requestAnimationFrame(animate);
+      };
+
+    </script>
+
+    <svg style="display: none;" id="map" viewBox="0 0 610 560" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <title>Diplomacy</title>
+    <desc>
+    Variant SVG coding 2006 by Martin Asal
+    Copyright: CC-BY-SA-3.0-DE  (http://creativecommons.org/licenses/by-sa/3.0/deed.de)
+    Created for Diplomap (http://www.games2relax.net/diplomap/)
+    </desc>
+
+    <defs>
+    <style type="text/css">
+    <![CDATA[
+      .l, .Unowned	{fill:#FFFFDD; stroke:black; stroke-linejoin:round}
+      .w		{fill:#99CCFF; stroke:black; stroke-linejoin:round}
+      .s		{fill:#DDDDDD; stroke:black; stroke-linejoin:round}
+
+      text		{font-family:Arial,Helvetica,sans-serif; font-size:8px}
+
+      .Austria	{fill:#FF0000; stroke:black}
+      .England	{fill:#0000FF; stroke:black}
+      .France 	{fill:#00FFFF; stroke:black}
+      .Germany	{fill:#808080; stroke:black}
+      .Italy  	{fill:#00FF00; stroke:black}
+      .Russia 	{fill:#008000; stroke:black}
+      .Turkey 	{fill:#FFFF00; stroke:black}
+    ]]>
+    </style>
+    </defs>
+
+    <g id="Norwegian Sea" title="Norwegian Sea">
+      <polygon class="w" points="362,33 357,39 343,44 324,54 320,64 310,75 309,84 303,86 292,111 277,132 269,134 264,142 258,141 236,154 198,154 171,181 171,197 158,193 152,194 154,188 161,185 162,181
+        148,177 148,0 362,0"/>
+      <text x="220" y="70">Nrg</text>
+    </g>
+    <g id="North Sea" title="North Sea">
+      <path class="w" d="M171,197 L171,181 A27,27 0,0,1 198,154 L241,154 L241,224 L248,224 L245,237 L211,237 L211,301 L173,301 L165,293 L140,197Z"/>
+      <text x="190" y="230">Nth</text>
+    </g>
+
+    <g id="Switzerland" title="Switzerland">
+      <polygon class="s" points="209,363 208,367 194,382 197,385 203,379 207,386 213,387 221,385 227,390 229,385 243,388 245,384 241,378 234,374 234,366 232,363 225,362 222,365"/>
+      <text x="215" y="378">Swi</text>
+    </g>
+    <g id="Adriatic Sea" title="Adriatic Sea">
+      <polygon class="w" points="322,480 297,456 300,453 290,453 278,443 272,424 260,417 261,401 270,398 276,399 275,403 278,410 282,401 286,402 289,418 306,436 331,454 331,477 335,480"/>
+      <text x="308" y="460">Adr</text>
+    </g>
+    <g id="Aegean Sea" title="Aegean Sea">
+      <polygon class="w" points="376,537 371,520 378,521 377,513 386,516 385,509 370,494 371,491 378,494 368,483 371,477 379,484 382,483 381,477 386,478 380,472 392,472 400,468 408,470 410,473 414,475
+        410,482 409,487 417,486 417,489 420,495 417,498 417,507 423,510 427,524 435,523 435,530 416,549 412,547 387,546 383,544"/>
+      <text x="392" y="510">Aeg</text>
+    </g>
+    <g id="Albania" title="Albania">
+      <polygon class="l" points="331,454 331,477 335,480 339,487 350,477 350,471 346,466 346,452 337,446 330,445"/>
+      <text x="333" y="460">Alb</text>
+    </g>
+    <g id="Ankara" title="Ankara">
+      <polygon class="l" points="555,438 551,437 520,441 514,438 511,440 502,433 481,438 470,447 464,457 468,461 468,479 466,491 473,491 490,480 501,482 508,480 531,460 546,462 555,460 557,449"/> 
+      <text x="510" y="455">Ank</text>
+    </g>
+    <g id="Apulia" title="Apulia">
+      <polygon class="l" points="304,484 310,480 318,485 322,485 322,480 297,456 300,453 290,453 278,443 274,447 279,451 280,455 279,458 293,481"/>
+      <text x="291" y="470">Apu</text>
+    </g>
+    <g id="Armenia" title="Armenia">
+      <polygon class="l" points="609,493 584,478 563,479 562,471 556,467 555,460 557,449 555,438 570,427 589,442 594,439 603,441 609,440"/>
+      <text x="585" y="467">Arm</text>
+    </g>
+    <g id="Baltic Sea" title="Baltic Sea">
+      <polygon class="w" points="266,255 271,260 278,254 277,250 280,248 279,243 282,253 289,254 294,245 305,244 312,229 311,220 359,220 349,229 347,243 347,248 348,254 344,262 337,264 334,273 328,274
+        326,265 314,266 307,273 294,275 286,274 287,267 280,266 266,275 261,274 260,269 256,266 256,263 254,255"/>
+      <text x="308" y="260">Bal</text>
+    </g>
+    <g id="Barents Sea" title="Barents Sea">
+      <polygon class="w" points="540 0 535,9 530,6 517,19 516,33 513,38 513,23 507,20 505,26 499,33 492,48 495,58 488,60 479,57 477,55 481,50 473,43 466,45 472,62 478,66 478,74 472,72 468,74 457,91
+        469,100 467,106 462,109 444,101 442,110 447,115 454,119 452,122 434,118 426,103 426,94 414,88 412,83 445,84 457,79 459,66 453,61 417,47 405,49 401,45 397,48 391,47 395,41 394,38 384,33 382,40
+        380,33 377,31 374,38 371,33 366,42 366,33 362,33 362,0"/>
+      <text x="440" y="15">Bar</text>
+    </g>
+    <g id="Belgium" title="Belgium">
+      <polygon class="l" points="191,299 194,303 206,306 205,311 208,315 210,326 205,331 192,323 184,315 169,311 173,301"/>
+      <text x="192" y="321">Bel</text>
+    </g>
+    <g id="Berlin" title="Berlin">
+      <polygon class="l" points="294,275 286,274 287,267 280,266 266,275 266,283 262,287 264,293 261,296 263,310 288,305 296,300 297,296 292,290"/>
+      <text x="272" y="292">Ber</text>
+    </g>
+    <g id="Black Sea" title="Black Sea">
+      <polygon class="w" points="440,458 430,455 426,450 422,441 425,427 429,426 430,423 432,409 439,404 438,397 446,378 459,375 461,377 459,379 465,383 476,381 478,383 472,385 468,392 477,396 477,401
+        486,404 488,397 494,396 497,392 507,389 506,384 494,387 485,378 503,364 526,351 527,354 514,365 517,371 520,371 515,384 511,383 510,386 517,393 528,394 554,406 567,408 573,417 570,427 555,438
+        551,437 520,441 514,438 511,440 502,433 481,438 470,447 464,457 442,460"/>
+      <text x="500" y="418">Bla</text>
+    </g>
+    <g id="Bohemia" title="Bohemia">
+      <polygon class="l" points="281,356 276,346 268,343 264,329 266,325 278,326 288,321 297,322 311,334 314,332 321,339 322,347 316,348 303,346 295,349 292,357"/>
+      <text x="283" y="347">Boh</text>
+    </g>
+    <g id="Brest" title="Brest">
+      <polygon class="l" points="150,319 144,318 142,312 136,310 136,326 124,323 122,318 102,317 100,322 103,328 109,329 123,344 122,350 123,357 128,363 146,365 146,337 148,329"/>
+      <text x="130" y="345">Bre</text>
+    </g>
+    <g id="Budapest" title="Budapest">
+      <polygon class="l" points="394,376 395,382 401,385 406,396 401,402 387,402 367,406 365,412 360,413 342,410 338,412 335,410 332,410 323,408 321,398 311,394 308,383 311,375 322,370 335,354 337,350
+        350,347 360,351 368,353 377,360 378,363 384,365"/>
+      <text x="350" y="390">Bud</text>
+    </g>
+    <g id="Bulgaria EC" title="Bulgaria (ec)">
+      <polyline class="l" points="413,464 412,454 420,451 426,450 422,441 425,427 429,426 430,423 422,420 410,420 404,422 398,427 390,425 382,427 375,423 370,425 367,421 365,425 368,433 371,438"/>
+      <text x="395" y="443">Bul</text>
+    </g>
+    <g id="Bulgaria SC" title="Bulgaria (sc)">
+      <polyline class="l" points="371,438 366,439 371,456 365,461 369,464 376,464 388,460 392,472 400,468 408,470 413,464 412,454"/>
+    </g>
+    <g id="Burgundy" title="Burgundy">
+      <polygon class="l" points="192,323 205,331 204,338 211,346 213,352 209,363 208,367 194,382 178,381 178,390 173,396 168,395 163,387 165,383 158,380 156,374 165,365 185,344 188,332"/>
+      <text x="185" y="371">Bur</text>
+    </g>
+    <g id="Clyde" title="Clyde">
+      <polygon class="l" points="138,214 130,208 129,197 139,189 140,182 148,177 162,181 161,185 154,188 152,194 146,200 144,213"/>
+      <text x="133" y="201">Cly</text>
+    </g>
+    <g id="Constantinople" title="Constantinople">
+      <polygon class="l" points="408,470 410,473 414,475 410,482 409,487 417,486 417,489 423,487 432,493 452,495 466,491 468,479 468,461 464,457 442,460 440,458 430,455 426,450 420,451 412,454 413,464"/>
+      <polygon class="w" points="414,475 421,467 435,463 440,458 442,460 439,463 448,464 425,475"/>
+      <text x="435" y="483">Con</text>
+    </g>
+    <g id="Denmark" title="Denmark">
+      <polygon class="l" points="279,243 275,242 269,243 266,240 267,234 266,221 263,223 248,224 245,237 243,247 244,254 254,255 266,255 271,260 278,254 277,250 280,248 "/>
+      <polygon class="w" points="269,243 268,246 263,247 266,255 254,255 257,247 266,240"/>
+      <text x="250" y="235">Den</text>
+    </g>
+    <g id="Eastern Mediterranean" title="Eastern Mediterranean">
+      <polygon class="w" points="435,530 441,526 447,528 453,534 464,531 466,521 475,520 485,528 491,530 505,526 511,514 520,517 527,508 530,509 525,518 526,530 532,535 528,559 400,559 400,554 414,552 416,549"/>
+      <text x="455" y="550">Eas</text>
+    </g>
+    <g id="Edinburgh" title="Edinburgh">
+      <polygon class="l" points="152,194 158,193 171,197 170,202 165,210 158,214 151,215 157,216 161,218 163,226 155,228 145,217 144,213 146,200"/>
+      <text x="152" y="202">Edi</text>
+    </g>
+    <g id="English Channel" title="English Channel">
+      <polygon class="w" points="173,301 169,311 153,315 155,320 150,319 144,318 142,312 136,310 136,326 124,323 122,318 102,317 88,303 100,291 110,292 120,295 124,291 134,294 147,295 160,298 168,296"/>
+      <text x="135" y="306">Eng</text>
+    </g>
+    <g id="Finland" title="Finland">
+      <polygon class="l" points="362,107 368,108 372,120 366,121 359,136 345,151 347,160 350,165 348,178 349,184 357,186 365,191 384,185 402,177 412,161 410,152 414,147 410,130 402,118 401,110 392,92
+        393,73 387,68 388,61 386,58 388,54 379,48 370,49 369,61 355,62 346,54 342,61 356,71"/>
+      <text x="375" y="160">Fin</text>
+    </g>
+    <g id="Galicia" title="Galicia">
+      <polygon class="l" points="333,330 341,330 344,332 353,327 356,323 361,324 367,329 374,327 379,324 383,327 385,332 399,338 404,354 403,360 404,371 394,376 384,365 378,363 377,360 368,353 360,351
+        350,347 337,350 329,346 322,347 321,339 322,347 321,339 325,340 329,338"/>
+      <text x="355" y="343">Gal</text>
+    </g>
+    <g id="Gascony" title="Gascony">
+      <polygon class="l" points="128,363 121,382 122,384 112,399 113,407 123,412 134,417 135,414 142,417 149,403 157,397 168,395 163,387 165,383 158,380 156,374 149,372 146,365"/>
+      <text x="130" y="400">Gas</text>
+    </g>
+    <g id="Greece" title="Greece">
+      <polygon class="l" points="339,487 346,498 350,498 347,500 352,508 367,507 371,511 355,510 350,514 357,521 359,533 360,528 367,536 368,531 376,537 371,520 378,521 377,513 386,516 385,509 370,494
+        371,491 378,494 368,483 371,477 379,484 382,483 381,477 386,478 380,472 392,472 388,460 376,464 369,464 361,467 356,471 350,471 350,477"/>
+      <text x="352" y="490">Gre</text>
+    </g>
+    <g id="Gulf of Lyon" title="Gulf of Lyon">
+      <polygon class="w" points="115,469 110,461 124,444 131,439 146,438 157,432 158,425 158,418 169,412 176,417 188,422 198,421 211,416 222,410 233,415 238,431 224,431 221,434 211,436 213,451 218,454
+        218,458 214,461 206,462 205,466 154,466 148,463 142,469"/>
+      <text x="170" y="457">GoL</text>
+    </g>
+    <g id="Gulf of Bothnia" title="Gulf of Bothnia">
+      <polygon class="w" points="311,220 314,209 322,206 328,203 331,193 326,183 320,182 321,161 330,146 343,138 351,128 347,121 349,112 355,104 362,107 368,108 372,120 366,121 359,136 345,151 347,160
+        350,165 348,178 349,184 357,186 365,191 384,185 402,177 403,183 411,184 414,187 408,187 400,192 399,197 387,196 371,198 369,202 365,204 368,210 372,213 373,221 377,227 373,229 366,228 359,220"/>
+      <text x="328" y="175">Bot</text>
+    </g>
+    <g id="Heligoland Bight" title="Heligoland Bight">
+      <polygon class="w" points="245,237 243,247 244,254 243,257 245,263 244,270 244,273 235,277 234,274 230,273 226,275 211,274 211,237"/>
+      <text x="220" y="265">Hel</text>
+    </g>
+    <g id="Holland" title="Holland">
+      <polygon class="l" points="226,275 227,280 225,292 220,298 215,297 213,302 210,313 208,315 205,311 206,306 194,303 191,299 198,289 205,276 205,279 207,279 211,274"/>
+      <text x="210" y="290">Hol</text>
+    </g>
+    <g id="Ionian Sea" title="Ionian Sea">
+      <polygon class="w" points="289,511 290,514 295,515 308,500 311,491 304,484 310,480 318,485 322,485 322,480 335,480 339,487 346,498 350,498 347,500 352,508 367,507 371,511 355,510 350,514 357,521
+        359,533 360,528 367,536 368,531 376,537 383,544 380,547 383,550 400,554 400,559 232,559 234,551 232,544 225,535 231,531 236,524 247,513 258,519 273,531 281,532 282,521 285,513 285,511"/>
+      <text x="315" y="520">Ion</text>
+    </g>
+    <g id="Irish Sea" title="Irish Sea">
+      <polygon class="w" points="100,291 112,287 122,281 130,282 127,276 119,272 116,272 115,265 128,262 126,256 121,257 132,250 135,250 139,240 136,229 130,227 120,227 110,232 109,246 98,259 87,257
+        70,261 58,273 88,303"/>
+      <text x="95" y="270">Iri</text>
+    </g>
+    <g id="Kiel" title="Kiel">
+      <polygon class="l" points="244,254 243,257 245,263 244,270 244,273 235,277 234,274 230,273 226,275 227,280 225,292 220,298 215,297 213,302 232,308 241,316 243,322 263,310 261,296 264,293
+        262,287 266,283 266,275 261,274 260,269 256,266 256,263 254,255"/>
+      <polygon class="w" points="244,270 244,273 256,266 256,263 "/>
+      <text x="237" y="285">Kie</text>
+    </g>
+    <g id="Liverpool" title="Liverpool">
+      <polygon class="l" points="128,262 126,256 121,257 132,250 135,250 139,240 136,229 130,227 130,223 138,217 138,214 144,213 145,217 155,228 155,239 151,248 150,264 143,262"/>
+      <text x="138" y="230">Lvp</text>
+    </g>
+    <g id="Livonia" title="Livonia">
+      <polygon class="l" points="369,202 365,204 368,210 372,213 373,221 377,227 373,229 366,228 359,220 349,229 347,243 354,251 356,261 362,260 367,265 365,281 372,283 379,290 389,285 392,278
+        404,275 405,239 409,228 405,217 394,205 382,206 372,205"/>
+      <text x="380" y="260">Lvn</text>
+    </g>
+    <g id="London" title="London">
+      <polygon class="l" points="166,269 168,270 171,268 177,270 178,274 176,283 165,293 172,294 168,296 160,298 147,295 145,281 150,277 153,271"/>
+      <text x="160" y="280">Lon</text>
+    </g>
+    <g id="Marseilles" title="Marseilles">
+      <polygon class="l" points="142,417 149,403 157,397 168,395 173,396 178,390 178,381 194,382 197,385 203,379 207,386 204,390 207,396 201,399 204,402 203,410 211,416 198,421 188,422 176,417
+        169,412 158,418 158,425 154,427"/>
+      <text x="173" y="412">Mar</text>
+    </g>
+    <g id="Mid-Atlantic Ocean" title="Mid-Atlantic Ocean">
+      <polygon class="w" points="102,317 100,322 103,328 109,329 123,344 122,350 123,357 128,363 121,382 122,384 112,399 101,396 96,397 72,384 59,381 54,375 48,374 46,378 39,375 33,381 35,384 32,396
+        30,406 17,427 14,427 10,433 13,440 15,441 12,450 13,454 8,462 19,469 27,468 33,475 34,484 37,490 37,495 33,496 17,518 0,520 0,273 58,273"/>
+      <text x="50" y="355">Mid</text>
+    </g>
+    <g id="Moscow" title="Moscow">
+      <polygon class="l" points="609,117 598,132 573,143 564,159 534,164 515,169 489,184 476,183 458,194 456,207 457,210 451,213 447,209 439,211 428,225 421,229 409,228 405,239 404,275 392,278
+        389,285 379,290 386,309 390,306 456,292 468,295 477,289 494,295 505,280 516,286 526,287 533,283 549,284 554,304 564,305 569,321 597,330 609,330"/>
+      <text x="460" y="265">Mos</text>
+    </g>
+    <g id="Munich" title="Munich">
+      <polygon class="l" points="234,366 243,370 246,369 250,371 267,368 271,370 269,362 275,362 281,356 276,346 268,343 264,329 266,325 278,326 288,321 284,314 288,305 263,310 243,322 237,322
+        219,344 211,346 213,352 209,363 222,365 225,362 232,363"/>
+      <text x="235" y="360">Mun</text>
+    </g>
+    <g id="Naples" title="Naples">
+      <polygon class="l" points="271,464 276,474 290,487 294,502 289,511 290,514 295,515 308,500 311,491 304,484 293,481 279,458"/>
+      <text x="293" y="493">Nap</text>
+    </g>
+    <g id="North Atlantic Ocean" title="North Atlantic Ocean">
+      <polygon class="w" points="70,261 64,250 67,242 71,245 81,234 74,228 80,225 78,218 82,217 89,220 94,220 95,218 94,216 97,216 101,212 110,212 119,217 120,227 130,227 130,223 138,217 138,214
+        130,208 129,197 139,189 140,182 148,177 148,0 0,0 0,273 58,273"/>
+      <text x="65" y="120">NAt</text>
+    </g>
+    <g id="North Africa" title="North Africa">
+      <polygon class="l" points="203,520 179,515 169,518 150,511 117,509 106,511 99,515 89,512 84,518 79,520 68,516 68,511 64,514 46,509 42,502 41,494 37,495 33,496 17,518 0,520 0,559 195,559 197,527"/>
+      <text x="130" y="536">NAf</text>
+    </g>
+    <g id="Norway" title="Norway">
+      <polygon class="l" points="397,48 391,47 395,41 394,38 384,33 382,40 380,33 377,31 374,38 371,33 366,42 366,33 362,33 357,39 343,44 324,54 320,64 310,75 309,84 303,86 292,111 277,132 269,134
+        264,142 258,141 236,154 237,160 233,167 231,180 233,186 229,192 231,201 241,209 246,210 266,201 270,193 275,203 279,204 287,177 285,170 290,164 292,133 301,132 300,126 309,115 308,104 311,101
+        324,71 332,74 330,64 341,65 342,61 346,54 355,62 369,61 370,49 379,48 388,54 386,58 388,61"/>
+      <text x="250" y="175">Nwy</text>
+    </g>
+    <g id="Paris" title="Paris">
+      <polygon class="l" points="146,365 149,372 156,374 165,365 185,344 188,332 172,328 165,331 159,331 148,329 146,337"/>
+      <text x="155" y="358">Par</text>
+    </g>
+    <g id="Picardy" title="Picardy">
+      <polygon class="l" points="169,311 153,315 155,320 150,319 148,329 159,331 165,331 172,328 188,332 192,323 184,315"/>
+      <text x="168" y="326">Pic</text>
+    </g>
+    <g id="Piedmont" title="Piedmont">
+      <polygon class="l" points="207,386 204,390 207,396 201,399 204,402 203,410 211,416 222,410 233,415 236,411 233,404 246,392 243,388 229,385 227,390 221,385 213,387"/>
+      <text x="215" y="408">Pie</text>
+    </g>
+    <g id="Portugal" title="Portugal">
+      <polygon class="l" points="32,396 30,406 17,427 14,427 10,433 13,440 15,441 12,450 13,454 8,462 19,469 27,468 36,457 34,447 40,441 37,431 42,432 52,412 61,411 62,407 55,400 42,399 43,395"/>
+      <text x="22" y="440">Por</text>
+    </g>
+    <g id="Prussia" title="Prussia">
+      <polygon class="l" points="347,243 347,248 348,254 344,262 337,264 334,273 328,274 326,265 314,266 307,273 294,275 292,290 297,296 296,300 320,303 324,299 326,292 341,287 345,289 359,286
+        365,281 367,265 362,260 356,261 354,251"/>
+      <text x="335" y="283">Pru</text>
+    </g>
+    <g id="Rome" title="Rome">
+      <polygon class="l" points="247,442 248,447 256,458 271,464 279,458 280,455 279,451 274,447 263,434 250,438"/>
+      <text x="257" y="452">Rom</text>
+    </g>
+    <g id="Ruhr" title="Ruhr">
+      <polygon class="l" points="213,302 210,313 208,315 210,326 205,331 204,338 211,346 219,344 237,322 243,322 241,316 232,308"/>
+      <text x="215" y="330">Ruh</text>
+    </g>
+    <g id="Rumania" title="Rumania">
+      <polygon class="l" points="403,360 404,371 394,376 395,382 401,385 406,396 401,402 387,402 367,406 365,412 367,421 370,425 375,423 382,427 390,425 398,427 404,422 410,420 422,420 430,423 432,409
+        439,404 438,397 427,399 422,382 423,376 414,372 411,361"/>
+      <text x="410" y="415">Rum</text>
+    </g>
+    <g id="Serbia" title="Serbia">
+      <polygon class="l" points="365,412 360,413 342,410 338,412 335,410 332,410 330,416 331,424 327,429 330,437 337,446 346,452 346,466 350,471 356,471 361,467 369,464 365,461 371,456 366,439 371,438
+        368,433 365,425 367,421"/>
+      <text x="350" y="450">Ser</text>
+    </g>
+    <g id="Sevastopol" title="Sevastopol">
+      <polygon class="l" points="438,397 446,378 459,375 461,377 459,379 465,383 476,381 478,383 472,385 468,392 477,396 477,401 486,404 488,397 494,396 497,392 507,389 506,384 494,387 485,378 503,364
+        526,351 527,354 514,365 517,371 520,371 515,384 511,383 510,386 517,393 528,394 554,406 567,408 573,417 570,427 589,442 594,439 603,441 609,440 609,330 597,330 569,321 564,305 554,304 549,284
+        533,283 526,287 516,286 505,280 494,295 477,289 468,295 470,303 466,307 460,345 445,350 434,360 432,372 423,376 422,382 427,399"/>
+      <text x="540" y="350">Sev</text>
+    </g>
+    <g id="Silesia" title="Silesia">
+      <polygon class="l" points="288,321 297,322 311,334 314,332 321,339 325,340 329,338 333,330 326,327 323,322 320,303 296,300 288,305 284,314"/>
+      <text x="304" y="325">Sil</text>
+    </g>
+    <g id="Skagerrak" title="Skagerrak">
+      <polygon class="w" points="241,209 246,210 266,201 270,193 275,203 277,218 276,224 282,236 279,240 279,243 275,242 269,243 266,240 267,234 266,221 263,223 248,224 241,224"/>
+      <text x="255" y="220">Ska</text>
+    </g>
+    <g id="Smyrna" title="Smyrna">
+      <polygon class="l" points="417,489 420,495 417,498 417,507 423,510 427,524 435,523 435,530 441,526 447,528 453,534 464,531 466,521 475,520 485,528 491,530 505,526 511,514 520,517 527,508 530,509
+        536,494 545,486 555,484 563,479 562,471 556,467 555,460 546,462 531,460 508,480 501,482 490,480 473,491 466,491 452,495 432,493 423,487"/>
+      <text x="460" y="510">Smy</text>
+    </g>
+    <g id="Spain NC" title="Spain (nc)">
+      <polyline class="l" points="134,417 123,412 113,407 112,399 101,396 96,397 72,384 59,381 54,375 48,374 46,378 39,375 33,381 35,384 32,396 43,395 42,399 55,400 62,407 61,411 52,412 42,432 37,431 40,441"/>
+    </g>
+    <g id="Spain SC" title="Spain (sc)">
+      <polyline class="l" points="40,441 34,447 36,457 27,468 33,475 34,484 37,490 47,488 52,489 60,486 78,491 83,494 86,485 90,483 98,484 107,474 113,473 115,469 110,461 124,444 131,439 146,438
+        157,432 158,425 154,427 142,417 135,414 134,417 123,412"/>
+      <text x="85" y="450">Spa</text>
+    </g>
+    <g id="St. Petersburg NC" title="St Petersburg (nc)">
+      <polyline class="l" points="534,164 564,159 573,143 598,132 609,117 609,0 540 0 535,9 530,6 517,19 516,33 513,38 513,23 507,20 505,26 499,33 492,48 495,58 488,60 479,57 477,55 481,50 473,43 466,45 472,62
+        478,66 478,74 472,72 468,74 457,91 469,100 467,106 462,109 444,101 442,110 447,115 454,119 452,122 434,118 426,103 426,94 414,88 412,83 445,84 457,79 459,66 453,61 417,47 405,49 401,45 397,48
+        388,61 387,68 393,73 392,92 401,110 402,118 410,130 414,147"/>
+      <text x="460" y="149">StP</text>
+    </g>
+    <g id="St. Petersburg SC" title="St Petersburg (sc)">
+      <polyline class="l" points="414,147 410,152 412,161 402,177 403,183 411,184 414,187 408,187 400,192 399,197 387,196 371,198 369,202 372,205 382,206 394,205 405,217 409,228 421,229 428,225 439,211
+        447,209 451,213 457,210 456,207 458,194 476,183 489,184 515,169 534,164 564,159"/>
+    </g>
+    <g id="Sweden" title="Sweden">
+      <polygon class="l" points="275,203 277,218 276,224 282,236 279,240 279,243 282,253 289,254 294,245 305,244 312,229 311,220 314,209 322,206 328,203 331,193 326,183 320,182 321,161 330,146 343,138
+        351,128 347,121 349,112 355,104 362,107 356,71 342,61 341,65 330,64 332,74 324,71 311,101 308,104 309,115 300,126 301,132 292,133 290,164 285,170 287,177 279,204"/>
+      <text x="300" y="170">Swe</text>
+    </g>
+    <g id="Syria" title="Syria">
+      <polygon class="l" points="530,509 536,494 545,486 555,484 563,479 584,478 609,493 609,559 528,559 532,535 526,530 525,518"/>
+      <text x="570" y="535">Syr</text>
+    </g>
+    <g title="Trieste">
+      <polygon class="l" points="276,399 275,403 278,410 282,401 286,402 289,418 306,436 331,454 330,445 337,446 330,437 327,429 331,424 330,416 332,410 323,408 321,398 311,394 308,383 299,385 294,380
+        289,385 276,386 279,389"/>
+      <text x="305" y="425">Tri</text>
+    </g>
+    <g id="Tunis" title="Tunis">
+      <polygon class="l" points="232,559 234,551 232,544 225,535 231,531 236,524 233,523 224,527 223,518 218,516 212,517 208,521 203,520 197,527 195,559"/>
+      <text x="210" y="555">Tun</text>
+    </g>
+    <g id="Tuscany" title="Tuscany">
+      <polygon class="l" points="233,415 238,431 247,442 250,438 263,434 253,418 246,416 240,415 236,411"/>
+      <text x="240" y="425">Tus</text>
+    </g>
+    <g id="Tyrolia" title="Tyrolia">
+      <polygon class="l" points="234,366 243,370 246,369 250,371 267,368 271,370 269,362 275,362 281,356 292,357 295,362 294,380 289,385 276,386 268,385 259,388 255,394 250,397 246,392 243,388 245,384
+        241,378 234,374"/>
+      <text x="255" y="380">Tyr</text>
+    </g>
+    <g id="Tyrrhenian Sea" title="Tyrrhenian Sea">
+      <polygon class="w" points="238,431 247,442 248,447 256,458 271,464 276,474 290,487 294,502 289,511 285,511 285,508 276,510 263,510 257,507 252,508 247,513 236,524 233,523 224,527 223,518
+        218,516 218,490 220,490 224,468 222,458 218,458 218,454 223,450 225,444 225,436 224,431"/>
+      <text x="245" y="495">Tyn</text>
+    </g>
+    <g id="Ukraine" title="Ukraine">
+      <polygon class="l" points="383,327 385,332 399,338 404,354 403,360 411,361 414,372 423,376 432,372 434,360 445,350 460,345 466,307 470,303 468,295 456,292 390,306 386,309"/>
+      <text x="420" y="340">Ukr</text>
+    </g>
+    <g id="Venice" title="Venice">
+      <polygon class="l" points="278,443 272,424 260,417 261,401 270,398 276,399 279,389 276,386 268,385 259,388 255,394 250,397 246,392 233,404 236,411 240,415 246,416 253,418 263,434 274,447"/>
+      <text x="245" y="407">Ven</text>
+    </g>
+    <g id="Vienna" title="Vienna">
+      <polygon class="l" points="292,357 295,349 303,346 316,348 322,347 329,346 337,350 335,354 322,370 311,375 308,383 299,385 294,380 295,362"/>
+      <text x="307" y="370">Vie</text>
+    </g>
+    <g id="Wales" title="Wales">
+      <polygon class="l" points="100,291 112,287 122,281 130,282 127,276 119,272 116,272 115,265 128,262 143,262 150,264 153,271 150,277 145,281 147,295 134,294 124,291 120,295 110,292"/>
+      <text x="130" y="275">Wal</text>
+    </g>
+    <g id="Warsaw" title="Warsaw">
+      <polygon class="l" points="333,330 326,327 323,322 320,303 324,299 326,292 341,287 345,289 359,286 365,281 372,283 379,290 386,309 383,327 379,324 374,327 367,329 361,324 356,323 353,327
+        344,332 341,330"/>
+      <text x="355" y="304">War</text>
+    </g>
+    <g id="Western Mediterranean" title="Western Mediterranean">
+      <polygon class="w" points="37,490 47,488 52,489 60,486 78,491 83,494 86,485 90,483 98,484 107,474 113,473 115,469 142,469 150,471 154,466 205,466 206,476 204,485 208,492 212,492 217,489 218,490
+        218,516 212,517 208,521 203,520 179,515 169,518 150,511 117,509 106,511 99,515 89,512 84,518 79,520 68,516 68,511 64,514 46,509 42,502 41,494 37,495"/>
+      <text x="160" y="491">Wes</text>
+    </g>
+    <g id="Yorkshire" title="Yorkshire">
+      <polygon class="l" points="163,226 163,239 168,246 170,252 169,265 166,269 153,271 150,264 151,248 155,239 155,228"/>
+      <text x="155" y="254">Yor</text>
+    </g><defs>
+
+    <g id="A">
+      <path d="M9,-6 L2,0 M9,6 L0,0"/>
+      <path d="M-11,-6 v4 h17 a2,2 0,0 0 0,-4z"/>
+      <circle r="6"/>
+    </g>
+    <g id="F">
+      <polygon points="-2,-3 10,-3 -2,-13"/>
+      <polygon points="-12,-1 -6,5 6,5 12,-1"/>
+    </g>
+    <g id="sc"> <!-- colored SC -->
+      <circle r="4"/>
+    </g>
+    <use id="SC" xlink:href="#sc" class="Unowned"/>
+    </defs>
+
+    <g id="SCNorway" title="Norway"><use xlink:href="#sc" class="Unowned" transform="translate(270,187)"/></g>
+    <g id="SCSweden" title="Sweden"><use xlink:href="#sc" class="Unowned" transform="translate(323,196)"/></g>
+    <g id="SCDenmark" title="Denmark"><use xlink:href="#sc" class="Unowned" transform="translate(272,252)"/></g>
+    <g id="SCMoscow" title="Moscow"><use xlink:href="#sc" class="Unowned" transform="translate(481,234)"/></g>
+    <g id="SCSevastopol" title="Sevastopol"><use xlink:href="#sc" class="Unowned" transform="translate(483,396)"/></g>
+    <g id="SCSt. Petersburg" title="St Petersburg"><use xlink:href="#sc" class="Unowned" transform="translate(418,187)"/></g>
+    <g id="SCAnkara" title="Ankara"><use xlink:href="#sc" class="Unowned" transform="translate(482,469)"/></g>
+    <g id="SCConstantinople" title="Constantinople"><use xlink:href="#sc" class="Unowned" transform="translate(429,460)"/></g>
+    <g id="SCSmyrna" title="Smyrna"><use xlink:href="#sc" class="Unowned" transform="translate(424,502)"/></g>
+    <g id="SCRumania" title="Rumania"><use xlink:href="#sc" class="Unowned" transform="translate(402,413)"/></g>
+    <g id="SCBulgaria" title="Bulgaria"><use xlink:href="#sc" class="Unowned" transform="translate(377,444)"/></g>
+    <g id="SCGreece" title="Greece"><use xlink:href="#sc" class="Unowned" transform="translate(378,507)"/></g>
+    <g id="SCSerbia" title="Serbia"><use xlink:href="#sc" class="Unowned" transform="translate(343,419)"/></g>
+    <g id="SCWarsaw" title="Warsaw"><use xlink:href="#sc" class="Unowned" transform="translate(346,302)"/></g>
+    <g id="SCBudapest" title="Budapest"><use xlink:href="#sc" class="Unowned" transform="translate(326,376)"/></g>
+    <g id="SCVienna" title="Vienna"><use xlink:href="#sc" class="Unowned" transform="translate(301,363)"/></g>
+    <g id="SCTrieste" title="Trieste"><use xlink:href="#sc" class="Unowned" transform="translate(284,396)"/></g>
+    <g id="SCBerlin" title="Berlin"><use xlink:href="#sc" class="Unowned" transform="translate(281,298)"/></g>
+    <g id="SCKiel" title="Kiel"><use xlink:href="#sc" class="Unowned" transform="translate(254,278)"/></g>
+    <g id="SCMunich" title="Munich"><use xlink:href="#sc" class="Unowned" transform="translate(258,359)"/></g>
+    <g id="SCVenice" title="Venice"><use xlink:href="#sc" class="Unowned" transform="translate(261,397)"/></g>
+    <g id="SCRome" title="Rome"><use xlink:href="#sc" class="Unowned" transform="translate(252,443)"/></g>
+    <g id="SCNaples" title="Naples"><use xlink:href="#sc" class="Unowned" transform="translate(278,469)"/></g>
+    <g id="SCTunis" title="Tunis"><use xlink:href="#sc" class="Unowned" transform="translate(220,529)"/></g>
+    <g id="SCSpain" title="Spain"><use xlink:href="#sc" class="Unowned" transform="translate(80,432)"/></g>
+    <g id="SCPortugal" title="Portugal"><use xlink:href="#sc" class="Unowned" transform="translate(15,434)"/></g>
+    <g id="SCMarseilles" title="Marseilles"><use xlink:href="#sc" class="Unowned" transform="translate(186,417)"/></g>
+    <g id="SCParis" title="Paris"><use xlink:href="#sc" class="Unowned" transform="translate(173,334)"/></g>
+    <g id="SCBrest" title="Brest"><use xlink:href="#sc" class="Unowned" transform="translate(106,322)"/></g>
+    <g id="SCBelgium" title="Belgium"><use xlink:href="#sc" class="Unowned" transform="translate(186,305)"/></g>
+    <g id="SCHolland" title="Holland"><use xlink:href="#sc" class="Unowned" transform="translate(205,284)"/></g>
+    <g id="SCLondon" title="London"><use xlink:href="#sc" class="Unowned" transform="translate(162,290)"/></g>
+    <g id="SCLiverpool" title="Liverpool"><use xlink:href="#sc" class="Unowned" transform="translate(144,257)"/></g>
+    <g id="SCEdinburgh" title="Edinburgh"><use xlink:href="#sc" class="Unowned" transform="translate(154,219)"/></g>
+
+    <!--
+    <g title="Vienna"><use xlink:href="#A" id="Vienna" class="Austria" transform="translate(&Vienna;)"/></g>
+    <g title="Budapest"><use xlink:href="#A" id="Budapest" class="Austria" transform="translate(&Budapest;)"/></g>
+    <g title="Trieste"><use xlink:href="#F" id="Trieste" class="Austria" transform="translate(&Trieste;)"/></g>
+    <g title="London"><use xlink:href="#F" id="London" class="England" transform="translate(&London;)"/></g>
+    <g title="Edinburgh"><use xlink:href="#F" id="Edinburgh" class="England" transform="translate(&Edinburgh;)"/></g>
+    <g title="Liverpool"><use xlink:href="#A" id="Liverpool" class="England" transform="translate(&Liverpool;)"/></g>
+    <g title="Paris"><use xlink:href="#A" id="Paris" class="France" transform="translate(&Paris;)"/></g>
+    <g title="Marseilles"><use xlink:href="#A" id="Marseilles" class="France" transform="translate(&Marseilles;)"/></g>
+    <g title="Brest"><use xlink:href="#F" id="Brest" class="France" transform="translate(&Brest;)"/></g>
+    <g title="Berlin"><use xlink:href="#A" id="Berlin" class="Germany" transform="translate(&Berlin;)"/></g>
+    <g title="Munich"><use xlink:href="#A" id="Munich" class="Germany" transform="translate(&Munich;)"/></g>
+    <g title="Kiel"><use xlink:href="#F" id="Kiel" class="Germany" transform="translate(&Kiel;)"/></g>
+    <g title="Rome"><use xlink:href="#A" id="Rome" class="Italy" transform="translate(&Rome;)"/></g>
+    <g title="Venice"><use xlink:href="#A" id="Venice" class="Italy" transform="translate(&Venice;)"/></g>
+    <g title="Naples"><use xlink:href="#F" id="Naples" class="Italy" transform="translate(&Naples;)"/></g>
+    <g title="Moscow"><use xlink:href="#A" id="Moscow" class="Russia" transform="translate(&Moscow;)"/></g>
+    <g title="Warsaw"><use xlink:href="#A" id="Warsaw" class="Russia" transform="translate(&Warsaw;)"/></g>
+    <g title="St Petersburg (sc)"><use xlink:href="#F" id="St_Petersburg" class="Russia" transform="translate(&St_Petersburg__sc;)"/></g>
+    <g title="Sevastopol"><use xlink:href="#F" id="Sevastopol" class="Russia" transform="translate(&Sevastopol;)"/></g>
+    <g title="Constantinople"><use xlink:href="#A" id="Constantinople" class="Turkey" transform="translate(&Constantinople;)"/></g>
+    <g title="Smyrna"><use xlink:href="#A" id="Smyrna" class="Turkey" transform="translate(&Smyrna;)"/></g>
+    <g title="Ankara"><use xlink:href="#F" id="Ankara" class="Turkey" transform="translate(&Ankara;)"/></g>
+    -->
+    </svg>
+
+  </body>
+
+</html>
diff --git a/diplomacy-server.cabal b/diplomacy-server.cabal
new file mode 100644
--- /dev/null
+++ b/diplomacy-server.cabal
@@ -0,0 +1,84 @@
+-- Initial diplomacy-server.cabal generated by cabal init.  For further 
+-- documentation, see http://haskell.org/cabal/users-guide/
+
+name:                diplomacy-server
+version:             0.1.0.0
+synopsis:            Play Diplomacy over HTTP
+-- description:         
+homepage:            https://github.com/avieth/diplomacy-server
+license:             BSD3
+license-file:        LICENSE
+author:              Alexander Vieth
+maintainer:          aovieth@gmail.com
+-- copyright:           
+-- category:            
+build-type:          Simple
+extra-source-files:  README.md
+cabal-version:       >=1.10
+
+extra-source-files: ./client.html
+
+executable diplomacy-server
+  main-is:             Main.hs
+  Hs-Source-Dirs:      ./
+  Other-Modules:       Types.Credentials
+                     , Types.GameId
+                     , Types.Server
+                     , Types.GameState
+                     , Types.GreatPower
+                     , Types.Order
+                     , Types.ServerOptions
+                     , Types.UserData
+                     , Types.Unit
+                     , Resources.Advance
+                     , Resources.Client
+                     , Resources.Game
+                     , Resources.Game.Create
+                     , Resources.Game.Remove
+                     , Resources.Join
+                     , Resources.Metadata
+                     , Resources.Order
+                     , Resources.Pause
+                     , Resources.Resolution
+                     , Resources.Start
+                     , Router
+  -- other-modules:       
+  other-extensions:    AutoDeriveTypeable
+                     , RankNTypes
+                     , GADTs
+                     , DeriveGeneric
+                     , StandaloneDeriving
+                     , DataKinds
+                     , ScopedTypeVariables
+                     , FlexibleContexts
+                     , OverloadedStrings
+                     , FlexibleInstances
+                     , KindSignatures
+
+  build-depends:       base >=4.7 && <4.8
+                     , async >=2.0 && <2.1
+                     , stm >=2.4 && <2.5
+                     , transformers >=0.3 && <0.4
+                     , containers >=0.5 && <0.6
+                     , diplomacy >=0.1 && <0.2
+                     , TypeNat >=0.4 && <0.5
+                     , hourglass >=0.2 && <0.3
+                     , rest-core >=0.36 && <0.37
+                     , rest-wai >=0.1 && <0.2
+                     , wai >=3.0 && <3.1
+                     , warp >=3.0 && <3.1
+                     , warp-tls >=3.0 && <3.1
+                     , optparse-applicative >=0.11 && <0.12
+                     , transformers-compat >=0.4 && <0.5
+                     , bytestring >=0.10 && <0.11
+                     , aeson >=0.9 && <0.10
+                     , json-schema >=0.7 && <0.8
+                     , mtl >=2.1 && <2.2
+                     , Stream >=0.4 && <0.5
+                     , deepseq >=1.3 && <1.4
+                     , text >=1.2 && <1.3
+                     , filepath >=1.3 && <1.4
+                     , random >=1.1 && <1.2
+                     , parsec >=3.1 && <3.2
+  -- hs-source-dirs:      
+  default-language:    Haskell2010
