CS-220 Spring 2016 Lab 7 Rock/Paper/Scissors

Background - The Ancient Game

Many of us have played the child's game of "Rock/Paper/Scissors". For complete details and a history of the game, see Wikipedia Rock-paper-scissors. The basics are as follows... for each round, each player chooses either rock, paper, or scissors. If both players choose the same object, the round is a draw - nobody wins. If one player chooses rock, and the other scissors, the rock wins. Scissors beats paper, and paper beats rock.

A game consists of multiple rounds - in our case, a game will consist of 50 rounds. The reason to play multiple rounds is to try to determine your opponent's strategy, and come up with a strategy that will consistently beat your opponent.

We will each code a C file that contains a function to play a rock-paper-scissors game. A game playing infrastructure will be provided, along with some very simple player strategies. You will be able to code your function and test it during the lab. When you are satisified with your strategy, you can copy your player code to blackboard. On Monday, we will conduct a tournament - each player against every other player - to determine who coded the winningest function. We will also run an all-time tournament against students who took CS-220 in previous semesters to find an all-time champion.

Getting Started

  1. Download the lab7.tar.gz tar file, move it to your home directory, and execute tar -xvzf lab7.tar.gz. This will create a Lab_07 sub-directory. Move to that directory.
  2. Run make to build the code.
  3. Run ./round scissors paper to run a round between a player that always chooses scissors and a player that always chooses paper.
  4. Run ./match scissors rock to run an entire match of 50 rounds between a player that always chooses scissors and a player that always chooses a rock.

The "rps" Data Type

We talked about enumerated types last week... now we get to use one. In this lab, we define a type named "rps" which can have one of four values...
Rock, Paper, Scissors, or Unknown.

A variable of type "rps" must have one of those four values. For instance, you can declare a variable like rps guess;, and then assign a value like guess=Paper;, or check the value of guess as in if (guess==Scissors)... The rps type is defined in file rps.h.

There is also function called "fmt_rps" declared in rps.h, and defined in rps.c which returns the character string that describes an rps value. This can be used to print an rps value. For instance...

rps choice;
...
printf("My rock, paper, scissors choice is: %s\n",fmt_rps(choice));

Player Functions

A player function is a function which will play the rock-paper-scissors game. When we play a round of rock-paper-scissors, we choose two player functions, and invoke each one. Each player function must return a rock paper scissors choice (the return type of the player function is an "rps" type.). A single round of rock-paper-scissors consists of invoking two different player functions, comparing the choice of the fist player with the choice of the second player, and figuring out which player won the round. If both players chose the same rps value, for instance, if both chose Rock, then the round is a tie, and neither player wins.

Note that player functions have three arguments, but we won't use these until we learn about match play below.

Four player functions have been provided to you in the Lab_05 directory, as follows:

In general, each player must have the same function prototype... namely
rps player_<XXX>(int round, rps * myhist, rps * opphist)
where <XXX> is a unique player ID.

Playing a Match

In our rock/paper/scissors infrastructure, a match consists of playing 50 rounds of rock/paper/scissors. In match play, each round is usually worth one point. If a player wins a round, one point is added to his score. If there is a tie, then the value of the next round is doubled. So after one tie, if there is a winner in the next round, that winner gets two points. If there is no winner, the stakes get doubled again, so the winner of the third round would get four points. The most points you can get in a round is when the first 49 rounds are a tie, and you win the last round. In this scenario, you get 249 points, and your opponnent gets zero!

Player functions also get more interesting during match play. When playing a match, the three arguments to a player function become more useful.

The first argument is an integer that tells the player function which round is currently being played. The first time the player function is called, the round will be round 0. Then round 1, round 2, and so on, up to round 49.

The second argument is a pointer to a list of the rps values this player function chose for previous rounds, starting with round 0. C allows you to interpret this pointer as either a pointer to a list of rps values, or as an array of rps values. Therefore, if I wanted to check if I chose "Rock" in round 3, I could use either the following code: if (myhist[2]==Rock) ...
Or, I could use lists and pointer arithmetic to do the same thing: if (*(myhist+2)==Rock) ...

The third argument is a pointer to the choices my opponent player function made in previous rounds, which again can be used either as a pointer or an array.

Remember, both myhist and opphist do not contain the choices for the current or future rounds, and are only valid for index 0 to round-1.

Hint: If you want to remember something between calls to your player function, you must declare a variable as static. For instance, if I want to keep track of the number of times I chose "Rock" without looking at myhist, I could define "n_rock" as a static variable and use it as follows:


static int n_rock=0;
...
if (next_rps==Rock) n_rock++;
...
return next_rps;

You may wish to determine, for previous rounds, which player function "won" the round in your player function. You may use the "eval_round" function declared in rps.h and defined in rps.c. The eval_round function takes four arguments; the first two are the rps choice for player 1 and player 2 respectively. The last two arguments are pointers to the names of player1 and player2 respectively. If these last two arguments are NULL, the "eval_round" function does not print any messages. If the name arguments are not NULL, then eval_round will print a message saying who was playing, what their choices were, and who won. In either case, the eval_round returns a value of the "winner" enumerated type (as defined in rps.h), which can have values of either Player1, Player2, or Tie.

Player Registration

Our rock-paper-scissors infrastructure contains a list of all players, called a player registry. Each entry in the registry consists of a player name, and a pointer to a player function. In order to play a round, a match, or participate in a tournament, a player must first be registered. Registration occurs by invoking the "register_player" function declared in rps.h and defined in rps.c. The register_player function takes two arguments

As an example, my LDAP userId is "tbarten1" so I would code a player function called "player_tbarten1", and invoke register_player(player_tbarten1,"tbarten1");.

Making and Submitting your Player Code

Now it's time to actually make some player code. Here are the steps...

  1. Copy player_rock.c to a file called player_<XXX>.c where <XXX> is your ID. For instance, if your ID was tbarten1, you would use the command cp player_rock.c player_tbarten1.c.
  2. Edit your player_<XXX>.c file.
    1. Change the name of the player function in the file from player_rock to player_<XXX>, for instance, player_tbarten1.
    2. Figure out a strategy for playing a rock-paper-scissors match, and replace the code "return Rock;" with a C implementation of that strategy.

      Your strategy can use myhist and opphist, but it cannot return a purely random result. There must be some logic to determine what choice to make.

      Note: Your C file will be included inside the rock-paper-scissor code, so you will not need any "#include" statements in your file unless you are using some very special library functions.

    3. Change the arguments in the invocation of "register_player" from register_player(player_rock,"rock") to register_player(player_<XXX>,"<XXX>");. where <XXX> is your LDAP userId.
      For instance, register_player(player_tbarten1,"tbarten1");.
    4. Note: It is considered a violation of tournament rules to modify or override any functions used by the infrastructure. Students in the past have tried to cheat by creating their own version of the eval_round function, or even coding their own rand function. This will be checked and disallowed before the tournament starts.
  3. Edit the players.h file by adding a line that says
    #include "player_<XXX>.c"

    For instance, #include "player_tbarten1.c"

  4. Run make to create three executable commands - round, match, and tournament. At this point, your player function will available and will be registered when you invoke round, match, or tourney.

When you invoke the round executable, the arguments you need to specify are the registered names of the two players you want to play against each other. So, for instance, to run a round of the rock player against the scissors player, you would run the command ./round rock scissors. Using the example above, to run a round of the "tbarten1" player against the random player, you would run the commmand
./round tbarten1 random.

The match executable has the same conventions as the round executable... it takes two registered player names as arguments, and plays a match between those two players. For instance, to play a 50 round match between the random player and the scissors player, you would use the command
./match random scissors.

Running a Tournament

The tourney executable does not take arguments. It runs a tournament among all registered players by running a match between pairs of players.

The tournament is a double-elimination tournament. That means all players start out in the "winners bracket". A tier of the tournament consists of pairing all players in the winners bracket to play against each other, and all players in the losing bracket are paired against each other. If you are in the winners bracket and win your match, you stay in the winners bracket. If you are in the winners bracket and lose a match, you move to the losers bracket. If you are in the losers bracket and win a match, you stay in the losers bracket. If you are in the losers bracket and lose a match, you are eliminated from the tournament.

If there are an odd number of players in a bracket for a specific tier, one randomly picked player gets a "bye" and is assumed to have "won" his match.

Eventually, the tournament narrows down to a single player in the winners bracket and a single player in the losers bracket. A final match is played against these two players to determine the tournament winner and champion.

Submitting your Player

When you are satisfied with your player function, upload your player_<XXX>.c file on blackboard in the Lab_07 submission area. Submissions will be allowed up to 11:59 PM on Friday, March 18. Late submissions will not be accepted.

Over the weekend, the professor will compile and build a tournament that includes every student submission. During Monday's class, we will run a tournament between all the submissions to see who can code the best rock paper scissor player function!

Lab Report

We're going to give the TA's a break this week... no lab report! Just create and submit your player function before Friday evening and you will get credit for this lab.

All student submissions automatically receive 5 out of 10 points. If your submission compiles and works within the infrastructure, you will receive another 3 points, so you now have 8 out of 10 points. If are in the winners bracket after the first tier, you will receive another point or 9/10. All players in the winners bracket after the second tier get the full 10 points for the lab.