werewolf-0.2.0.0: test/src/Game/Werewolf/Test/Engine.hs
{-|
Module : Game.Werewolf.Test.Engine
Copyright : (c) Henry J. Wylde, 2015
License : BSD3
Maintainer : public@hjwylde.com
-}
{-# OPTIONS_HADDOCK hide, prune #-}
{-# LANGUAGE OverloadedStrings #-}
module Game.Werewolf.Test.Engine (
-- * checkTurn
prop_checkTurnSkipsSeersWhenNoSeers, prop_checkTurnDoesNothingWhenGameOver,
prop_checkSeersTurnAdvancesToWerewolves, prop_checkSeersTurnResetsSees,
prop_checkSeersTurnDoesNothingUnlessAllSeen, prop_checkVillagersTurnAdvancesToSeers,
prop_checkVillagersTurnLynchesOnePlayerWhenConsensus,
prop_checkVillagersTurnLynchesNoOneWhenConflicted, prop_checkVillagersTurnResetsVotes,
prop_checkVillagersTurnDoesNothingUnlessAllVoted, prop_checkWerewolvesTurnAdvancesToVillagers,
prop_checkWerewolvesTurnKillsOnePlayerWhenConsensus,
prop_checkWerewolvesTurnKillsNoOneWhenConflicted, prop_checkWerewolvesTurnResetsVotes,
prop_checkWerewolvesTurnDoesNothingUnlessAllVoted,
-- * checkGameOver
prop_checkGameOverAdvancesTurn, prop_checkGameOverDoesNothingWhenAtLeastTwoAllegiancesAlive,
-- * startGame
prop_startGameStartsWithSeersTurn, prop_startGameUsesGivenPlayers,
prop_startGameErrorsUnlessUniquePlayerNames, prop_startGameErrorsWhenLessThan7Players,
prop_startGameErrorsWhenMoreThan24Players,
-- * createPlayers
prop_createPlayersUsesGivenPlayerNames, prop_createPlayersCreatesAlivePlayers,
-- * randomiseRoles
prop_randomiseRolesReturnsNRoles, prop_randomiseRolesProportionsRoles,
prop_randomiseRolesHasOneSeer,
) where
import Control.Lens hiding (elements)
import Control.Monad.Except
import Control.Monad.Writer
import Data.Either.Extra
import Data.List.Extra
import qualified Data.Map as Map
import Data.Text (Text)
import Game.Werewolf.Engine hiding (doesPlayerExist, isGameOver, isSeersTurn,
isVillagersTurn, isWerewolvesTurn, killPlayer)
import Game.Werewolf.Game
import Game.Werewolf.Player
import Game.Werewolf.Role hiding (Villagers, Werewolves, _name)
import Game.Werewolf.Test.Arbitrary
import Test.QuickCheck
import Test.QuickCheck.Monadic
prop_checkTurnSkipsSeersWhenNoSeers :: Game -> Property
prop_checkTurnSkipsSeersWhenNoSeers game =
forAll (runArbitraryCommands n game') $ \game'' ->
isWerewolvesTurn $ run_ checkTurn game''
where
game' = (foldl killPlayer game (filterSeers $ game ^. players)) { _turn = Villagers }
n = length . filterAlive $ game' ^. players
prop_checkTurnDoesNothingWhenGameOver :: Game -> Property
prop_checkTurnDoesNothingWhenGameOver game = run_ checkTurn game' === game'
where
game' = game { _turn = NoOne }
prop_checkSeersTurnAdvancesToWerewolves :: Game -> Property
prop_checkSeersTurnAdvancesToWerewolves game =
forAll (runArbitraryCommands n game') $ \game'' ->
isWerewolvesTurn $ run_ checkTurn game''
where
game' = game { _turn = Seers }
n = length . filterSeers $ game' ^. players
prop_checkSeersTurnResetsSees :: Game -> Property
prop_checkSeersTurnResetsSees game =
forAll (runArbitraryCommands n game') $ \game'' ->
Map.null $ run_ checkTurn game'' ^. sees
where
game' = game { _turn = Seers }
n = length . filterSeers $ game' ^. players
prop_checkSeersTurnDoesNothingUnlessAllSeen :: Game -> Property
prop_checkSeersTurnDoesNothingUnlessAllSeen game =
forAll (runArbitraryCommands n game') $ \game'' ->
isSeersTurn $ run_ checkTurn game''
where
game' = game { _turn = Seers }
n = length (filterSeers $ game' ^. players) - 1
prop_checkVillagersTurnAdvancesToSeers :: Game -> Property
prop_checkVillagersTurnAdvancesToSeers game =
forAll (runArbitraryCommands n game') $ \game'' ->
not (null . filterAlive . filterSeers $ run_ checkTurn game'' ^. players)
==> isSeersTurn $ run_ checkTurn game''
where
game' = game { _turn = Villagers }
n = length $ game' ^. players
prop_checkVillagersTurnLynchesOnePlayerWhenConsensus :: Game -> Property
prop_checkVillagersTurnLynchesOnePlayerWhenConsensus game =
forAll (runArbitraryCommands n game') $ \game'' ->
length (last $ groupSortOn (length . flip elemIndices (Map.elems $ game'' ^. votes)) (nub . Map.elems $ game'' ^. votes)) == 1
==> length (filterDead $ run_ checkTurn game'' ^. players) == 1
where
game' = game { _turn = Villagers }
n = length $ game' ^. players
prop_checkVillagersTurnLynchesNoOneWhenConflicted :: Game -> Property
prop_checkVillagersTurnLynchesNoOneWhenConflicted game =
forAll (runArbitraryCommands n game') $ \game'' ->
length (last $ groupSortOn (length . flip elemIndices (Map.elems $ game'' ^. votes)) (nub . Map.elems $ game'' ^. votes)) > 1
==> length (filterDead $ run_ checkTurn game'' ^. players) == 0
where
game' = game { _turn = Villagers }
n = length $ game' ^. players
prop_checkVillagersTurnResetsVotes :: Game -> Property
prop_checkVillagersTurnResetsVotes game =
forAll (runArbitraryCommands n game') $ \game'' ->
Map.null $ run_ checkTurn game'' ^. votes
where
game' = game { _turn = Villagers }
n = length $ game' ^. players
prop_checkVillagersTurnDoesNothingUnlessAllVoted :: Game -> Property
prop_checkVillagersTurnDoesNothingUnlessAllVoted game =
forAll (runArbitraryCommands n game') $ \game'' ->
isVillagersTurn $ run_ checkTurn game''
where
game' = game { _turn = Villagers }
n = length (game' ^. players) - 1
prop_checkWerewolvesTurnAdvancesToVillagers :: Game -> Property
prop_checkWerewolvesTurnAdvancesToVillagers game =
forAll (runArbitraryCommands n game') $ \game' ->
isVillagersTurn $ run_ checkTurn game'
where
game' = game { _turn = Werewolves }
n = length . filterWerewolves $ game' ^. players
prop_checkWerewolvesTurnKillsOnePlayerWhenConsensus :: Game -> Property
prop_checkWerewolvesTurnKillsOnePlayerWhenConsensus game =
forAll (runArbitraryCommands n game') $ \game'' ->
length (last $ groupSortOn (length . flip elemIndices (Map.elems $ game'' ^. votes)) (nub . Map.elems $ game'' ^. votes)) == 1
==> length (filterDead $ run_ checkTurn game'' ^. players) == 1
where
game' = game { _turn = Werewolves }
n = length $ game' ^. players
prop_checkWerewolvesTurnKillsNoOneWhenConflicted :: Game -> Property
prop_checkWerewolvesTurnKillsNoOneWhenConflicted game =
forAll (runArbitraryCommands n game') $ \game'' ->
length (last $ groupSortOn (length . flip elemIndices (Map.elems $ game'' ^. votes)) (nub . Map.elems $ game'' ^. votes)) > 1
==> length (filterDead $ run_ checkTurn game'' ^. players) == 0
where
game' = game { _turn = Werewolves }
n = length $ game' ^. players
prop_checkWerewolvesTurnResetsVotes :: Game -> Property
prop_checkWerewolvesTurnResetsVotes game =
forAll (runArbitraryCommands n game') $ \game' ->
Map.null $ run_ checkTurn game' ^. votes
where
game' = game { _turn = Werewolves }
n = length . filterWerewolves $ game' ^. players
prop_checkWerewolvesTurnDoesNothingUnlessAllVoted :: Game -> Property
prop_checkWerewolvesTurnDoesNothingUnlessAllVoted game =
forAll (runArbitraryCommands n game') $ \game'' ->
isWerewolvesTurn $ run_ checkTurn game''
where
game' = game { _turn = Werewolves }
n = length (filterWerewolves $ game' ^. players) - 1
prop_checkGameOverAdvancesTurn :: Game -> Property
prop_checkGameOverAdvancesTurn game =
forAll (sublistOf $ game ^. players) $ \players' ->
let game' = foldl killPlayer game players' in
length (nub . map (_allegiance . _role) . filterAlive $ game' ^. players) <= 1
==> isGameOver $ run_ checkGameOver game'
prop_checkGameOverDoesNothingWhenAtLeastTwoAllegiancesAlive :: Game -> Property
prop_checkGameOverDoesNothingWhenAtLeastTwoAllegiancesAlive game =
not (isGameOver game)
==> forAll (sublistOf $ game ^. players) $ \players' ->
let game' = foldl killPlayer game players' in
length (nub . map (_allegiance . _role) . filterAlive $ game' ^. players) > 1
==> not . isGameOver $ run_ checkGameOver game'
prop_startGameStartsWithSeersTurn :: [Player] -> Property
prop_startGameStartsWithSeersTurn players = and [
isRight . runExcept . runWriterT $ startGame "" players
] ==> isSeersTurn (fst . fromRight . runExcept . runWriterT $ startGame "" players)
prop_startGameUsesGivenPlayers :: [Player] -> Property
prop_startGameUsesGivenPlayers players' = and [
isRight . runExcept . runWriterT $ startGame "" players'
] ==> (fst . fromRight . runExcept . runWriterT $ startGame "" players') ^. players == players'
prop_startGameErrorsUnlessUniquePlayerNames :: [Player] -> Property
prop_startGameErrorsUnlessUniquePlayerNames players = and [
isRight . runExcept . runWriterT $ startGame "" players
] ==> forAll (elements players) $ \player -> isLeft (runExcept . runWriterT $ startGame "" (player:players))
prop_startGameErrorsWhenLessThan7Players :: [Player] -> Property
prop_startGameErrorsWhenLessThan7Players players = and [
length players < 7
] ==> isLeft (runExcept . runWriterT $ startGame "" players)
prop_startGameErrorsWhenMoreThan24Players :: Property
prop_startGameErrorsWhenMoreThan24Players = forAll (resize 30 $ listOf arbitrary) $ \players -> and [
length players > 24
] ==> isLeft (runExcept . runWriterT $ startGame "" players)
prop_createPlayersUsesGivenPlayerNames :: [Text] -> Property
prop_createPlayersUsesGivenPlayerNames playerNames = monadicIO $ createPlayers playerNames >>= return . (playerNames ==) . map _name
prop_createPlayersCreatesAlivePlayers :: [Text] -> Property
prop_createPlayersCreatesAlivePlayers playerNames = monadicIO $ createPlayers playerNames >>= return . all ((==) Alive . _state)
prop_randomiseRolesReturnsNRoles :: Int -> Property
prop_randomiseRolesReturnsNRoles n = monadicIO $ randomiseRoles n >>= return . (==) n . length
prop_randomiseRolesProportionsRoles :: Int -> Property
prop_randomiseRolesProportionsRoles n = monadicIO $ do
roles <- randomiseRoles n
let werewolvesCount = length $ elemIndices werewolfRole roles
return $ n `quot` 6 + 1 == werewolvesCount
prop_randomiseRolesHasOneSeer :: Int -> Property
prop_randomiseRolesHasOneSeer n = monadicIO $ randomiseRoles n >>= return . (1 ==) . length . elemIndices seerRole