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?
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.
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 these functions to write tests, so include enough to give a good specification of your project's execution.
> -- | is this location a valid move for the player
> valid :: Board -> Location -> Bool
> valid = undefined
> -- | update the board after the current player
> -- makes a move at a particular location
> makeMove :: Game -> Location -> Maybe Game
> makeMove = 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
Your checkpoint must define tests, either using HUnit or Quickcheck. That testing code does not need to be complete, but your test cases should be more than just 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 test 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
> 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 should think about what types you will need to be able
> -- to generate random values for.
Your design does not need to be perfect. Ask your mentor for help. For example, if this is 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?"