CS140 Lab 7 - Code Processing


Important--Read the Following Instructions Before You Begin


Lab Description

You and your friends Rad and Banjolina decide to go into business providing web and cell phone support for reward programs like mycokerewards.com. Users can set up accounts with you that will maintain points. Users can accumulate points by collecting codes from various products (such as bottlecaps and 12-packs, as in mycokerewards.com), and then they can spend the points on various prizes.

Users can enter codes via a web site, or they can register one or more cell phones with their account, and then they can text codes to a given phone number, which will register the points.

Rad is handling the business and marketing end of this endeavor, and Banjolina is doing all of the web programming. Your job is to write the server that maintains information about users, prizes and codes, and talks with Banjolina's web front-end. Since you haven't taken CS360 yet, your server won't do any real networking. Instead, it will communicate via files and standard input.

As with many of our labs, I give you a header file that defines a class, and you have to implement the methods. I have a driver program that you compile with your code, and that will be the final product.

Here's the header, in code_processor.h:

#include <set>
#include <deque>
#include <map>
#include <vector>
#include <iostream>
#include <cstdlib>
#include <fstream>
using namespace std;

class User {
  public:
     string username;
     string realname;
     int points;
     set <string> phone_numbers;
};

class Prize {
  public:
     string id;
     string description;
     int points;
     int quantity;
};

class Code_Processor {
  public:
    int New_Prize(string id, string description, int points, int quantity);
    int New_User(string username, string realname, int starting_points);
    int Delete_User(string username);

    int Add_Phone(string username, string phone);
    int Remove_Phone(string username, string phone);
    string Show_Phones(string username);

    int Enter_Code(string username, string code);
    int Text_Code(string phone, string code);
    int Mark_Code_Used(string code);

    int Balance(string username);
    int Redeem_Prize(string username, string prize);
    
    ~Code_Processor();
    int Write(const char *file);

    void Double_Check_Internals();   /* You don't write this */

  protected:
    map <string, User *> Names;
    map <string, User *> Phones;
    set <string> Codes;
    map <string, Prize *> Prizes;
};

While this looks like a mouthful, it's not too bad. Users store the following data:

Prizes store the following data:

A Code_Processor keeps track of Users, Codes and Prizes. Users are stored in the map Names, which is keyed on their usernames. Phone numbers are stored in the map Phones, which is keyed on the phone numbers, and whose second field points to the user that has registered the cell phone.

There is a set Codes, which stores the codes that have been entered by all users. This set exists so that users can't enter a code more than once. Finally, there is a map Prizes, keyed on the id of each prize.

You'll note that both Names and Phones point to users. In other words, each user has just one User instance, and that is pointed to both in Names and in Phones. If the user has multiple phones, then there will be multiple entries in Phones that point to that user. Moreover, there are two data structures that hold phones -- Phones, which is keyed on the phone number, and the set phone_numbers which is part of the User's data.

Now, you are to write the following methods:


cp_tester.cpp

The program cp_tester.cpp is a front end for code_processor.cpp. You call it with filenames on the command line argument, and it reads files that have commands to execute on a Code_Processor. If a filename is "-", it reads the commands from standard input. The commands are specified on separate lines -- blank lines are ok, and lines that begin with a pound sign are ignored. Lines may not have more than 20 words on them.

Otherwise, the following commands are supported:

  • "PRIZE id points quantity description": Calls New_Prize() with the given arguments. Id is a single word. Description may be multiple words.

  • "ADD_USER username starting_points realname": Calls New_User() with the given arguments. Username must be one word. Realname can contain any number of words.

  • "DELETE_USER username": Calls Delete_User with the given username.

  • "ADD_PHONE username phone-number": Makes the appropriate Add_Phone() call. Both username and phone-number must be one word.

  • "REMOVE_PHONE username phone-number": Makes the appropriate Remove_Phone() call.

  • "SHOW_PHONES username": Makes the appropriate Show_Phones() call.

  • "ENTER_CODE username code": Makes the appropriate Enter_Code() call. The code should be one word.

  • "TEXT_CODE phone code": Makes the appropriate Text_Code() call.

  • "MARK_USED code ...": You can specify up to 19 codes on a line. It will call Mark_Code_Used() on each of these codes.

  • "BALANCE username": calls Balance() and prints the output.

  • "REDEEM username prize": calls Redeem().

  • "DOUBLE_CHECK": calls Double_Check_Internals().

  • "WRITE filename": calls Write() on the given filename. Explanation below.

  • "QUIT": stops reading. You can simply end input too, and that will stop reading.

    Write()

    The Write() method is very important. Whenever you write a server like this one, you should make it fault-tolerant. In other words, you should make it so that it can save its state so that you can terminate the server and start it up again later. The Write() method should save the Code_Processor's state to the given file and return 0. Many students forget to return 0 and the C++ compiler won't complain about it. Should you forget to return 0, then the C++ compiler will return an integer that the cp_tester program will interpret as a bad file write and you will get a "cannot write to file ..." message. Write should return -1 if it can't open/create the file.

    The format of Write() should be as a file that cp_tester can use as input to recreate the state of the Code_Processor. It should only consist of ADD_USER, PRIZE, ADD_PHONE and MARK_USED lines, and when cp_tester is run with the file as input, it should recreate the state of the Code_Processor.

    I don't care about the order or format of the lines, as long as they create the proper Code_Processor when they are fed to cp_tester. My grading program will test your files by using them as input to my cp_tester and looking at the output of my Write() call.


    Some examples

    Let's start with a very simple example:
    UNIX> cp_tester -
    CP_Tester> ADD_USER tigerwoods 0 Tiger Woods
    ADD_USER successful
    CP_Tester> ADD_USER the-donald 100 Donald Trump
    ADD_USER successful
    CP_Tester> PRIZE mp3 40 5000 Free MP3 download from Bapster
    PRIZE successful
    CP_Tester> PRIZE cancun 10000 1 All expense-paid vacation to Cancun
    PRIZE successful
    CP_Tester> WRITE cp1.txt
    WRITE successful
    CP_Tester> QUIT
    UNIX> cat cp1.txt
    PRIZE     cancun     10000      1 All expense-paid vacation to Cancun
    PRIZE     mp3           40   5000 Free MP3 download from Bapster
    ADD_USER  the-donald   100 Donald Trump
    ADD_USER  tigerwoods     0 Tiger Woods
    UNIX> 
    
    I've added two prizes and two users, and then written the server's state to cp1.txt. You'll note that the order of cp1.txt is different from my input. That's fine -- if you use it as input to cp_tester, it will create the same server state. For example:
    UNIX> cp_tester cp1.txt -
    CP_Tester> BALANCE tigerwoods
    0 Points
    CP_Tester> BALANCE the-donald
    100 Points
    CP_Tester> WRITE cp2.txt
    WRITE successful
    CP_Tester> QUIT
    UNIX> cat cp2.txt
    PRIZE     cancun     10000      1 All expense-paid vacation to Cancun
    PRIZE     mp3           40   5000 Free MP3 download from Bapster
    ADD_USER  the-donald   100 Donald Trump
    ADD_USER  tigerwoods     0 Tiger Woods
    UNIX> 
    
    When I called cp_tester, I gave it two command line arguments: cp1.txt and -. So, it first read commands from cp1.txt, which recreated the same state as when I created cp1.txt, and then it read from standard input. When I entered WRITE cp2.txt, it created cp2.txt, which is identical to cp1.txt, since they have the same state.

    Suppose I call cp_tester with cp1.txt and cp2.txt on the command line. I should expect four error messages, since the users and prizes already exist when it tries to process cp2.txt:

    UNIX> cp_tester cp1.txt cp2.txt
    Prize cancun couldn't be added
    Prize mp3 couldn't be added
    ADD_USER the-donald unsuccessful
    ADD_USER tigerwoods unsuccessful
    UNIX> 
    
    This is because cp_tester checks the return values of the New_Prize() and New_User() calls.

    Let's add a few phone numbers and enter some codes. If you check the hashes using djbhash.cpp from the hashing lecture notes, you'll see that they are each divisible by 13 and not by 17, so they are each worth three points:

    UNIX> /home/plank/cs140/Notes/Hashing/djbhash | awk '{ print $1%17, $1%13 }'
    Df18ly81CO1mo4
    11 0
    IDWNZJ20ENkAxP
    2 0
    h0yuKnVD6DvRUu
    11 0
    UNIX> cp_tester cp1.txt -
    CP_Tester> ADD_PHONE tigerwoods 865-974-4400       
    ADD_PHONE successful
    CP_Tester> ADD_PHONE tigerwoods 1-800-Big-Putt
    ADD_PHONE successful
    CP_Tester> SHOW_PHONES tigerwoods
    1-800-Big-Putt
    865-974-4400
    CP_Tester> ENTER_CODE tigerwoods Df18ly81CO1mo4
    ENTER_CODE: Added 3 points to tigerwoods.
    CP_Tester> TEXT_CODE 865-974-4400 IDWNZJ20ENkAxP
    TEXT_CODE: Added 3 points.
    CP_Tester> TEXT_CODE 1-800-Big-Putt h0yuKnVD6DvRUu
    TEXT_CODE: Added 3 points.
    CP_Tester> BALANCE tigerwoods
    9 Points
    CP_Tester> WRITE cp3.txt
    WRITE successful
    CP_Tester> QUIT
    UNIX> 
    
    Each ENTER_CODE and TEXT_CODE call adds three points to tigerwoods' account, giving him 9 points in all. After the WRITE call, cp3.txt looks as follows:

    PRIZE     cancun     10000      1 All expense-paid vacation to Cancun
    PRIZE     mp3           40   5000 Free MP3 download from Bapster
    ADD_USER  the-donald   100 Donald Trump
    ADD_USER  tigerwoods     9 Tiger Woods
    ADD_PHONE tigerwoods 1-800-Big-Putt
    ADD_PHONE tigerwoods 865-974-4400
    MARK_USED Df18ly81CO1mo4 IDWNZJ20ENkAxP h0yuKnVD6DvRUu
    

    The phones have been registered to tigerwoods, his point total has been updated, and the codes have been marked as used. Although I put multiple codes on a MARK_USED line, you don't have to. Just remember the 20-word limit on a line.

    And again, your output does not have to match mine -- it simply needs to create the same Code_Processor.

    Let's take a look at an example where some prizes are redeemed:

    UNIX> cp_tester cp3.txt -
    CP_Tester> ADD_USER billgates 500000 Bill Gates
    ADD_USER successful
    CP_Tester> REDEEM tigerwoods mp3
    REDEEM:       either the user doesn't exist,
                  or the prize doesn't exist,
                  or the user can't afford the prize.
    CP_Tester> REDEEM the-donald mp3
    REDEEM successful
    CP_Tester> REDEEM billgates cancun
    REDEEM successful
    CP_Tester> WRITE cp4.txt
    WRITE successful
    CP_Tester> QUIT
    UNIX> cat cp4.txt
    PRIZE     mp3           40   4999 Free MP3 download from Bapster
    ADD_USER  billgates  490000 Bill Gates
    ADD_USER  the-donald    60 Donald Trump
    ADD_USER  tigerwoods     9 Tiger Woods
    ADD_PHONE tigerwoods 1-800-Big-Putt
    ADD_PHONE tigerwoods 865-974-4400
    MARK_USED Df18ly81CO1mo4 IDWNZJ20ENkAxP h0yuKnVD6DvRUu
    UNIX> 
    
    Since tigerwoods only has 9 points, he can't even afford an MP3 from Bapster. the-donald has no such problem, and billgates can easily afford the Cancun vacation (like he needs it). The updated points for the users and the updated quantities for the prizes have been reflected in the file. Since the quantity of cancun went to zero, it has been removed from the system.

    The Gradescript

    Obviously, the gradescript is going to use cp_tester to test your code_processor.cpp. The output of cp_tester must match mine, and any files created by WRITE must create the same Code_Processor as mine. I will test this by using your file as input to my cp_tester, calling WRITE, and then checking the output file against mine verbatim. I'm doing this to be nice to you, since I'm not making exact formatting matter.


    random_codes.cpp

    The program random_codes.cpp generates random, valid codes.

    Strategy

    Your strategy here should be to first create a code_processor.cpp that implements dummy methods for each method. That way you can compile the program and create a cp_tester. It won't work (except for QUIT), but now you can start programming incrementally.

    The first thing you should do is implement New_Prize(), and then implement the part of the Write() method that creates the file and stores the prizes. Test this by only making PRIZE and WRITE calls in cp_tester.

    Then move onto the others. I implemented these in the following order:

    Although this is a large lab writeup, each of these methods is relatively small. While the grading will of course include the gradescript, the TA's will double-check your destructor by hand.