werewolf-0.4.2.1: 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 (
-- * checkStage
prop_checkStageSkipsSeersTurnWhenNoSeer, prop_checkStageSkipsWitchsTurnWhenNoWitch,
prop_checkStageDoesNothingWhenGameOver,
prop_checkSeersTurnAdvancesToWerewolvesTurn, prop_checkSeersTurnResetsSee,
prop_checkSeersTurnDoesNothingUnlessSeen,
prop_checkVillagesTurnAdvancesToSeersTurn, prop_checkVillagesTurnLynchesOnePlayerWhenConsensus,
prop_checkVillagesTurnLynchesNoOneWhenConflictedAndNoScapegoats,
prop_checkVillagesTurnLynchesScapegoatWhenConflicted, prop_checkVillagesTurnResetsVotes,
prop_checkVillagesTurnDoesNothingUnlessAllVoted,
prop_checkWerewolvesTurnAdvancesToWitchsTurn,
prop_checkWerewolvesTurnDoesntSkipWitchsTurnWhenWitchDevoured,
prop_checkWerewolvesTurnSkipsWitchsTurnWhenHealedAndPoisoned,
prop_checkWerewolvesTurnKillsOnePlayerWhenConsensus,
prop_checkWerewolvesTurnKillsNoOneWhenConflicted, prop_checkWerewolvesTurnResetsVotes,
prop_checkWerewolvesTurnDoesNothingUnlessAllVoted,
prop_checkWitchsTurnAdvancesToVillagesTurn, prop_checkWitchsTurnHealsDevoureeWhenHealed,
prop_checkWitchsTurnKillsOnePlayerWhenPoisoned, prop_checkWitchsTurnDoesNothingWhenPassed,
prop_checkWitchsTurnResetsHeal, prop_checkWitchsTurnResetsPoison,
-- * checkGameOver
prop_checkGameOverAdvancesStage, prop_checkGameOverDoesNothingWhenAtLeastTwoAllegiancesAlive,
-- * startGame
prop_startGameStartsWithSunsetStage, prop_startGameUsesGivenPlayers,
prop_startGameErrorsUnlessUniquePlayerNames, prop_startGameErrorsWhenLessThan7Players,
prop_startGameErrorsWhenMoreThan24Players, prop_startGameErrorsWhenMoreThan1Scapegoat,
prop_startGameErrorsWhenMoreThan1Seer, prop_startGameErrorsWhenMoreThan1VillagerVillager,
prop_startGameErrorsWhenMoreThan1Witch,
-- * createPlayers
prop_createPlayersUsesGivenPlayerNames, prop_createPlayersUsesGivenRoles,
prop_createPlayersCreatesAlivePlayers,
-- * randomiseRoles
prop_randomiseRolesReturnsNRoles, prop_randomiseRolesUsesGivenRoles,
prop_randomiseRolesProportionsAllegiances,
) 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.Maybe
import Data.Text (Text)
import Game.Werewolf.Command
import Game.Werewolf.Engine hiding (doesPlayerExist, getDevourEvent,
getVoteResult, isGameOver, isSeersTurn,
isVillagesTurn, isWerewolvesTurn, isWitchsTurn,
killPlayer)
import Game.Werewolf.Game
import Game.Werewolf.Player
import Game.Werewolf.Role hiding (name, _name)
import qualified Game.Werewolf.Role as Role
import Game.Werewolf.Test.Arbitrary
import Test.QuickCheck
import Test.QuickCheck.Monadic
prop_checkStageSkipsSeersTurnWhenNoSeer :: Game -> Property
prop_checkStageSkipsSeersTurnWhenNoSeer game =
forAll (runArbitraryCommands n game') $ \game'' ->
isWerewolvesTurn $ run_ checkStage game''
where
game' = (foldl killPlayer game (filterSeers $ game ^. players)) { _stage = VillagesTurn }
n = length $ game' ^. players
prop_checkStageSkipsWitchsTurnWhenNoWitch :: Game -> Property
prop_checkStageSkipsWitchsTurnWhenNoWitch game =
forAll (runArbitraryCommands n game') $ \game'' ->
isVillagesTurn $ run_ checkStage game''
where
game' = (foldl killPlayer game (filterWitches $ game ^. players)) { _stage = WerewolvesTurn }
n = length . filterWerewolves $ game' ^. players
prop_checkStageDoesNothingWhenGameOver :: Game -> Property
prop_checkStageDoesNothingWhenGameOver game = run_ checkStage game' === game'
where
game' = game { _stage = GameOver }
prop_checkSeersTurnAdvancesToWerewolvesTurn :: Game -> Property
prop_checkSeersTurnAdvancesToWerewolvesTurn game =
forAll (arbitraryCommand game') $ \command ->
isWerewolvesTurn $ run_ checkStage (run_ (apply command) game')
where
game' = game { _stage = SeersTurn }
prop_checkSeersTurnResetsSee :: Game -> Property
prop_checkSeersTurnResetsSee game =
forAll (arbitraryCommand game') $ \command ->
isNothing $ run_ checkStage (run_ (apply command) game') ^. see
where
game' = game { _stage = SeersTurn }
prop_checkSeersTurnDoesNothingUnlessSeen :: Game -> Bool
prop_checkSeersTurnDoesNothingUnlessSeen game = isSeersTurn $ run_ checkStage game'
where
game' = game { _stage = SeersTurn }
prop_checkVillagesTurnAdvancesToSeersTurn :: Game -> Property
prop_checkVillagesTurnAdvancesToSeersTurn game =
forAll (runArbitraryCommands n game') $ \game'' ->
not (null . filterAlive . filterSeers $ run_ checkStage game'' ^. players)
==> isSeersTurn $ run_ checkStage game''
where
game' = game { _stage = VillagesTurn }
n = length $ game' ^. players
prop_checkVillagesTurnLynchesOnePlayerWhenConsensus :: Game -> Property
prop_checkVillagesTurnLynchesOnePlayerWhenConsensus game =
forAll (runArbitraryCommands n game') $ \game'' ->
length (getVoteResult game'') == 1
==> length (filterDead $ run_ checkStage game'' ^. players) == 1
where
game' = game { _stage = VillagesTurn }
n = length $ game' ^. players
prop_checkVillagesTurnLynchesNoOneWhenConflictedAndNoScapegoats :: Game -> Property
prop_checkVillagesTurnLynchesNoOneWhenConflictedAndNoScapegoats game =
forAll (runArbitraryCommands n game') $ \game'' ->
length (getVoteResult game'') > 1
==> length (filterDead $ run_ checkStage game'' ^. players) == length (filterDead $ game' ^. players)
where
game' = (foldl killPlayer game (filterScapegoats $ game ^. players)) { _stage = VillagesTurn }
n = length $ game' ^. players
prop_checkVillagesTurnLynchesScapegoatWhenConflicted :: Game -> Property
prop_checkVillagesTurnLynchesScapegoatWhenConflicted game =
forAll (runArbitraryCommands n game') $ \game'' -> and [
length (getVoteResult game'') > 1,
any isScapegoat $ game' ^. players
] ==> isScapegoat $ head (filterDead $ run_ checkStage game'' ^. players)
where
game' = game { _stage = VillagesTurn }
n = length $ game' ^. players
prop_checkVillagesTurnResetsVotes :: Game -> Property
prop_checkVillagesTurnResetsVotes game =
forAll (runArbitraryCommands n game') $ \game'' ->
Map.null $ run_ checkStage game'' ^. votes
where
game' = game { _stage = VillagesTurn }
n = length $ game' ^. players
prop_checkVillagesTurnDoesNothingUnlessAllVoted :: Game -> Property
prop_checkVillagesTurnDoesNothingUnlessAllVoted game =
forAll (runArbitraryCommands n game') $ \game'' ->
isVillagesTurn $ run_ checkStage game''
where
game' = game { _stage = VillagesTurn }
n = length (game' ^. players) - 1
prop_checkWerewolvesTurnAdvancesToWitchsTurn :: Game -> Property
prop_checkWerewolvesTurnAdvancesToWitchsTurn game =
forAll (runArbitraryCommands n game') $ \game'' ->
length (getVoteResult game'') == 1
==> let target = head $ getVoteResult game''
in not (isWitch target)
==> isWitchsTurn $ run_ checkStage game''
where
game' = game { _stage = WerewolvesTurn }
n = length . filterWerewolves $ game' ^. players
prop_checkWerewolvesTurnDoesntSkipWitchsTurnWhenWitchDevoured :: Game -> Property
prop_checkWerewolvesTurnDoesntSkipWitchsTurnWhenWitchDevoured game =
forAll (arbitraryWitch game) $ \witch ->
let devourVoteCommands = map (\werewolf -> devourVoteCommand (werewolf ^. name) (witch ^. name)) (filterWerewolves $ game ^. players)
game'' = foldl (flip $ run_ . apply) game' devourVoteCommands
in isWitchsTurn $ run_ checkStage game''
where
game' = game { _stage = WerewolvesTurn }
prop_checkWerewolvesTurnSkipsWitchsTurnWhenHealedAndPoisoned :: Game -> Bool
prop_checkWerewolvesTurnSkipsWitchsTurnWhenHealedAndPoisoned game =
not . isWitchsTurn $ run_ checkStage game'
where
game' = game { _stage = WerewolvesTurn, _healUsed = True, _poisonUsed = True }
prop_checkWerewolvesTurnKillsOnePlayerWhenConsensus :: Game -> Property
prop_checkWerewolvesTurnKillsOnePlayerWhenConsensus game =
forAll (runArbitraryCommands n game') $ \game'' ->
length (getVoteResult game'') == 1
==> length (filterDead . filter (not . isWitch) $ run_ checkStage game'' ^. players) == 1
where
game' = (foldl killPlayer game (filterWitches $ game ^. players)) { _stage = WerewolvesTurn }
n = length . filterWerewolves $ game' ^. players
prop_checkWerewolvesTurnKillsNoOneWhenConflicted :: Game -> Property
prop_checkWerewolvesTurnKillsNoOneWhenConflicted game =
forAll (runArbitraryCommands n game') $ \game'' ->
length (getVoteResult game'') > 1
==> length (filterDead $ run_ checkStage game'' ^. players) == 0
where
game' = game { _stage = WerewolvesTurn }
n = length . filterWerewolves $ game' ^. players
prop_checkWerewolvesTurnResetsVotes :: Game -> Property
prop_checkWerewolvesTurnResetsVotes game =
forAll (runArbitraryCommands n game') $ \game'' ->
Map.null $ run_ checkStage game'' ^. votes
where
game' = game { _stage = WerewolvesTurn }
n = length . filterWerewolves $ game' ^. players
prop_checkWerewolvesTurnDoesNothingUnlessAllVoted :: Game -> Property
prop_checkWerewolvesTurnDoesNothingUnlessAllVoted game =
forAll (runArbitraryCommands n game') $ \game'' ->
isWerewolvesTurn $ run_ checkStage game''
where
game' = game { _stage = WerewolvesTurn }
n = length (filterWerewolves $ game' ^. players) - 1
prop_checkWitchsTurnAdvancesToVillagesTurn :: Game -> Property
prop_checkWitchsTurnAdvancesToVillagesTurn game =
forAll (arbitraryPassCommand game') $ \command ->
isVillagesTurn $ run_ checkStage (run_ (apply command) game')
where
game' = game { _stage = WitchsTurn }
prop_checkWitchsTurnHealsDevoureeWhenHealed :: Game -> Property
prop_checkWitchsTurnHealsDevoureeWhenHealed game =
forAll (runArbitraryCommands n game') $ \game'' ->
length (getVoteResult game'') == 1
==> let target = head $ getVoteResult game''
in not (isWitch target)
==> let game''' = run_ checkStage game''
in forAll (arbitraryHealCommand game''') $ \command ->
forAll (arbitraryPassCommand game''') $ \passCommand ->
null . filterDead $ run_ checkStage
(run_ (apply passCommand) $
run_ (apply command) game'''
) ^. players
where
game' = game { _stage = WerewolvesTurn }
n = length . filterWerewolves $ game' ^. players
prop_checkWitchsTurnKillsOnePlayerWhenPoisoned :: Game -> Property
prop_checkWitchsTurnKillsOnePlayerWhenPoisoned game =
forAll (arbitraryPoisonCommand game') $ \command ->
forAll (arbitraryPassCommand game') $ \passCommand ->
length (filterDead $ run_ checkStage (run_ (apply passCommand) $ run_ (apply command) game') ^. players) == 1
where
game' = game { _stage = WitchsTurn }
prop_checkWitchsTurnDoesNothingWhenPassed :: Game -> Property
prop_checkWitchsTurnDoesNothingWhenPassed game =
forAll (arbitraryPassCommand game') $ \command ->
null . filterDead $ run_ checkStage (run_ (apply command) game') ^. players
where
game' = game { _stage = WitchsTurn }
prop_checkWitchsTurnResetsHeal :: Game -> Property
prop_checkWitchsTurnResetsHeal game =
forAll (runArbitraryCommands n game') $ \game'' ->
length (getVoteResult game'') == 1
==> let target = head $ getVoteResult game''
in not (isWitch target)
==> let game''' = run_ checkStage game''
in forAll (arbitraryHealCommand game''') $ \command ->
forAll (arbitraryPassCommand game''') $ \passCommand ->
not $ run_ checkStage
(run_ (apply passCommand) $
run_ (apply command) game'''
) ^. heal
where
game' = game { _stage = WerewolvesTurn }
n = length . filterWerewolves $ game' ^. players
prop_checkWitchsTurnResetsPoison :: Game -> Property
prop_checkWitchsTurnResetsPoison game =
forAll (arbitraryPoisonCommand game') $ \command ->
isNothing $ run_ checkStage (run_ (apply command) game') ^. poison
where
game' = game { _stage = WitchsTurn }
prop_checkGameOverAdvancesStage :: Game -> Property
prop_checkGameOverAdvancesStage 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_startGameStartsWithSunsetStage :: Property
prop_startGameStartsWithSunsetStage =
forAll arbitraryPlayerSet $ \players ->
isSunset (fst . fromRight . runExcept . runWriterT $ startGame "" players)
prop_startGameUsesGivenPlayers :: Property
prop_startGameUsesGivenPlayers =
forAll arbitraryPlayerSet $ \players' ->
(fst . fromRight . runExcept . runWriterT $ startGame "" players') ^. players == players'
prop_startGameErrorsUnlessUniquePlayerNames :: Game -> Property
prop_startGameErrorsUnlessUniquePlayerNames game =
forAll (elements players') $ \player -> isLeft (runExcept . runWriterT $ startGame "" (player:players'))
where
players' = game ^. players
prop_startGameErrorsWhenLessThan7Players :: [Player] -> Property
prop_startGameErrorsWhenLessThan7Players players =
length players < 7
==> isLeft (runExcept . runWriterT $ startGame "" players)
prop_startGameErrorsWhenMoreThan24Players :: Property
prop_startGameErrorsWhenMoreThan24Players =
forAll (resize 30 $ listOf arbitrary) $ \players ->
length players > 24
==> isLeft (runExcept . runWriterT $ startGame "" players)
prop_startGameErrorsWhenMoreThan1Scapegoat :: [Player] -> Property
prop_startGameErrorsWhenMoreThan1Scapegoat players =
length (filterScapegoats players) > 1
==> isLeft (runExcept . runWriterT $ startGame "" players)
prop_startGameErrorsWhenMoreThan1Seer :: [Player] -> Property
prop_startGameErrorsWhenMoreThan1Seer players =
length (filterSeers players) > 1
==> isLeft (runExcept . runWriterT $ startGame "" players)
prop_startGameErrorsWhenMoreThan1VillagerVillager :: [Player] -> Property
prop_startGameErrorsWhenMoreThan1VillagerVillager players =
length (filterVillagerVillagers players) > 1
==> isLeft (runExcept . runWriterT $ startGame "" players)
prop_startGameErrorsWhenMoreThan1Witch :: [Player] -> Property
prop_startGameErrorsWhenMoreThan1Witch players =
length (filterWitches players) > 1
==> isLeft (runExcept . runWriterT $ startGame "" players)
prop_createPlayersUsesGivenPlayerNames :: [Text] -> [Role] -> Property
prop_createPlayersUsesGivenPlayerNames playerNames extraRoles = monadicIO $ createPlayers playerNames extraRoles >>= return . (playerNames ==) . map _name
prop_createPlayersUsesGivenRoles :: [Text] -> [Role] -> Property
prop_createPlayersUsesGivenRoles playerNames extraRoles = monadicIO $ createPlayers playerNames extraRoles >>= return . isSubsequenceOf extraRoles . map _role
prop_createPlayersCreatesAlivePlayers :: [Text] -> [Role] -> Property
prop_createPlayersCreatesAlivePlayers playerNames extraRoles = monadicIO $ createPlayers playerNames extraRoles >>= return . all ((Alive ==) . _state)
prop_randomiseRolesReturnsNRoles :: [Role] -> Int -> Property
prop_randomiseRolesReturnsNRoles extraRoles n = monadicIO $ randomiseRoles extraRoles n >>= return . (==) n . length
prop_randomiseRolesUsesGivenRoles :: [Role] -> Int -> Property
prop_randomiseRolesUsesGivenRoles extraRoles n = monadicIO $ randomiseRoles extraRoles n >>= return . isSubsequenceOf extraRoles
prop_randomiseRolesProportionsAllegiances :: [Role] -> Int -> Property
prop_randomiseRolesProportionsAllegiances extraRoles n = monadicIO $ do
roles <- randomiseRoles extraRoles n
let werewolvesCount = length . elemIndices Role.Werewolves $ map _allegiance roles
return $ n `quot` 6 + 1 == werewolvesCount