TDA 452
DIT 142
HT 2016

Functional Programming 2016
Black Jack

1 Introduction

In this exercise you will write an implementation of (a simple variant of) the game Black Jack. By doing so you will learn to define recursive functions and QuickCheck properties. Since you have not really learned how to do input/output yet, we provide you with a wrapper which takes care of those things for you.

Later in the course you will learn how to write graphical user interfaces, and if you want to you can then write such an interface for this program. To cater for this possibility, try to write your program in such a way that you can understand it in a month's time.

2 The game

The game you will implement is a simple variant of Black Jack. There are two players, the “guest” and the bank. First the guest plays. She (he) can draw as many cards as she wants, as long as the total value does not exceed 21. When the guest has decided to stop, or gone bust (score over 21), the bank plays. The bank draws cards until its score is 16 or higher, and then it stops.

The value of a hand (the score) is the sum of the values of the cards. The values are as follows:

The winner is the player with the highest score that does not exceed 21. If the players end up with the same score, then the bank wins. The bank also wins if both players go bust.

3 Your task

Your task is the following:
  1. Read this document carefully.

  2. Compute size hand2 (see Section 3.1) by hand, step by step:

    size hand2
      = size (Add (Card (Numeric 2) Hearts)
                  (Add (Card Jack Spades) Empty))
      = ...
      = 2
    
    Write down this sequence of equations in the beginning of a Haskell file called BlackJack.hs, as a comment.
  3. Implement the Haskell functions and QuickCheck properties listed below. Write them in BlackJack.hs.

  4. While writing the functions (not afterwards), document them.

We will now go through some of the above points in more detail.

3.1 Recursive types

We provide two Haskell files that you should use, Cards.hs and RunGame.hs. You do not need to understand anything about RunGame, but you have to understand most of Cards. That module contains definitions of data types used to model cards and collections of cards (hands). Read through the file so that you know which types you are supposed to use. (You do not yet need to understand the Arbitrary instances in Cards.)

One of the types is more difficult than the others:

data Hand = Empty | Add Card Hand
            deriving (Eq,Show)
A Hand can be either The type is defined in terms of itself; it is recursive. It is easy to create small values of this type, e.g. a hand containing two cards:
hand2 = Add (Card (Numeric 2) Hearts)
            (Add (Card Jack Spades) Empty)
We start with the empty hand, add the jack of spades, and finally add the two of hearts. It is tiresome to write down all 52 cards in a full deck by hand, though. We can do that more easily by using recursion. That is left as an exercise for you, see below.

We can use recursion both to build something of a recursive type (like a deck of cards) and to take it apart. For instance, say that you want to know the size of a Hand. That is easily accomplished using recursion and pattern matching. Note the similarity with recursion over integers:

size :: Num a => Hand -> a
size Empty           = 0
size (Add card hand) = 1 + size hand
At this stage in the course you might not have fully grasped recursive types; that is after all the reason for doing this assignment. To get a better idea of what happens when a recursive function is evaluated, take some time to work with size before you continue. You can for instance evaluate size hand2 by hand, on paper. The result should be 2, right?

The function size is included in the Cards module. You can find more recursive functions in the lecture notes.

3.2 Properties

To help you we have included a couple of QuickCheck properties below. Your functions must satisfy these properties. If they do not, then you know that something is wrong. Testing helps you find bugs that you might otherwise have missed. Note that you also have to write some properties yourself, as indicated below.

The purpose of writing properties is three-fold:

  1. They serve as a specification before you write your functions.
  2. During the implementation phase they help you with debugging.
  3. When you are finished they serve as mathematically precise documentation for your program.
So, to take maximum advantage of the properties, write them before you write the corresponding functions, or at the same time.

3.3 Documentation

The code has to be documented. See the RunGame and Cards modules to get an idea about what kind of documentation is expected of you. Try to follow these guidelines:

3.4 Functions

Write all code in a fresh file called BlackJack.hs. To make everything work, add the following lines in the top of the file:
module BlackJack where
import Cards
import RunGame
This tells the Haskell system that the module is called BlackJack, and that you want to use the functions and data types defined in Cards and RunGame. Download Cards.hs and RunGame.hs and store them in the same directory as BlackJack.hs, but do not modify the files.

You have to implement the following functions.

3.5 Interface

You have barely touched upon input/output in the lectures, so we provide the module RunGame to takes care of those things. All you have to do is to write the functions above, package them together (as explained below), and then call the runGame function with the package as an argument.

To “package up” these functions, write the following code:

implementation = Interface
  { iEmpty    = empty
  , iFullDeck = fullDeck
  , iValue    = value
  , iGameOver = gameOver
  , iWinner   = winner 
  , iDraw     = draw
  , iPlayBank = playBank
  , iShuffle  = shuffle
  }
To run the program, define
main :: IO ()
main = runGame implementation
in your source file, load the file, and run main.

4 Possible extensions

The following is completely optional, but if you want to do more, there are many possibilities: Most of the ideas above require that you program input/output (I/O) yourself. You have seen how to do simple I/O in the lectures. Use the RunGame module as a starting point.

Note that doing something extra will not directly affect your grade, but can of course be beneficial in the long run. Take care to do the compulsory part above before attempting something more advanced, though.