Skip to content

Instantly share code, notes, and snippets.

@srghma
Created December 24, 2019 17:08
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save srghma/d0bccbb8f4f49ee53c61a434a80c2d40 to your computer and use it in GitHub Desktop.
Save srghma/d0bccbb8f4f49ee53c61a434a80c2d40 to your computer and use it in GitHub Desktop.
module GameLoopSpec where
import Protolude
import Cli.AbstractUtils
import Cli.GameLoop
import Cli.Monads
import Cli.Types
import Test.Hspec
import Test.QuickCheck ( Gen
, choose
, generate
)
import Control.Monad.Random ( evalRand
, mkStdGen
)
import Codebreaker.Game
import Codebreaker.Marker
import Codebreaker.Marker.MarkerException
import Codebreaker.Marker.Type
import Codebreaker.Utils
import qualified Control.Exception
import qualified Control.Lens as Lens
import qualified Control.Monad.Mock as Mock
import qualified Control.Monad.Mock.TH as Mock
import qualified Control.Monad.Random as Random
import Control.Monad.Trans.Class
import qualified Control.Monad.Trans.Reader as Reader
import qualified Control.Monad.Trans.State as State
import qualified Data.Generics.Product as GLens
import qualified Data.List as List
import qualified Data.Text ( )
-- monad-mock docs
-- https://lexi-lambda.github.io/blog/2017/06/29/unit-testing-effectful-haskell-with-monad-mock/
-- how to make monad mock yourself using tagless-final approach
-- https://github.com/lexi-lambda/mtl-style-example/blob/master/test-suite/MTLStyleExample/MainSpec.hs
data TestAction r where
GetInputLine :: Text -> TestAction (Maybe Text)
PrintLine :: Text -> TestAction ()
GetRandomHintDigit :: [Int] -> TestAction Int
deriving instance Eq (TestAction r)
deriving instance Show (TestAction r)
Mock.deriveAction ''TestAction
-------------------------------------
instance Monad m => MonadGetInputLine (Mock.MockT TestAction m) where
getInputLine a = Mock.mockAction "getInputLine" (GetInputLine a)
-- TODO: there is better way https://github.com/scrive/monad-time/blob/master/src/Control/Monad/Time.hs
-- but use UndecidableInstances
instance MonadGetInputLine m => MonadGetInputLine (ReaderT r m) where
getInputLine = lift . getInputLine
instance MonadGetInputLine m => MonadGetInputLine (StateT s m) where
getInputLine = lift . getInputLine
-------------------------------------
instance Monad m => MonadPrint (Mock.MockT TestAction m) where
printLine text = Mock.mockAction "printLine" (PrintLine text)
instance MonadPrint m => MonadPrint (ReaderT r m) where
printLine = lift . printLine
instance MonadPrint m => MonadPrint (StateT s m) where
printLine = lift . printLine
-------------------------------------
instance Monad m => MonadGetRandomHintDigit (Mock.MockT TestAction m) where
getRandomHintDigit secret =
Mock.mockAction "getRandomHintDigit" (GetRandomHintDigit secret)
instance MonadGetRandomHintDigit m =>
MonadGetRandomHintDigit (ReaderT r m) where
getRandomHintDigit = lift . getRandomHintDigit
instance MonadGetRandomHintDigit m => MonadGetRandomHintDigit (StateT s m) where
getRandomHintDigit = lift . getRandomHintDigit
-------------------------------------
spec :: Spec
spec = do
context "main" $ do
it "test game" $ do
(result :: (GameResult, GameState)) <-
Control.Exception.evaluate
$ gameLoop
& flip
runStateT
(GameState {attemptsUsed = 0, secretIndexesAlreadyShownAsHint = []})
& flip
runReaderT
(GameEnv
{ secret = 1 :| [2, 3, 4]
, username = "myusername"
, difficulty = Easy
}
)
& Mock.runMock
[ GetInputLine "% " Mock.:-> Just "myinput"
, PrintLine
"Guess should contain only number and have length eq to 4"
Mock.:-> ()
, GetInputLine "% " Mock.:-> Just "1111"
, PrintLine "+ " Mock.:-> ()
, GetInputLine "% " Mock.:-> Just "1234"
, PrintLine "++++" Mock.:-> ()
, PrintLine "You won a game in 2 attempts" Mock.:-> ()
]
result
`shouldBe` ( Success
(CompletedGame
{ username = "myusername"
, difficulty = Easy
, attemptsTotal = 15
, attemptsUsed = 2
, hintsTotal = 2
, hintsUsed = 0
}
)
, GameState
{ attemptsUsed = 2
, secretIndexesAlreadyShownAsHint = []
}
)
return ()
module MarkerSpec where
import Protolude
import Test.Hspec
import Test.QuickCheck ( Gen
, Arbitrary
, arbitrary
, generate
, suchThat
)
import Test.QuickCheck.Instances.Text ( )
import Test.QuickCheck.Arbitrary.Generic ( genericArbitrary )
import qualified Data.Text as Text
import Codebreaker.Marker
import Codebreaker.Marker.MarkerException
import Codebreaker.Utils
instance Arbitrary a => Arbitrary (NonEmpty a) where
arbitrary = genericArbitrary
spec :: Spec
spec = do
context "when invalid" $ do
it "EmptyInput" $ do
let input = ""
secret <- generate (arbitrary :: Gen (NonEmpty Int))
computeMarker secret input `shouldBe` Left EmptyInput
it "InputLengthShouldBeEqualToSecretLength" $ do
let input = "0000"
let secret = [nonemtpyInt|00000|]
computeMarker secret input
`shouldBe` Left (InputLengthShouldBeEqualToSecretLength 5)
it "InputShouldContainOnlyNumbersFrom1To6" $ do
let input = "000a0"
let secret = [nonemtpyInt|00000|]
computeMarker secret input
`shouldBe` Left (InputShouldContainOnlyNumbersFrom1To6)
context "when valid"
-- from https://docs.google.com/document/d/1VW3Mk1W-pGkq0FadPih689_k971Zy8inzk6UCPHDLzs/edit
-- secret code, input, output
(mapM_ (\(secret :: NonEmpty Int, input :: Text, expectedOutput :: Text) -> it ("secret: " <> (show secret) <> ", input: " <> toS input <> ", expected: " <> toS expectedOutput) $ do
let output = printMarker <$> computeMarker secret input
output `shouldBe` Right expectedOutput
) [
([nonemtpyInt|6543|], "5643", "++--")
, ([nonemtpyInt|6543|], "6411", "+- ")
, ([nonemtpyInt|6543|], "6544", "+++ ")
, ([nonemtpyInt|6543|], "3456", "----")
, ([nonemtpyInt|6543|], "6666", "+ ")
, ([nonemtpyInt|6543|], "2666", "- ")
, ([nonemtpyInt|6543|], "2222", " ")
, ([nonemtpyInt|6666|], "1661", "++ ")
, ([nonemtpyInt|1234|], "3124", "+---")
, ([nonemtpyInt|1234|], "1524", "++- ")
, ([nonemtpyInt|1234|], "1234", "++++")
])
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment