Project Checkpoint #1: Types and Tests
For your first checkpoint, you will meet with your project mentor in class 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 the 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
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 these functions to write tests, so include enough to give 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
> -- | update the board after the current player
> -- makes a move at a particular location
> makeMove :: Game -> Location -> Maybe Game
> makeMove = undefined
> -- | display the current game board
> showBoard :: Board -> String
> showBoard = undefined
> -- | query the current player for a move
> getMove :: Game -> IO Location
> getMove = 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!
> -- | all valid locations
> locations :: [Location]
> locations = [Loc x y | x <- [1 .. 3], y <- [1 .. 3] ]
> -- | make moves until someone wins
> playGame :: Game -> IO ()
> playGame game = do
> print $ showBoard (board game)
> case checkEnd $ board game of
> Just (Win p) -> print $ "Player " ++ show p ++ " wins!"
> Just Tie -> print $ "It's a Tie!"
> Nothing -> do
> print $ "Player " ++ (show (current game)) ++ "'s turn"
> move <- getMove game
> case makeMove game move of
> Just game' -> playGame game'
> Nothing -> error "BUG: move is invalid!"
> main :: IO ()
> main = playGame initialGame
Your checkpoint must define tests, either using HUnit or Quickcheck. That testing code does not need to be complete, but you do need to have an extensive specification of the tests that you will write.
> -- Helper function to make writing test cases easier
> makeBoard :: [[Maybe Player]] -> Board
> makeBoard = undefined
> -- unit test for the end game
> 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
> 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 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?"