diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,6 +7,22 @@
 
 ### Upcoming
 
+### v1.5.0.0
+
+*Major*
+
+* Changed name of Spiteful Villagers variant to Spiteful village. ([#242](https://github.com/hjwylde/werewolf/issues/242))
+
+*Minor*
+
+* Added in a `variants` help subcommand. ([#227](https://github.com/hjwylde/werewolf/issues/227))
+
+*Revisions*
+
+* Fixed Jester lynched message to be gender neutral. ([#248](https://github.com/hjwylde/werewolf/issues/248))
+* Fixed Medusa description to be gender neutral. ([#247](https://github.com/hjwylde/werewolf/issues/247))
+* Pulled variant information out into external text files. ([#242](https://github.com/hjwylde/werewolf/issues/242))
+
 ### v1.4.0.1
 
 *Revisions*
diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -94,7 +94,7 @@
 * Standard
 * No role knowledge (players are not informed of the roles in play at the start of a game)
 * No role reveal (player roles are not revealed upon death)
-* Spiteful Villagers (the default Villager role becomes the Spiteful Villager)
+* Spiteful village (the default Villager role becomes the Spiteful Villager)
 
 ### Installing
 
diff --git a/app/Game/Werewolf/Message/Command.hs b/app/Game/Werewolf/Message/Command.hs
--- a/app/Game/Werewolf/Message/Command.hs
+++ b/app/Game/Werewolf/Message/Command.hs
@@ -31,7 +31,7 @@
     necromancerCommandsMessage, oracleCommandsMessage, orphanCommandsMessage,
     protectorCommandsMessage, roleMessage, rulesMessage, scapegoatCommandsMessage,
     seerCommandsMessage, stagesMessage, standardCommandsMessage, statusCommandsMessage,
-    witchCommandsMessage,
+    variantMessage, witchCommandsMessage,
 
     -- * Ping
     pingDiurnalRoleMessage, pingNocturnalRoleMessage, pingPlayerMessage, pingVillageMessage,
@@ -64,14 +64,15 @@
 import Game.Werewolf.Player
 import Game.Werewolf.Response
 import Game.Werewolf.Role                            hiding (name)
+import Game.Werewolf.Variant                         hiding (name)
 import Game.Werewolf.Variant.NoRoleKnowledge.Command as NoRoleKnowledge
 import Game.Werewolf.Variant.NoRoleReveal.Command    as NoRoleReveal
 import Game.Werewolf.Variant.Standard.Command        as Standard
 
 playerQuitMessage :: Player -> Game -> Message
 playerQuitMessage player game
-    | has (variant . _NoRoleReveal) game    = publicMessage $ NoRoleReveal.callerQuitText player
-    | otherwise                             = publicMessage $ Standard.callerQuitText player
+    | has (variant . noRoleReveal) game = publicMessage $ NoRoleReveal.callerQuitText player
+    | otherwise                         = publicMessage $ Standard.callerQuitText player
 
 playerVotedToBootMessage :: Player -> Player -> Message
 playerVotedToBootMessage caller = publicMessage . callerVotedBootText caller
@@ -81,8 +82,8 @@
 
 playerShotMessage :: Player -> Game -> Message
 playerShotMessage player game
-    | has (variant . _NoRoleReveal) game    = publicMessage $ NoRoleReveal.playerShotText player
-    | otherwise                             = publicMessage $ Standard.playerShotText player
+    | has (variant . noRoleReveal) game = publicMessage $ NoRoleReveal.playerShotText player
+    | otherwise                         = publicMessage $ Standard.playerShotText player
 
 gameEndedMessage :: Text -> Message
 gameEndedMessage = publicMessage . gameEndedText
@@ -161,6 +162,9 @@
 statusCommandsMessage :: Text -> Message
 statusCommandsMessage to = privateMessage to statusCommandsText
 
+variantMessage :: Text -> Variant -> Message
+variantMessage to = privateMessage to . variantDescriptionText
+
 witchCommandsMessage :: Text -> Message
 witchCommandsMessage to = privateMessage to witchCommandsText
 
@@ -169,8 +173,8 @@
 
 pingNocturnalRoleMessage :: Role -> Game -> Message
 pingNocturnalRoleMessage role game
-    | has (variant . _NoRoleKnowledge) game = publicMessage $ NoRoleKnowledge.nocturnalRolePingedText role
-    | has (variant . _NoRoleReveal) game    = publicMessage $ NoRoleReveal.nocturnalRolePingedText role
+    | has (variant . noRoleKnowledge) game  = publicMessage $ NoRoleKnowledge.nocturnalRolePingedText role
+    | has (variant . noRoleReveal) game     = publicMessage $ NoRoleReveal.nocturnalRolePingedText role
     | otherwise                             = publicMessage $ Standard.nocturnalRolePingedText role
 
 pingPlayerMessage :: Text -> Message
@@ -181,8 +185,8 @@
 
 pingWerewolvesMessage :: Game -> Message
 pingWerewolvesMessage game
-    | has (variant . _NoRoleKnowledge) game = publicMessage NoRoleKnowledge.werewolvesPingedText
-    | has (variant . _NoRoleReveal) game    = publicMessage NoRoleReveal.werewolvesPingedText
+    | has (variant . noRoleKnowledge) game  = publicMessage NoRoleKnowledge.werewolvesPingedText
+    | has (variant . noRoleReveal) game     = publicMessage NoRoleReveal.werewolvesPingedText
     | otherwise                             = publicMessage Standard.werewolvesPingedText
 
 deadRaisedMessages :: Game -> [Message]
@@ -203,8 +207,8 @@
 
 currentNocturnalTurnMessage :: Text -> Game -> Message
 currentNocturnalTurnMessage to game
-    | has (variant . _NoRoleKnowledge) game = privateMessage to $ NoRoleKnowledge.currentNocturnalTurnText game
-    | has (variant . _NoRoleReveal) game    = privateMessage to $ NoRoleReveal.currentNocturnalTurnText game
+    | has (variant . noRoleKnowledge) game  = privateMessage to $ NoRoleKnowledge.currentNocturnalTurnText game
+    | has (variant . noRoleReveal) game     = privateMessage to $ NoRoleReveal.currentNocturnalTurnText game
     | otherwise                             = privateMessage to $ Standard.currentNocturnalTurnText game
 
 gameIsOverMessage :: Text -> Message
@@ -219,7 +223,7 @@
         alivePlayersText' = alivePlayersText game
         deadPlayersText'
             | hasn't (players . traverse . dead) game   = T.empty
-            | has (variant . _NoRoleReveal) game        = NoRoleReveal.deadPlayersText game
+            | has (variant . noRoleReveal) game         = NoRoleReveal.deadPlayersText game
             | otherwise                                 = Standard.deadPlayersText game
 
 playerRescindedVoteMessage :: Text -> Player -> Message
diff --git a/app/Game/Werewolf/Message/Engine.hs b/app/Game/Werewolf/Message/Engine.hs
--- a/app/Game/Werewolf/Message/Engine.hs
+++ b/app/Game/Werewolf/Message/Engine.hs
@@ -35,14 +35,15 @@
 import Game.Werewolf.Player
 import Game.Werewolf.Response
 import Game.Werewolf.Role                           hiding (name)
+import Game.Werewolf.Variant                        hiding (name)
 import Game.Werewolf.Variant.NoRoleKnowledge.Engine as NoRoleKnowledge
 import Game.Werewolf.Variant.NoRoleReveal.Engine    as NoRoleReveal
 import Game.Werewolf.Variant.Standard.Engine        as Standard
 
 playerBootedMessage :: Player -> Game -> Message
 playerBootedMessage player game
-    | has (variant . _NoRoleReveal) game    = publicMessage $ NoRoleReveal.playerBootedText player
-    | otherwise                             = publicMessage $ Standard.playerBootedText player
+    | has (variant . noRoleReveal) game = publicMessage $ NoRoleReveal.playerBootedText player
+    | otherwise                         = publicMessage $ Standard.playerBootedText player
 
 gameOverMessages :: Game -> [Message]
 gameOverMessages game
@@ -127,7 +128,7 @@
         players'                = game ^. players
         gameVariantMessages     =
             [ gameVariantMessage game
-            | hasn't (variant . _Standard) game
+            | hasn't (variant . standard) game
             ]
         beholderMessages        =
             [ beholderMessage beholderName game
@@ -153,7 +154,7 @@
 
 rolesInGameMessage :: Maybe Text -> Game -> Message
 rolesInGameMessage mTo game
-    | has (variant . _NoRoleKnowledge) game = Message mTo $ NoRoleKnowledge.rolesInGameText game
+    | has (variant . noRoleKnowledge) game  = Message mTo $ NoRoleKnowledge.rolesInGameText game
     | otherwise                             = Message mTo $ Standard.rolesInGameText game
 
 newPlayerMessage :: Player -> Message
@@ -167,8 +168,8 @@
 
 spitefulVillagerKilledMessage :: Text -> Game -> Message
 spitefulVillagerKilledMessage to game
-    | has (variant . _NoRoleReveal) game    = privateMessage to $ NoRoleReveal.spitefulVillagerKilledText game
-    | otherwise                             = privateMessage to $ Standard.spitefulVillagerKilledText game
+    | has (variant . noRoleReveal) game = privateMessage to $ NoRoleReveal.spitefulVillagerKilledText game
+    | otherwise                         = privateMessage to $ Standard.spitefulVillagerKilledText game
 
 trueVillagerMessage :: Game -> Message
 trueVillagerMessage = publicMessage . trueVillagerText
@@ -213,9 +214,9 @@
 
 necromancersTurnMessages :: Text -> Game -> [Message]
 necromancersTurnMessages to game
-    | has (variant . _NoRoleKnowledge) game =
+    | has (variant . noRoleKnowledge) game  =
         [ privateMessage to necromancersTurnPrivateText ]
-    | has (variant . _NoRoleReveal) game    =
+    | has (variant . noRoleReveal) game     =
         [ privateMessage to necromancersTurnPrivateText ]
     | otherwise                             =
         [ publicMessage necromancersTurnPublicText
@@ -224,9 +225,9 @@
 
 oraclesTurnMessages :: Text -> Game -> [Message]
 oraclesTurnMessages to game
-    | has (variant . _NoRoleKnowledge) game =
+    | has (variant . noRoleKnowledge) game  =
         [ privateMessage to oraclesTurnPrivateText ]
-    | has (variant . _NoRoleReveal) game    =
+    | has (variant . noRoleReveal) game     =
         [ privateMessage to oraclesTurnPrivateText ]
     | otherwise                             =
         [ publicMessage oraclesTurnPublicText
@@ -235,9 +236,9 @@
 
 orphansTurnMessages :: Text -> Game -> [Message]
 orphansTurnMessages to game
-    | has (variant . _NoRoleKnowledge) game =
+    | has (variant . noRoleKnowledge) game  =
         [ privateMessage to orphansTurnPrivateText ]
-    | has (variant . _NoRoleReveal) game    =
+    | has (variant . noRoleReveal) game     =
         [ privateMessage to orphansTurnPrivateText ]
     | otherwise                             =
         [ publicMessage orphansTurnPublicText
@@ -246,9 +247,9 @@
 
 protectorsTurnMessages :: Text -> Game -> [Message]
 protectorsTurnMessages to game
-    | has (variant . _NoRoleKnowledge) game =
+    | has (variant . noRoleKnowledge) game  =
         [ privateMessage to protectorsTurnPrivateText ]
-    | has (variant . _NoRoleReveal) game    =
+    | has (variant . noRoleReveal) game     =
         [ privateMessage to protectorsTurnPrivateText ]
     | otherwise                             =
         [ publicMessage protectorsTurnPublicText
@@ -260,9 +261,9 @@
 
 seersTurnMessages :: Text -> Game -> [Message]
 seersTurnMessages to game
-    | has (variant . _NoRoleKnowledge) game =
+    | has (variant . noRoleKnowledge) game  =
         [ privateMessage to seersTurnPrivateText ]
-    | has (variant . _NoRoleReveal) game    =
+    | has (variant . noRoleReveal) game     =
         [ privateMessage to seersTurnPrivateText ]
     | otherwise                             =
         [ publicMessage seersTurnPublicText
@@ -271,8 +272,8 @@
 
 villageDrunksTurnMessages :: Game -> [Message]
 villageDrunksTurnMessages game
-    | has (variant . _NoRoleKnowledge) game = []
-    | has (variant . _NoRoleReveal) game    = []
+    | has (variant . noRoleKnowledge) game  = []
+    | has (variant . noRoleReveal) game     = []
     | otherwise                             = [publicMessage villageDrunksTurnText]
 
 sunriseMessage :: Message
@@ -294,9 +295,9 @@
 
 werewolvesTurnMessages :: [Text] -> Game -> [Message]
 werewolvesTurnMessages tos game
-    | has (variant . _NoRoleKnowledge) game =
+    | has (variant . noRoleKnowledge) game  =
         groupMessages tos werewolvesTurnPrivateText
-    | has (variant . _NoRoleReveal) game    =
+    | has (variant . noRoleReveal) game     =
         groupMessages tos werewolvesTurnPrivateText
     | otherwise                             =
         publicMessage werewolvesTurnPublicText
@@ -312,8 +313,8 @@
     where
         to              = game ^?! players . witches . name
         wakeUpMessages
-            | has (variant . _NoRoleKnowledge) game = []
-            | has (variant . _NoRoleReveal) game    = []
+            | has (variant . noRoleKnowledge) game  = []
+            | has (variant . noRoleReveal) game     = []
             | otherwise                             = [publicMessage witchsTurnText]
         passMessage     = privateMessage to passText
         healMessages
@@ -355,18 +356,18 @@
 
 werewolfLynchedMessage :: Player -> Game -> Message
 werewolfLynchedMessage player game
-    | has (variant . _NoRoleReveal) game    = publicMessage $ NoRoleReveal.playerLynchedText player
-    | otherwise                             = publicMessage $ Standard.werewolfLynchedText player
+    | has (variant . noRoleReveal) game = publicMessage $ NoRoleReveal.playerLynchedText player
+    | otherwise                         = publicMessage $ Standard.werewolfLynchedText player
 
 playerLynchedMessage :: Player -> Game -> Message
 playerLynchedMessage player game
-    | has (variant . _NoRoleReveal) game    = publicMessage $ NoRoleReveal.playerLynchedText player
-    | otherwise                             = publicMessage $ Standard.playerLynchedText player
+    | has (variant . noRoleReveal) game = publicMessage $ NoRoleReveal.playerLynchedText player
+    | otherwise                         = publicMessage $ Standard.playerLynchedText player
 
 saintLynchedMessage :: [Player] -> Game -> Message
 saintLynchedMessage voters game
-    | has (variant . _NoRoleReveal) game    = publicMessage $ NoRoleReveal.saintLynchedText voters
-    | otherwise                             = publicMessage $ Standard.saintLynchedText voters
+    | has (variant . noRoleReveal) game = publicMessage $ NoRoleReveal.saintLynchedText voters
+    | otherwise                         = publicMessage $ Standard.saintLynchedText voters
 
 scapegoatLynchedMessage :: Game -> Message
 scapegoatLynchedMessage = publicMessage . scapegoatLynchedText
@@ -379,16 +380,16 @@
 
 playerDevouredMessage :: Player -> Game -> Message
 playerDevouredMessage player game
-    | has (variant . _NoRoleReveal) game    = publicMessage $ NoRoleReveal.playerDevouredText player
-    | otherwise                             = publicMessage $ Standard.playerDevouredText player
+    | has (variant . noRoleReveal) game = publicMessage $ NoRoleReveal.playerDevouredText player
+    | otherwise                         = publicMessage $ Standard.playerDevouredText player
 
 playerDivinedMessage :: Text -> Player -> Message
 playerDivinedMessage to = privateMessage to . playerDivinedText
 
 playerPoisonedMessage :: Player -> Game -> Message
 playerPoisonedMessage player game
-    | has (variant . _NoRoleReveal) game    = publicMessage $ NoRoleReveal.playerPoisonedText player
-    | otherwise                             = publicMessage $ Standard.playerPoisonedText player
+    | has (variant . noRoleReveal) game = publicMessage $ NoRoleReveal.playerPoisonedText player
+    | otherwise                         = publicMessage $ Standard.playerPoisonedText player
 
 playerSeenMessage :: Text -> Player -> Message
 playerSeenMessage to player
@@ -398,8 +399,8 @@
 
 playerTurnedToStoneMessage :: Player -> Game -> Message
 playerTurnedToStoneMessage player game
-    | has (variant . _NoRoleReveal) game    = publicMessage $ NoRoleReveal.playerTurnedToStoneText player
-    | otherwise                             = publicMessage $ Standard.playerTurnedToStoneText player
+    | has (variant . noRoleReveal) game = publicMessage $ NoRoleReveal.playerTurnedToStoneText player
+    | otherwise                         = publicMessage $ Standard.playerTurnedToStoneText player
 
 villageDrunkJoinedVillageMessage :: Text -> Message
 villageDrunkJoinedVillageMessage to = privateMessage to villageDrunkJoinedVillageText
diff --git a/app/Game/Werewolf/Message/Error.hs b/app/Game/Werewolf/Message/Error.hs
--- a/app/Game/Werewolf/Message/Error.hs
+++ b/app/Game/Werewolf/Message/Error.hs
@@ -35,7 +35,7 @@
 
     -- ** Start
     gameAlreadyRunningMessage, mustHaveAtLeast7PlayersMessage, playerNamesMustBeUniqueMessage,
-    roleCountRestrictedMessage, roleDoesNotExistMessage,
+    roleCountRestrictedMessage, roleDoesNotExistMessage, variantDoesNotExistMessage,
 
     -- ** Unvote
     playerHasNotVotedMessage,
@@ -107,6 +107,9 @@
 
 roleDoesNotExistMessage :: Text -> Text -> Message
 roleDoesNotExistMessage to = privateMessage to . roleDoesNotExistText
+
+variantDoesNotExistMessage :: Text -> Text -> Message
+variantDoesNotExistMessage to = privateMessage to . variantDoesNotExistText
 
 playerHasNotVotedMessage :: Text -> Message
 playerHasNotVotedMessage to = privateMessage to callerNotVotedText
diff --git a/app/Game/Werewolf/Variant/Standard/Command.hs b/app/Game/Werewolf/Variant/Standard/Command.hs
--- a/app/Game/Werewolf/Variant/Standard/Command.hs
+++ b/app/Game/Werewolf/Variant/Standard/Command.hs
@@ -32,8 +32,8 @@
     oracleCommandsText, oraclesTurnText, orphanCommandsText, orphansTurnText, protectorCommandsText,
     protectorsTurnText, roleDescriptionText, scapegoatCommandsText, scapegoatsTurnText,
     seerCommandsText, seersTurnText, standardCommandsText, standardCycleText, statusCommandsText,
-    sunriseText, sunsetText, villageDrunksTurnText, villagesTurnText, werewolvesTurnText,
-    winConditionText, witchCommandsText, witchsTurnText,
+    sunriseText, sunsetText, variantDescriptionText, villageDrunksTurnText, villagesTurnText,
+    werewolvesTurnText, winConditionText, witchCommandsText, witchsTurnText,
 
     -- * Ping
     diurnalRolePingedText, nocturnalRolePingedText, playerPingedText, villagePingedText,
@@ -67,8 +67,9 @@
 import qualified Data.Text                     as T
 import           Data.Version
 
-import Game.Werewolf
-import Game.Werewolf.Message
+import           Game.Werewolf
+import           Game.Werewolf.Message
+import qualified Game.Werewolf.Variant as Variant
 
 import Werewolf.Version
 
@@ -158,6 +159,9 @@
 
 sunsetText :: Text
 sunsetText = [iFile|variant/standard/command/help/sunset.txt|]
+
+variantDescriptionText :: Variant -> Text
+variantDescriptionText variant = [iFile|variant/standard/command/help/variant.txt|]
 
 villageDrunksTurnText :: Text
 villageDrunksTurnText = [iFile|variant/standard/command/help/village-drunks-turn.txt|]
diff --git a/app/Game/Werewolf/Variant/Standard/Error.hs b/app/Game/Werewolf/Variant/Standard/Error.hs
--- a/app/Game/Werewolf/Variant/Standard/Error.hs
+++ b/app/Game/Werewolf/Variant/Standard/Error.hs
@@ -37,7 +37,7 @@
 
     -- ** Start
     gameAlreadyRunningText, playerCountTooLowText, playerNamesNotUniqueText,
-    roleCountRestrictedText, roleDoesNotExistText,
+    roleCountRestrictedText, roleDoesNotExistText, variantDoesNotExistText,
 
     -- ** Unvote
     callerNotVotedText,
@@ -108,6 +108,9 @@
 
 roleDoesNotExistText :: Text -> Text
 roleDoesNotExistText roleName = [iFile|variant/standard/error/command/start/role-does-not-exist.txt|]
+
+variantDoesNotExistText :: Text -> Text
+variantDoesNotExistText variantName = [iFile|variant/standard/error/command/start/variant-does-not-exist.txt|]
 
 callerNotVotedText :: Text
 callerNotVotedText = [iFile|variant/standard/error/command/unvote/caller-not-voted.txt|]
diff --git a/app/Werewolf/Command/Help.hs b/app/Werewolf/Command/Help.hs
--- a/app/Werewolf/Command/Help.hs
+++ b/app/Werewolf/Command/Help.hs
@@ -42,6 +42,8 @@
     { optAll :: Bool
     } | Roles
     { optAll :: Bool
+    } | Variants
+    { optAll :: Bool
     } deriving (Eq, Show)
 
 handle :: MonadIO m => Text -> Text -> Options -> m ()
@@ -53,9 +55,9 @@
         }
 handle callerName tag (Options (Just (Roles optAll))) = do
     mGame       <- getGame tag optAll
-    let mGame'  = mGame >>= \game -> case game ^. variant of
-            NoRoleKnowledge -> Nothing
-            _               -> Just game
+    let mGame'  = mGame >>= \game -> if has (variant . noRoleKnowledge) game
+            then Nothing
+            else Just game
 
     let roles' = sortBy (compare `on` humanise) . nub $ maybe allRoles (toListOf $ players . roles) mGame'
 
@@ -64,9 +66,9 @@
         }
 handle callerName tag (Options (Just (Rules optAll))) = do
     mGame       <- getGame tag optAll
-    let mGame'  = mGame >>= \game -> case game ^. variant of
-            NoRoleKnowledge -> Nothing
-            _               -> Just game
+    let mGame'  = mGame >>= \game -> if has (variant . noRoleKnowledge) game
+            then Nothing
+            else Just game
 
     exitWith success
         { messages = rulesMessages callerName mGame'
@@ -74,6 +76,16 @@
 handle callerName _ (Options Nothing) = exitWith success
     { messages = helpMessages callerName
     }
+handle callerName tag (Options (Just (Variants optAll))) = do
+    mGame <- getGame tag optAll
+
+    let variants = sortBy (compare `on` humanise) $ case mGame of
+            Just game   -> [game ^. variant]
+            Nothing     -> allVariants
+
+    exitWith success
+        { messages = map (variantMessage callerName) variants
+        }
 
 commandsMessages :: Text -> Maybe Game -> [Message]
 commandsMessages callerName mGame =
diff --git a/app/Werewolf/Command/Start.hs b/app/Werewolf/Command/Start.hs
--- a/app/Werewolf/Command/Start.hs
+++ b/app/Werewolf/Command/Start.hs
@@ -29,9 +29,10 @@
 
 import Data.Text (Text)
 
-import Game.Werewolf
-import Game.Werewolf.Engine
-import Game.Werewolf.Message.Error
+import           Game.Werewolf
+import           Game.Werewolf.Engine
+import           Game.Werewolf.Message.Error
+import qualified Game.Werewolf.Variant       as Variant
 
 import System.Random.Shuffle
 
@@ -39,7 +40,7 @@
 
 data Options = Options
     { optExtraRoles :: ExtraRoles
-    , optVariant    :: Variant
+    , optVariant    :: Text
     , argPlayers    :: [Text]
     } deriving (Eq, Show)
 
@@ -58,12 +59,14 @@
             Random          -> randomExtraRoles $ length playerNames
             Use roleNames   -> useExtraRoles callerName roleNames
 
-        let defaultVillagerRole = if variant == SpitefulVillagers then spitefulVillagerRole else simpleVillagerRole
+        variant' <- useVariant callerName variant
+
+        let defaultVillagerRole = if is spitefulVillage variant' then spitefulVillagerRole else simpleVillagerRole
         let roles = padRoles extraRoles' (length playerNames + 1) defaultVillagerRole
 
         players <- createPlayers (callerName:playerNames) <$> shuffleM roles
 
-        runWriterT $ startGame callerName variant players >>= execStateT checkStage
+        runWriterT $ startGame callerName variant' players >>= execStateT checkStage
 
     case result of
         Left errorMessages      -> exitWith failure { messages = errorMessages }
@@ -79,12 +82,20 @@
     take count <$> shuffleM restrictedRoles
 
 useExtraRoles :: MonadError [Message] m => Text -> [Text] -> m [Role]
-useExtraRoles callerName roleNames = forM roleNames $ \roleName -> case findByTag roleName of
+useExtraRoles callerName roleNames = forM roleNames $ \roleName -> case findRoleByTag roleName of
     Just role   -> return role
     Nothing     -> throwError [roleDoesNotExistMessage callerName roleName]
 
-findByTag :: Text -> Maybe Role
-findByTag tag' = restrictedRoles ^? traverse . filteredBy tag tag'
+findRoleByTag :: Text -> Maybe Role
+findRoleByTag tag' = restrictedRoles ^? traverse . filteredBy tag tag'
+
+useVariant :: MonadError [Message] m => Text -> Text -> m Variant
+useVariant callerName variantName = case findVariantByTag variantName of
+    Just variant   -> return variant
+    Nothing         -> throwError [variantDoesNotExistMessage callerName variantName]
+
+findVariantByTag :: Text -> Maybe Variant
+findVariantByTag tag' = allVariants ^? traverse . filteredBy Variant.tag tag'
 
 padRoles :: [Role] -> Int -> Role -> [Role]
 padRoles roles n defaultVillagerRole = roles ++ villagerRoles ++ simpleWerewolfRoles
diff --git a/app/Werewolf/Options.hs b/app/Werewolf/Options.hs
--- a/app/Werewolf/Options.hs
+++ b/app/Werewolf/Options.hs
@@ -19,14 +19,15 @@
     werewolfPrefs, werewolfInfo, werewolf,
 ) where
 
+import Control.Lens
+
 import           Data.Text    (Text)
 import qualified Data.Text    as T
 import           Data.Version (showVersion)
 
-import Game.Werewolf (Variant (..))
+import Game.Werewolf.Variant
 
 import Options.Applicative
-import Options.Applicative.Types
 
 import qualified Werewolf.Command.Boot      as Boot
 import qualified Werewolf.Command.Choose    as Choose
@@ -149,6 +150,7 @@
         [ command "commands"    $ info (Help.Commands <$> allOption)    (fullDesc <> progDesc "Print the in-game commands")
         , command "roles"       $ info (Help.Roles <$> allOption)       (fullDesc <> progDesc "Print the roles and their descriptions")
         , command "rules"       $ info (Help.Rules <$> allOption)       (fullDesc <> progDesc "Print the game rules")
+        , command "variants"    $ info (Help.Variants <$> allOption)    (fullDesc <> progDesc "Print the variants and their descriptions")
         ])
     where
         allOption = switch $ long "all" <> short 'a'
@@ -181,20 +183,13 @@
 start :: Parser Command
 start = fmap Start $ Start.Options
     <$> (extraRolesOption <|> randomExtraRolesOption)
-    <*> variantOption (mconcat [
+    <*> fmap T.pack (strOption $ mconcat [
         long "variant", short 'v', metavar "VARIANT",
-        value Standard, showDefaultWith $ const "standard",
+        value standardVariantTag, showDefaultWith $ const standardVariantTag,
         help "Specify the game variant"
         ])
     <*> some (T.pack <$> strArgument (metavar "PLAYER..."))
     where
-        variantOption = option $ readerAsk >>= \opt -> case opt of
-            "standard"              -> return Standard
-            "no-role-knowledge"     -> return NoRoleKnowledge
-            "no-role-reveal"        -> return NoRoleReveal
-            "spiteful-villagers"    -> return SpitefulVillagers
-            _                       -> readerError $ "unrecognised variant `" ++ opt ++ "'"
-
         extraRolesOption = fmap (Start.Use . filter (/= T.empty) . T.splitOn "," . T.pack) (strOption $ mconcat
             [ long "extra-roles", short 'e', metavar "ROLE,..."
             , value []
@@ -205,6 +200,8 @@
             [ long "random-extra-roles", short 'r'
             , help "Use random extra roles"
             ]
+
+        standardVariantTag = T.unpack $ standardVariant ^. tag
 
 status :: Parser Command
 status = pure Status
diff --git a/src/Game/Werewolf.hs b/src/Game/Werewolf.hs
--- a/src/Game/Werewolf.hs
+++ b/src/Game/Werewolf.hs
@@ -12,9 +12,10 @@
 * "Game.Werewolf.Player"
 * "Game.Werewolf.Response"
 * "Game.Werewolf.Role"
+* "Game.Werewolf.Variant"
 
-N.B., where clashes are found between "Game.Werewolf.Player" and "Game.Werewolf.Role", the
-"Game.Werewolf.Player" functions are preferred.
+N.B., where clashes are found between "Game.Werewolf.Player", "Game.Werewolf.Role" and
+"Game.Werewolf.Variant", the "Game.Werewolf.Player" functions are preferred.
 -}
 
 module Game.Werewolf (
@@ -25,3 +26,4 @@
 import Game.Werewolf.Player   as Werewolf
 import Game.Werewolf.Response as Werewolf
 import Game.Werewolf.Role     as Werewolf hiding (activity, name)
+import Game.Werewolf.Variant  as Werewolf hiding (description, name, tag)
diff --git a/src/Game/Werewolf/Game.hs b/src/Game/Werewolf/Game.hs
--- a/src/Game/Werewolf/Game.hs
+++ b/src/Game/Werewolf/Game.hs
@@ -22,9 +22,6 @@
     healUsed, hunterRetaliated, jesterRevealed, marks, passed, poison, poisonUsed, priorProtect,
     protect, roleModel, scapegoatBlamed, see, votes,
 
-    Variant(..),
-    _Standard, _NoRoleKnowledge, _NoRoleReveal,
-
     Stage(..),
     _DruidsTurn, _GameOver, _HuntersTurn1, _HuntersTurn2, _Lynching, _NecromancersTurn,
     _OraclesTurn, _OrphansTurn, _ProtectorsTurn, _ScapegoatsTurn, _SeersTurn, _Sunrise, _Sunset,
@@ -60,7 +57,8 @@
 import           Data.Text            (Text)
 
 import Game.Werewolf.Player
-import Game.Werewolf.Role   hiding (activity, name)
+import Game.Werewolf.Role    hiding (activity, name)
+import Game.Werewolf.Variant hiding (name)
 
 import Prelude hiding (round)
 
@@ -98,15 +96,6 @@
     , _votes              :: Map Text Text    -- ^ Villagers and Werewolves
     } deriving (Eq, Read, Show)
 
-data Variant = Standard | NoRoleKnowledge | NoRoleReveal | SpitefulVillagers
-    deriving (Eq, Read, Show)
-
-instance Humanise Variant where
-    humanise Standard           = "standard"
-    humanise NoRoleKnowledge    = "no role knowledge"
-    humanise NoRoleReveal       = "no role reveal"
-    humanise SpitefulVillagers  = "spiteful villagers"
-
 -- | Most of these are fairly self-explainable (the turn stages). 'Sunrise' and 'Sunset' are
 --   provided as meaningful breaks between the day and night as, for example, a 'VillagesTurn' may
 --   not always be available (curse that retched Scapegoat).
@@ -138,8 +127,6 @@
     humanise WitchsTurn         = "Witch's turn"
 
 makeLenses ''Game
-
-makePrisms ''Variant
 
 makePrisms ''Stage
 
diff --git a/src/Game/Werewolf/Player.hs b/src/Game/Werewolf/Player.hs
--- a/src/Game/Werewolf/Player.hs
+++ b/src/Game/Werewolf/Player.hs
@@ -295,7 +295,7 @@
 werewolf :: Traversal' Player ()
 werewolf = role . allegiance . _Werewolves
 
--- | This 'Traversal' provides the traversal of 'Player' names.
+-- | The traversal of 'Player' names.
 --
 -- @
 -- 'names' = 'traverse' . 'name'
@@ -303,7 +303,7 @@
 names :: Traversable t => Traversal' (t Player) Text
 names = traverse . name
 
--- | This 'Traversal' provides the traversal of 'Player' roles.
+-- | The traversal of 'Player' roles.
 --
 -- @
 -- 'roles' = 'traverse' . 'role'
@@ -311,7 +311,7 @@
 roles :: Traversable t => Traversal' (t Player) Role
 roles = traverse . role
 
--- | This 'Traversal' provides the traversal of 'Player' states.
+-- | The traversal of 'Player' states.
 --
 -- @
 -- 'states' = 'traverse' . 'state'
@@ -319,7 +319,7 @@
 states :: Traversable t => Traversal' (t Player) State
 states = traverse . state
 
--- | This 'Traversal' provides the traversal of 'Player's with the given name.
+-- | The traversal of 'Player's with the given name.
 --
 -- @
 -- 'named' name' = 'filteredBy' . 'name' name'
@@ -327,7 +327,7 @@
 named :: Text -> Traversal' Player Player
 named name' = filteredBy name name'
 
--- | This 'Traversal' provides the traversal of 'alphaWolf' 'Player's.
+-- | The traversal of 'alphaWolf' 'Player's.
 --
 -- @
 -- 'alphaWolves' = 'traverse' . 'filtered' ('is' 'alphaWolf')
@@ -335,7 +335,7 @@
 alphaWolves :: Traversable t => Traversal' (t Player) Player
 alphaWolves = traverse . filtered (is alphaWolf)
 
--- | This 'Traversal' provides the traversal of 'beholder' 'Player's.
+-- | The traversal of 'beholder' 'Player's.
 --
 -- @
 -- 'beholders' = 'traverse' . 'filtered' ('is' 'beholder')
@@ -343,7 +343,7 @@
 beholders :: Traversable t => Traversal' (t Player) Player
 beholders = traverse . filtered (is beholder)
 
--- | This 'Traversal' provides the traversal of 'crookedSenator' 'Player's.
+-- | The traversal of 'crookedSenator' 'Player's.
 --
 -- @
 -- 'crookedSenators' = 'traverse' . 'filtered' ('is' 'crookedSenator')
@@ -351,7 +351,7 @@
 crookedSenators :: Traversable t => Traversal' (t Player) Player
 crookedSenators = traverse . filtered (is crookedSenator)
 
--- | This 'Traversal' provides the traversal of 'dullahan' 'Player's.
+-- | The traversal of 'dullahan' 'Player's.
 --
 -- @
 -- 'dullahans' = 'traverse' . 'filtered' ('is' 'dullahan')
@@ -359,7 +359,7 @@
 dullahans :: Traversable t => Traversal' (t Player) Player
 dullahans = traverse . filtered (is dullahan)
 
--- | This 'Traversal' provides the traversal of 'druid' 'Player's.
+-- | The traversal of 'druid' 'Player's.
 --
 -- @
 -- 'druids' = 'traverse' . 'filtered' ('is' 'druid')
@@ -367,7 +367,7 @@
 druids :: Traversable t => Traversal' (t Player) Player
 druids = traverse . filtered (is druid)
 
--- | This 'Traversal' provides the traversal of 'fallenAngel' 'Player's.
+-- | The traversal of 'fallenAngel' 'Player's.
 --
 -- @
 -- 'fallenAngels' = 'traverse' . 'filtered' ('is' 'fallenAngel')
@@ -375,7 +375,7 @@
 fallenAngels :: Traversable t => Traversal' (t Player) Player
 fallenAngels = traverse . filtered (is fallenAngel)
 
--- | This 'Traversal' provides the traversal of 'hunter' 'Player's.
+-- | The traversal of 'hunter' 'Player's.
 --
 -- @
 -- 'hunters' = 'traverse' . 'filtered' ('is' 'hunter')
@@ -383,7 +383,7 @@
 hunters :: Traversable t => Traversal' (t Player) Player
 hunters = traverse . filtered (is hunter)
 
--- | This 'Traversal' provides the traversal of 'jester' 'Player's.
+-- | The traversal of 'jester' 'Player's.
 --
 -- @
 -- 'jesters' = 'traverse' . 'filtered' ('is' 'jester')
@@ -391,7 +391,7 @@
 jesters :: Traversable t => Traversal' (t Player) Player
 jesters = traverse . filtered (is jester)
 
--- | This 'Traversal' provides the traversal of 'lycan' 'Player's.
+-- | The traversal of 'lycan' 'Player's.
 --
 -- @
 -- 'lycans' = 'traverse' . 'filtered' ('is' 'lycan')
@@ -399,7 +399,7 @@
 lycans :: Traversable t => Traversal' (t Player) Player
 lycans = traverse . filtered (is lycan)
 
--- | This 'Traversal' provides the traversal of 'medusa' 'Player's.
+-- | The traversal of 'medusa' 'Player's.
 --
 -- @
 -- 'medusas' = 'traverse' . 'filtered' ('is' 'medusa')
@@ -407,7 +407,7 @@
 medusas :: Traversable t => Traversal' (t Player) Player
 medusas = traverse . filtered (is medusa)
 
--- | This 'Traversal' provides the traversal of 'necromancer' 'Player's.
+-- | The traversal of 'necromancer' 'Player's.
 --
 -- @
 -- 'necromancers' = 'traverse' . 'filtered' ('is' 'necromancer')
@@ -415,7 +415,7 @@
 necromancers :: Traversable t => Traversal' (t Player) Player
 necromancers = traverse . filtered (is necromancer)
 
--- | This 'Traversal' provides the traversal of 'oracle' 'Player's.
+-- | The traversal of 'oracle' 'Player's.
 --
 -- @
 -- 'oracles' = 'traverse' . 'filtered' ('is' 'oracle')
@@ -423,7 +423,7 @@
 oracles :: Traversable t => Traversal' (t Player) Player
 oracles = traverse . filtered (is oracle)
 
--- | This 'Traversal' provides the traversal of 'orphan' 'Player's.
+-- | The traversal of 'orphan' 'Player's.
 --
 -- @
 -- 'orphans' = 'traverse' . 'filtered' ('is' 'orphan')
@@ -431,7 +431,7 @@
 orphans :: Traversable t => Traversal' (t Player) Player
 orphans = traverse . filtered (is orphan)
 
--- | This 'Traversal' provides the traversal of 'protector' 'Player's.
+-- | The traversal of 'protector' 'Player's.
 --
 -- @
 -- 'protectors' = 'traverse' . 'filtered' ('is' 'protector')
@@ -439,7 +439,7 @@
 protectors :: Traversable t => Traversal' (t Player) Player
 protectors = traverse . filtered (is protector)
 
--- | This 'Traversal' provides the traversal of 'saint' 'Player's.
+-- | The traversal of 'saint' 'Player's.
 --
 -- @
 -- 'saints' = 'traverse' . 'filtered' ('is' 'saint')
@@ -447,7 +447,7 @@
 saints :: Traversable t => Traversal' (t Player) Player
 saints = traverse . filtered (is saint)
 
--- | This 'Traversal' provides the traversal of 'scapegoat' 'Player's.
+-- | The traversal of 'scapegoat' 'Player's.
 --
 -- @
 -- 'scapegoats' = 'traverse' . 'filtered' ('is' 'scapegoat')
@@ -455,7 +455,7 @@
 scapegoats :: Traversable t => Traversal' (t Player) Player
 scapegoats = traverse . filtered (is scapegoat)
 
--- | This 'Traversal' provides the traversal of 'seer' 'Player's.
+-- | The traversal of 'seer' 'Player's.
 --
 -- @
 -- 'seers' = 'traverse' . 'filtered' ('is' 'seer')
@@ -463,7 +463,7 @@
 seers :: Traversable t => Traversal' (t Player) Player
 seers = traverse . filtered (is seer)
 
--- | This 'Traversal' provides the traversal of 'simpleVillager' 'Player's.
+-- | The traversal of 'simpleVillager' 'Player's.
 --
 -- @
 -- 'simpleVillagers' = 'traverse' . 'filtered' ('is' 'simpleVillager')
@@ -471,7 +471,7 @@
 simpleVillagers :: Traversable t => Traversal' (t Player) Player
 simpleVillagers = traverse . filtered (is simpleVillager)
 
--- | This 'Traversal' provides the traversal of 'simpleWerewolf' 'Player's.
+-- | The traversal of 'simpleWerewolf' 'Player's.
 --
 -- @
 -- 'simpleWerewolves' = 'traverse' . 'filtered' ('is' 'simpleWerewolf')
@@ -479,7 +479,7 @@
 simpleWerewolves :: Traversable t => Traversal' (t Player) Player
 simpleWerewolves = traverse . filtered (is simpleWerewolf)
 
--- | This 'Traversal' provides the traversal of 'spitefulVillager' 'Player's.
+-- | The traversal of 'spitefulVillager' 'Player's.
 --
 -- @
 -- 'spitefulVillagers' = 'traverse' . 'filtered' ('is' 'spitefulVillager')
@@ -487,7 +487,7 @@
 spitefulVillagers :: Traversable t => Traversal' (t Player) Player
 spitefulVillagers = traverse . filtered (is spitefulVillager)
 
--- | This 'Traversal' provides the traversal of 'trueVillager' 'Player's.
+-- | The traversal of 'trueVillager' 'Player's.
 --
 -- @
 -- 'trueVillagers' = 'traverse' . 'filtered' ('is' 'trueVillager')
@@ -495,7 +495,7 @@
 trueVillagers :: Traversable t => Traversal' (t Player) Player
 trueVillagers = traverse . filtered (is trueVillager)
 
--- | This 'Traversal' provides the traversal of 'villageDrunk' 'Player's.
+-- | The traversal of 'villageDrunk' 'Player's.
 --
 -- @
 -- 'villageDrunks' = 'traverse' . 'filtered' ('is' 'villageDrunk')
@@ -503,7 +503,7 @@
 villageDrunks :: Traversable t => Traversal' (t Player) Player
 villageDrunks = traverse . filtered (is villageDrunk)
 
--- | This 'Traversal' provides the traversal of 'witch' 'Player's.
+-- | The traversal of 'witch' 'Player's.
 --
 -- @
 -- 'witches' = 'traverse' . 'filtered' ('is' 'witch')
@@ -511,7 +511,7 @@
 witches :: Traversable t => Traversal' (t Player) Player
 witches = traverse . filtered (is witch)
 
--- | This 'Traversal' provides the traversal of 'zombie' 'Player's.
+-- | The traversal of 'zombie' 'Player's.
 --
 -- @
 -- 'zombies' = 'traverse' . 'filtered' ('is' 'zombie')
@@ -519,7 +519,7 @@
 zombies :: Traversable t => Traversal' (t Player) Player
 zombies = traverse . filtered (is zombie)
 
--- | This 'Traversal' provides the traversal of 'loner' 'Player's.
+-- | The traversal of 'loner' 'Player's.
 --
 -- @
 -- 'loners' = 'traverse' . 'filtered' . ('is' 'loner')
@@ -527,7 +527,7 @@
 loners :: Traversable t => Traversal' (t Player) Player
 loners = traverse . filtered (is loner)
 
--- | This 'Traversal' provides the traversal of 'villager' 'Player's.
+-- | The traversal of 'villager' 'Player's.
 --
 -- @
 -- 'villagers' = 'traverse' . 'filtered' ('is' 'villager')
@@ -535,7 +535,7 @@
 villagers :: Traversable t => Traversal' (t Player) Player
 villagers = traverse . filtered (is villager)
 
--- | This 'Traversal' provides the traversal of 'werewolf' 'Player's.
+-- | The traversal of 'werewolf' 'Player's.
 --
 -- @
 -- 'werewolves' = 'traverse' . 'filtered' ('is' 'werewolf')
@@ -543,7 +543,7 @@
 werewolves :: Traversable t => Traversal' (t Player) Player
 werewolves = traverse . filtered (is werewolf)
 
--- | This 'Traversal' provides the traversal of 'Alive' 'Player's.
+-- | The traversal of 'Alive' 'Player's.
 --
 -- @
 -- 'alive' = 'filtered' ('has' $ 'state' . '_Alive')
@@ -551,7 +551,7 @@
 alive :: Traversal' Player Player
 alive = filtered (has $ state . _Alive)
 
--- | This 'Traversal' provides the traversal of 'Dead' 'Player's.
+-- | The traversal of 'Dead' 'Player's.
 --
 -- @
 -- 'dead' = 'filtered' ('has' $ 'state' . '_Dead')
diff --git a/src/Game/Werewolf/Variant.hs b/src/Game/Werewolf/Variant.hs
new file mode 100644
--- /dev/null
+++ b/src/Game/Werewolf/Variant.hs
@@ -0,0 +1,121 @@
+{-|
+Module      : Game.Werewolf.Variant
+Description : Simplistic variant data structure with lenses and instances.
+
+Copyright   : (c) Henry J. Wylde, 2016
+License     : BSD3
+Maintainer  : public@hjwylde.com
+
+Variants alter how a game plays out. Either by changing the messages returned, or by changing the
+game logic.
+-}
+
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE QuasiQuotes       #-}
+{-# LANGUAGE TemplateHaskell   #-}
+
+module Game.Werewolf.Variant (
+    -- * Variant
+    Variant,
+    tag, name, description,
+
+    -- ** Instances
+    allVariants,
+
+    standardVariant, noRoleKnowledgeVariant, noRoleRevealVariant, spitefulVillageVariant,
+
+    -- ** Traversals
+    standard, noRoleKnowledge, noRoleReveal, spitefulVillage,
+) where
+
+import Control.Lens
+
+import Data.String.Humanise
+import Data.String.Interpolate.Extra
+import Data.Text                     as T
+
+import Game.Werewolf.Role hiding (description, name, tag)
+
+-- | Variant definitions require only a few pieces of information.
+data Variant = Variant
+    { _tag         :: Text
+    , _name        :: Text
+    , _description :: Text
+    } deriving (Eq, Read, Show)
+
+makeLenses ''Variant
+
+instance Humanise Variant where
+    humanise = view name
+
+-- | A list containing all the variants defined in this file.
+allVariants :: [Variant]
+allVariants =
+    [ standardVariant
+    , noRoleKnowledgeVariant
+    , noRoleRevealVariant
+    , spitefulVillageVariant
+    ]
+
+standardVariant :: Variant
+standardVariant = Variant
+    { _tag          = "standard"
+    , _name         = T.strip [iFile|variant/standard/name.txt|]
+    , _description  = T.strip [iFile|variant/standard/description.txt|]
+    }
+
+noRoleKnowledgeVariant :: Variant
+noRoleKnowledgeVariant = Variant
+    { _tag          = "no-role-knowledge"
+    , _name         = T.strip [iFile|variant/no-role-knowledge/name.txt|]
+    , _description  = T.strip [iFile|variant/no-role-knowledge/description.txt|]
+    }
+
+noRoleRevealVariant :: Variant
+noRoleRevealVariant = Variant
+    { _tag          = "no-role-reveal"
+    , _name         = T.strip [iFile|variant/no-role-reveal/name.txt|]
+    , _description  = T.strip [iFile|variant/no-role-reveal/description.txt|]
+    }
+
+spitefulVillageVariant :: Variant
+spitefulVillageVariant = Variant
+    { _tag          = "spiteful-village"
+    , _name         = T.strip [iFile|variant/spiteful-village/name.txt|]
+    , _description  = T.strip [iFile|variant/spiteful-village/description.txt|]
+    }
+
+-- | The traversal of 'standard' 'Variant's.
+--
+-- @
+-- 'standard' = 'only' 'standardVariant'
+-- @
+standard :: Traversal' Variant ()
+standard = only standardVariant
+
+
+-- | The traversal of 'noRoleKnowledge' 'Variant's.
+--
+-- @
+-- 'noRoleKnowledge' = 'only' 'noRoleKnowledgeVariant'
+-- @
+noRoleKnowledge :: Traversal' Variant ()
+noRoleKnowledge = only noRoleKnowledgeVariant
+
+
+-- | The traversal of 'noRoleReveal' 'Variant's.
+--
+-- @
+-- 'noRoleReveal' = 'only' 'noRoleRevealVariant'
+-- @
+noRoleReveal :: Traversal' Variant ()
+noRoleReveal = only noRoleRevealVariant
+
+
+-- | The traversal of 'spitefulVillage' 'Variant's.
+--
+-- @
+-- 'spitefulVillage' = 'only' 'spitefulVillageVariant'
+-- @
+spitefulVillage :: Traversal' Variant ()
+spitefulVillage = only spitefulVillageVariant
diff --git a/variant/no-role-knowledge/description.txt b/variant/no-role-knowledge/description.txt
new file mode 100644
--- /dev/null
+++ b/variant/no-role-knowledge/description.txt
@@ -0,0 +1,1 @@
+In "No role knowledge", players are not informed of the roles in play at the start of a game. Further, nocturnal player turns are obscured.
diff --git a/variant/no-role-knowledge/name.txt b/variant/no-role-knowledge/name.txt
new file mode 100644
--- /dev/null
+++ b/variant/no-role-knowledge/name.txt
@@ -0,0 +1,1 @@
+No role knowledge
diff --git a/variant/no-role-reveal/description.txt b/variant/no-role-reveal/description.txt
new file mode 100644
--- /dev/null
+++ b/variant/no-role-reveal/description.txt
@@ -0,0 +1,1 @@
+In "No role reveal", players are not informed of each others roles upon death. Further, nocturnal player turns are obscured.
diff --git a/variant/no-role-reveal/name.txt b/variant/no-role-reveal/name.txt
new file mode 100644
--- /dev/null
+++ b/variant/no-role-reveal/name.txt
@@ -0,0 +1,1 @@
+No role reveal
diff --git a/variant/spiteful-village/description.txt b/variant/spiteful-village/description.txt
new file mode 100644
--- /dev/null
+++ b/variant/spiteful-village/description.txt
@@ -0,0 +1,1 @@
+In "Spiteful village", the default Villager role becomes the #{humanise spitefulVillagerRole}.
diff --git a/variant/spiteful-village/name.txt b/variant/spiteful-village/name.txt
new file mode 100644
--- /dev/null
+++ b/variant/spiteful-village/name.txt
@@ -0,0 +1,1 @@
+Spiteful village
diff --git a/variant/standard/command/help/help-commands.txt b/variant/standard/command/help/help-commands.txt
--- a/variant/standard/command/help/help-commands.txt
+++ b/variant/standard/command/help/help-commands.txt
@@ -2,3 +2,4 @@
 - `help commands [-a | --all]`
 - `help roles [-a | --all]`
 - `help rules [-a | --all]`
+- `help variants [-a | --all]`
diff --git a/variant/standard/command/help/variant.txt b/variant/standard/command/help/variant.txt
new file mode 100644
--- /dev/null
+++ b/variant/standard/command/help/variant.txt
@@ -0,0 +1,2 @@
+#{humanise variant} (#{variant ^. Variant.tag}):
+#{variant ^. Variant.description}
diff --git a/variant/standard/description.txt b/variant/standard/description.txt
new file mode 100644
--- /dev/null
+++ b/variant/standard/description.txt
@@ -0,0 +1,1 @@
+The Standard variant; everything plays out as normal.
diff --git a/variant/standard/engine/lynching/jester-lynched.txt b/variant/standard/engine/lynching/jester-lynched.txt
--- a/variant/standard/engine/lynching/jester-lynched.txt
+++ b/variant/standard/engine/lynching/jester-lynched.txt
@@ -1,1 +1,1 @@
-Just as the townsfolk tie #{humanise jester} up to the pyre, a voice in the crowd yells out. "We can't burn #{humanise jester}! He's the joke of the town!" #{humanise jester} the #{humanise jesterRole} is quickly untied and apologised to.
+Just as the townsfolk tie #{humanise jester} up to the pyre, a voice in the crowd yells out. "We can't burn #{humanise jester}! They're the joke of the town!" #{humanise jester} the #{humanise jesterRole} is quickly untied and apologised to.
diff --git a/variant/standard/error/command/start/variant-does-not-exist.txt b/variant/standard/error/command/start/variant-does-not-exist.txt
new file mode 100644
--- /dev/null
+++ b/variant/standard/error/command/start/variant-does-not-exist.txt
@@ -0,0 +1,1 @@
+Variant #{variantName} does not exist.
diff --git a/variant/standard/name.txt b/variant/standard/name.txt
new file mode 100644
--- /dev/null
+++ b/variant/standard/name.txt
@@ -0,0 +1,1 @@
+Standard
diff --git a/variant/standard/role/medusa/description.txt b/variant/standard/role/medusa/description.txt
--- a/variant/standard/role/medusa/description.txt
+++ b/variant/standard/role/medusa/description.txt
@@ -1,1 +1,1 @@
-A beautiful flirt, the Medusa is aligned with the Villagers but harbours a terrifying secret. During the day they are well known in the village of Fougères for their stunning appearance which captures the eye and love of all the townsfolk. However when their secret takes ahold at sundown, their true self is revealed. Any who gaze upon her true form would see live snakes for hair and the few that further look into her eyes are turned to stone.
+A beautiful flirt, the Medusa is aligned with the Villagers but harbours a terrifying secret. During the day they are well known in the village of Fougères for their stunning appearance which captures the eye and love of all the townsfolk. However when their secret takes ahold at sundown, their true self is revealed. Any who gaze upon their true form would see live snakes for hair and the few that further look into their eyes are turned to stone.
diff --git a/variant/standard/role/necromancer/description.txt b/variant/standard/role/necromancer/description.txt
--- a/variant/standard/role/necromancer/description.txt
+++ b/variant/standard/role/necromancer/description.txt
@@ -1,1 +1,1 @@
-The dead are feared among the living; the dead outnumber the living. In most villages the dead remain that way, but not when the Necromancer is present. The Necromancer devoted their life to learning black magic and methods of bringing people back to life. Unfortunately this art is hard to perfect and all they can manage to bring back are soulless Zombies.
+The dead are feared among the living; the dead outnumber the living. In most villages the dead remain that way, but not when the Necromancer is present. The Necromancer devoted their life to learning black magic and methods of bringing people back to life. Unfortunately this art is hard to perfect and all they can manage to bring back are soulless #{humanise zombieRole}s.
diff --git a/variant/standard/role/necromancer/rules.txt b/variant/standard/role/necromancer/rules.txt
--- a/variant/standard/role/necromancer/rules.txt
+++ b/variant/standard/role/necromancer/rules.txt
@@ -1,3 +1,2 @@
-Once per game the Necromancer can choose to resurrect all dead players as Zombies. The Zombies are aligned with the Necromancer and they cannot be lynched or devoured.
-If the Necromancer is killed, all Zombies die with them.
-The Necromancer and Zombies win if they are the last ones alive.
+Once per game the Necromancer can choose to resurrect all dead players as #{humanise zombieRole}s. The #{humanise zombieRole}s are aligned with the Necromancer and they cannot be lynched or devoured. If the Necromancer is killed, all #{humanise zombieRole}s die with them.
+The Necromancer and #{humanise zombieRole}s win if they are the last ones alive.
diff --git a/variant/standard/role/zombie/description.txt b/variant/standard/role/zombie/description.txt
--- a/variant/standard/role/zombie/description.txt
+++ b/variant/standard/role/zombie/description.txt
@@ -1,1 +1,1 @@
-A loyal follower of the Necromancer. A Zombie has no mind of its own and blindly obeys every command of their master.
+A loyal follower of the #{humanise necromancerRole}. A Zombie has no mind of its own and blindly obeys every command of their master.
diff --git a/variant/standard/role/zombie/rules.txt b/variant/standard/role/zombie/rules.txt
--- a/variant/standard/role/zombie/rules.txt
+++ b/variant/standard/role/zombie/rules.txt
@@ -1,1 +1,1 @@
-A Zombie wins with the Necromancer. They cannot be killed, however they die when the Necromancer dies.
+A Zombie wins with the #{humanise necromancerRole}. They cannot be killed, however they die when the #{humanise necromancerRole} dies.
diff --git a/werewolf.cabal b/werewolf.cabal
--- a/werewolf.cabal
+++ b/werewolf.cabal
@@ -1,5 +1,5 @@
 name:           werewolf
-version:        1.4.0.1
+version:        1.5.0.0
 
 author:         Henry J. Wylde
 maintainer:     public@hjwylde.com
@@ -21,10 +21,14 @@
 extra-source-files:
     CHANGELOG.md
     README.md
+    variant/no-role-knowledge/description.txt
+    variant/no-role-knowledge/name.txt
     variant/no-role-knowledge/command/ping/nocturnal-role-pinged.txt
     variant/no-role-knowledge/command/ping/werewolves-pinged.txt
     variant/no-role-knowledge/command/status/current-nocturnal-turn.txt
     variant/no-role-knowledge/engine/new-game/roles-in-game.txt
+    variant/no-role-reveal/description.txt
+    variant/no-role-reveal/name.txt
     variant/no-role-reveal/command/choose/player-shot.txt
     variant/no-role-reveal/command/ping/nocturnal-role-pinged.txt
     variant/no-role-reveal/command/ping/werewolves-pinged.txt
@@ -39,6 +43,10 @@
     variant/no-role-reveal/engine/sunrise/player-devoured.txt
     variant/no-role-reveal/engine/sunrise/player-poisoned.txt
     variant/no-role-reveal/engine/sunrise/player-turned-to-stone.txt
+    variant/spiteful-village/description.txt
+    variant/spiteful-village/name.txt
+    variant/standard/description.txt
+    variant/standard/name.txt
     variant/standard/command/boot/player-voted-boot.txt
     variant/standard/command/choose/player-shot.txt
     variant/standard/command/circle/game-circle.txt
@@ -68,6 +76,7 @@
     variant/standard/command/help/status-commands.txt
     variant/standard/command/help/sunrise.txt
     variant/standard/command/help/sunset.txt
+    variant/standard/command/help/variant.txt
     variant/standard/command/help/village-drunks-turn.txt
     variant/standard/command/help/villages-turn.txt
     variant/standard/command/help/werewolves-turn.txt
@@ -176,6 +185,7 @@
     variant/standard/error/command/start/player-names-not-unique.txt
     variant/standard/error/command/start/role-count-restricted.txt
     variant/standard/error/command/start/role-does-not-exist.txt
+    variant/standard/error/command/start/variant-does-not-exist.txt
     variant/standard/error/command/unvote/caller-not-voted.txt
     variant/standard/error/command/vote/caller-already-voted.txt
     variant/standard/error/command/vote/caller-cannot-devour-werewolf.txt
@@ -346,6 +356,7 @@
         Game.Werewolf.Player
         Game.Werewolf.Response
         Game.Werewolf.Role
+        Game.Werewolf.Variant
 
     default-language: Haskell2010
     other-extensions:
