CIS 552: Advanced Programming

Fall 2017

  • Home
  • Schedule
  • Homework
  • Resources
  • Style guide
  • Syllabus

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:

  1. What modules will comprise your project?
  2. What data structures will you use to model the your project?
  3. What functions will you need to write? What are their type signatures?
  4. What testing will you do?
  5. 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?"

Design adapted from Minimalistic Design | Powered by Pandoc and Hakyll