COSC 140 -- Lab 6


The format of this lab

I have written a header file, candyCrush.hpp and a program candyCrushTest.cpp that uses the class/methods defined in that header file. Your job is to create the file candyCrush.cpp, which implements the methods defined in candyCrush.hpp.

You are allowed to add additional protected methods to candyCrush.hpp but you may not change the function prototypes of the existing methods nor may you delete any of them. You are not allowed to modify candyCrushTest.cpp. You will turn in candyCrush.hpp and candyCrush.cpp, and the TA's will compile and test them with the candyCrushTest.cpp that is in the lab directory.


Candy Crush

Candy Crush is a match-based puzzle game that awards points for collapsing columns or rows of contiguous candies. Below is a sample Candy Crush screen shot:

One of the possible matches would be the two yellow-colored toffee candies at the beginning of the first row of the diagram and another would be the two purple-colored candies at the end of the first row of the diagram. There are other matches you could make in the columns, but for this lab we are going to focus on row matches. Specifically you are going to use an STL list to implement a single row of a simplified Candy Crush like game.

It would be too complicated to create fancy graphics for this lab assignment, so instead you will be using a list of strings to represent flavors of candy. In Part A you will write code to randomly generate the first candy sequence, put it in the list, and print out the portion of the list that you filled. In Part B you will generate enough candy sequences to completely fill a 12 item row of candies. In Part B you will also write code to allow the user to select a sequence of candies and assign points based on the length of the sequence. Finally in Part C you will remove the candy sequence selected by the user from the row, slide the remaining candies to the left, and fill the right side of the row with new candies.


Input

Your program will read from stdin. Here is some sample input and how to interpret it:

256  10     // Seed for the random number generator and length of a candy crush row
grape cherry orange lemon toffee    // The candy flavors
20 10  // The probability for each possible candy sequence length 
10 20
25 30
20 50
15 70
15 120
0  150
0  180
0  210
0 300
You will be using a random number generator for this lab so the first input is the seed you will use to the random number generator. It must be an integer.

The second input on the first line is the length of the candy crush row and must be a non-negative integer between 1 and 100.

The second line of the input will be a list of the candy flavors that will be used in the game.

The remaining lines of the input will be the probabilities of generating a flavor sequence of a particular length and the points they are worth. The sequence lengths are not explicitly given. Your program should assume they begin with 1. Hence in the above input there is a 20% chance of generating a sequence of length 1 and it is worth 10 points if a player selects a sequence of length 1. Similarly there is a 25% chance of generating a sequence of length 3 and it is worth 30 points when a player selects it. You must provide a probability and point value for every sequence length up to the row length, since it is possible to have a row that consists only of one flavor. Note that there is no chance of generating a sequence of length 7-10. However, suppose you generate a "toffee" sequence of length 4 and then generate a "toffee" sequence of length 5. You will end up with a contiguous "toffee" sequence of length 9.

The probabilities must add up to 100 and must be non-negative integers.


candyCrush.hpp

Take a look at the header file candyCrush.hpp:

class candyCrush {
public:
  candyCrush(const string &inputFile);
  int getRowLength() const;
  int getScore() const;
  void printCandy() const;
  int play(int choice);		
protected:
  list<string> candy;
  vector<string> flavors;
  vector<int> points;
  vector<int> probabilities;
  int score;
  int rowLength;
};
	    

You must minimally implement this public API. You are free to add additional protected methods and instance variables to help with your implementation. The method descriptions are as follows:

candyCrush(const string &inputFile): This constructor function sets up your initial game state. In this constructor you will generate a random sequence of candies and assign them to your candy list. You will do this as follows:

  1. You will read the input and assign it to the appropriate instance variables. Here are a few additional instructions about what to do with input:

  2. You will then perform the following operations to generate the candy sequence and fill the list:
    1. use rand() to generate a random number between 0 and n-1 to choose a candy where n is the number of candy flavors. Assign numbers to the candy flavors in the order they are presented to you in the input. For example given the sample input shown above, assignment of numbers to candies should be as follows:
      0: grape
      1: cherry
      2: orange
      3: lemon
      4: toffee
      
    2. use rand() to generate a random number between 0 and 99 to choose the length of the candy sequence. Find the range in the probabilities vector which contains this number. Add 1 to the index associated with that range and you have the length of your sequence. The random number must be less than the cumulative probability associated with the index for that index to be chosen. For example, suppose your probabilities vector is:
      0: 20 
      1: 30
      2: 55
      3: 75
      4: 90
      5: 100
      
      and your random number is 90. Then the index you will choose is 5, not 4, and your sequence length will be 6. If you want to make life easier on yourself, you can start storing your probabilities at index location 1 rather than 0, so that you don't have to remember to add 1 to the index to get your sequence length.
    3. If the sequence length is k, add up to k strings of the flavor you generated in step 1 to the candy list. If adding the full k strings would cause you to overflow the row, then stop when you have filled the row.
  3. int getRowLength() const: Returns the length of the candy crush row.

    void printCandy() const: Prints the candy crush row as 8 flavors per row, left justified, in fields 10 characters wide.

    int play(int choice): choice is an integer from 0 to rowLength-1 indicating which candy flavor the player selected. The test program error checks rowLength so you do not have to do so yourself. The return value is the number of points scored by the player on this turn. This method must:

    1. Find the candy list element corresponding to the player's choice.
    2. Compute the length of the flavor's sequence and add the points for that sequence to the user's score.
    3. Delete the strings in that sequence from the candy list
    4. If the sequence is of length k, then starting at the end of the candy list, add k flavors to the end of the list using the same strategy you used to generate the initial list (i.e., generate a flavor and a sequence length, add that many flavors to the end of the list, and repeat until the row is full). You probably want a separate method named something like fillCandyList to fill the list with candy.

    play() does not print anything. There are commands you can use in the test program to print the candy crush row or the player's current score.

    int getScore(): Returns the players current score.


Testing Your Program

I've written a main() procedure in candyCrushTest.cpp. It needs to be compiled with your implementation of candyCrush.cpp. You run it as follows:

./candyCrushTest inputFile

Then it accepts commands on standard input:

The Gradescript

The gradescript is in an incremental format. The test cases are laid out as follows:

The grading programs are in /home/ssmit285/public/gradescripts/cs140_bvz/lab6 as gradeall and gradescript, like usual. Your executable must be named candyCrushTest for you to run it, and it will flag you wrong if it doesn't find that file.

Don't go assuming that the test cases are error-free either. I've purposefully hidden a few faulty/bad examples (14 in total) in the gradescripts from 31 to 100. Just like the previous lab assignments, you can use the gradescripts and solution example to debug until you match outputs. If you run into an infinite loop, the gradescript will auto-fail that particular test case after 2 seconds of run time.


Examples

The following example interaction with the test program uses this input file:

157  10   
grape cherry orange lemon toffee
20 10 
10 20
25 30
20 50
15 70
10 120
0  150
0  180
0  210
0 300
Here is a series of sample interactions with the test program and the output the test program will produce. In each case I have highlighted the flavor that the user has chosen:
UNIX> ./candyCrushTest input.txt    
PRINT
lemon     grape     grape     grape     grape     grape     grape     orange    
orange    cherry    

CHOOSE 0
you scored 10 points
The player chose index location 0, which corresponds to "lemon". There is a sequence of 1 lemon flavors here so the player receives 10 points (note that in the input file, a sequence of length 1 is worth 10 points).
PRINT
grape     grape     grape     grape     grape     grape     orange    orange    
cherry    grape     

CHOOSE 3
you scored 120 points
Notice that after the user chooses the lemon sequence in the above example, that the lemon has been deleted and the remaining entries have been "moved over" one element. Finally a new flavor, cherry, has been added at the end of the row.

The user now chooses a grape candy. This grape candy is in the middle of a sequence of 6 grape candies so the user scores 120 points. The program then deletes this 6 candy sequence, moves the remaining 4 candies to the left, and adds 4 candies at the end of the row to get the following sequence:

PRINT
orange    orange    cherry    grape     lemon     lemon     lemon     lemon     
orange    orange

CHOOSE 9
you scored 20 points
The user now chooses the last candy in the sequence, which is an orange candy. This candy belongs to a sequence of two orange candies and hence the user scores 20 points. The program deletes this sequence of two candies and adds two new candies to the end of the row to get the following sequence:
PRINT
orange    orange    cherry    grape     lemon     lemon     lemon     lemon     
lemon     lemon


SCORE
current score = 150

QUIT
Note that two lemon candies were added at the end of the sequence, so the lemon sequence that was previously 4 candies long is now 6 candies long. Hence it is possible for candy sequences to increase in length, either because additional candies of the same flavor are added at the end of a row, or because candies with the same flavor are both before and after a chosen candy sequence. For example, suppose that you had the following row (I have arbitrarily chosen this row as an example--it was not generated by my candyCrush executable):
PRINT
toffee    toffee    toffee    grape     grape     toffee    toffee    toffee    
toffee    toffee    

CHOOSE 4
you scored 20 points
  
The player chose index location 4, which corresponds to "grape". When the grape sequence is deleted, the two toffee sequences on either side of the grape sequence are concatenated, resulting in a toffee sequence that is 8 candies long:
PRINT
toffee    toffee    toffee    toffee    toffee    toffee    toffee    toffee    lemon     orange
  

Issues With The Random Number Generator

In order to generate to same output as my sample executable, you will need to do two things:

  1. Run your program on a hydra machine. Different machines/OS's will use different random number generators and hence will generate different random numbers from the same initial seed. Hence if you want to match my output, you need to run your executable on a hydra machine. All of the hydra machines run the same OS and hence the same random number generator so you can use any of the hydra machines.

  2. Use the numbers generated by the random number generator in the same way that I used them. Here is the order you should use:

    1. Generate the random number used to select the candy (grape, cherry, orange, etc).
    2. Generate the random number used to select the length of the candy sequence.

  3. You must use srand() to seed the random number generator and rand() to generate your random numbers. If you use a different function, such as lrand48(), then you will get random numbers that are different from mine.

Use Incremental Development

As always you should strive to break the program into parts and code up and test each part before moving to the next part. I coded the constructor, printCandy, and rowLength functions first, then the play and getScore functions, and finally I inserted error checking code. There are a fair number of error checks you need to perform. In order to use the test editor to test your constructor and printCandy functions (use the PRINT command), you will need to provide empty stubs for the remaining methods in the public API.

In lab you will be asked to try to enumerate these error checks. You can run my test executable to see the exact error message that your program should generate.