Project Checkpoint #1: Types and Tests
For your first checkpoint, you will meet with your project mentor to discuss your initial progress. You must have some initial code that sketches the beginning of your project completed by this checkpoint. (See example below of what we are looking for.)
Your mentor will ask you the following questions, looking at the code in your project repository for answers:
- What modules will comprise your project?
- What data structures will you use to model your project?
- What functions will you need to write? What are their type signatures?
- What testing will you do?
- What questions do you have for your mentor?
This checkpoint is worth 10 points of your final project grade. If you don't have the types, function declarations and testing code as specified below, you will not get full credit for this checkpoint.
Sample Project
Suppose your project is write a TicTacToe game (NOTE: this is too simple of a project for CIS 552!) Then, at your first checkpoint you might show your mentor this code skeleton. Note that this code compiles, but doesn't really do anything (yet).
This sample code is all in the same module. However, as your project will be larger, you should divide it up into several Haskell modules.
> import Data.Maybe (isJust)
> import qualified Data.Map as M
> import Test.HUnit
> import Test.QuickCheck
> import Control.Monad.State
The most important part of your skeleton are the type definitions that you are planning to work with. For example, in TicTacToe, we need a model of a game in progress, including the Board
and the player next to go.
> -----------------------------
> -- type definitions (model)
> -----------------------------
> data Player = X | O deriving (Eq, Show)
> data Location = Loc Int Int deriving (Eq, Ord, Show)
> type Board = M.Map Location Player
> data Game = Game { board :: Board , current :: Player } deriving (Eq, Show)
> data End = Win Player | Tie deriving (Eq, Show)
> -----------------------------
> -- function declarations
> -----------------------------
Next you should sketch out some of the functions that you plan to implement by giving their type signatures but leaving their bodies unimplemented.
Each function should have a comment explaining what it should do.
How many functions should you declare here? That depends on your tests (see below). You need to be able to specify what your project does using tests, so include enough functions to be able to write a good specification of your project's execution.
> -- | starting board for the game
> initialGame :: Game
> initialGame = undefined
> -- | is the board still playable
> checkEnd :: Board -> Maybe End
> checkEnd = undefined
> -- | is this location a valid move for the player
> valid :: Board -> Location -> Bool
> valid = undefined
> -- | make a move at a particular location
> makeMove :: Game -> Location -> Maybe Game
> makeMove = undefined
> -- | display the current game board
> showBoard :: Board -> String
> showBoard = undefined
You are allowed to do some implementation work here. Maybe you will have worked a little bit of the code out so that you can see how the parts interact with each other. You are not prohibited from writing code at this stage!
> -- | Create a type class for the interface for the main game interface
> -- so that it can be tested
> class Monad m => Interface m where
> -- ask the current player for their next move
> getMove :: Game -> m Location
> -- send a message to all players
> message :: String -> m ()
> -- send a message to the indicated player
> playerMessage :: Player -> String -> m ()
> -- | all valid locations
> locations :: [Location]
> locations = [Loc x y | x <- [1 .. 3], y <- [1 .. 3] ]
> -- | make moves until someone wins
> playGame :: Interface m => Game -> m ()
> playGame game = do
> playerMessage (current game) $ showBoard (board game)
> case checkEnd $ board game of
> Just (Win p) -> message $ "Player " ++ show p ++ " wins!"
> Just Tie -> message $ "It's a Tie!"
> Nothing -> do
> playerMessage (current game) $ "It's your turn"
> move <- getMove game
> case makeMove game move of
> Just game' -> playGame game'
> Nothing -> error "BUG: move is invalid!"
> instance Interface IO where
> getMove = undefined
> playerMessage = undefined
> message = undefined
> main :: IO ()
> main = playGame initialGame
> -----------------------------
> -- Test Cases
> -----------------------------
Your checkpoint must define tests, either using HUnit and/or Quickcheck. That testing code does not need to be complete, but your test cases for this checkpoint should not just be comments or the names of tests that you plan to do. We will be looking for worked out testing code, either in the form of a unit test (testChecEnd
) or quick check properties (prop_validMove
).
> -- Helper function to make writing test cases easier.
> -- declared, but not yet implemented
> makeBoard :: [[Maybe Player]] -> Board
> makeBoard = undefined
> -- unit tests for the end game
> -- clear from reading this code what is being tested and what it
> -- depends on
> testCheckEnd :: Test
> testCheckEnd = TestList [
> "Win for X" ~: checkEnd winBoard ~?= Just (Win X)
> , "Initial is playable" ~: checkEnd (board initialGame) ~?= Nothing
> , "Tie game" ~: checkEnd tieBoard ~?= Just Tie
> ] where
> winBoard = makeBoard [ [Just X, Just X, Just X ],
> [Nothing, Just O, Nothing],
> [Nothing, Just O, Nothing] ]
> tieBoard = makeBoard [ [Just X, Just O, Just X],
> [Just O, Just X, Just O],
> [Just O, Just X, Just X] ]
> -- a quickcheck property about validity
> prop_validMove :: Game -> Location -> Bool
> prop_validMove game move =
> isJust (makeMove game move) == valid (board game) move
> -- Arbitrary instances. These don't need to be complete yet,
> -- but you should think about what types you will need to be able
> -- to generate random values for.
> instance Arbitrary Game where
> arbitrary = Game <$> arbitrary <*> arbitrary
> instance Arbitrary Player where
> arbitrary = elements [X, O]
> instance Arbitrary Location where
> arbitrary = elements locations
Your design does not need to be perfect. Ask your mentor for help. For example, if this was your project code, you might want to ask your mentor the following question during your checkpoint:
"How can I redesign the structure of the game so that I can be sure that getMove
only returns valid moves?"