CS303E Project 1: Fall 2024

Instructor: Dr. Bill Young
Due Date: October 14, 2024 at 11:59pm

Copyright © William D. Young. All rights reserved.

Assignment (Rock, Paper, Scissors)

I'm sure you've all played the game of rock, paper, scissors. It's a fun kid's game and also a way to decide arguments. Your task in this assignment is to implement the game. Your program should loop through an arbitrary number of games. In each game, the user specifies one of three possible plays: rock, paper, or scissors. The machine's response is chosen randomly. The two are then compared to see who won, if anyone. The rules are simple: If both choose the same response, it is a draw (there is no winner).

The user's inputs are specified by entering a number: 1 for rock, 2 for paper, 3 for scissors. If the user enters a 4, the program should print some useful statistics about the series of game results, print a goodbye message, and exit. The program should reject any other inputs with an error message; it should not crash.

On exit, the program computes and prints some final statistics about the play:

Print percentages with one digit after the decimal point. Don't compute or print the statistics if no games were completed, since the percentages would involve dividing by the number of games, which is 0. Instead print a message stating no games were completed. See the sample output below.

Note that you'll need to use several of the constructs you've learned in class including a loop and if statements. You also must define at least two functions (in addition to your main() routine). For example, my version defined the following three functions, though you don't have to use these same ones. (ROCK is a symbolic constant; see the discussion in the Programming Tips section below.)

def playName( play ):
    """For each possible move, return the name to print. For example, 
       ROCK prints as 'rock'."""
    ...

def defeats( play1, play2 ):
    """Boolean valued function that returns True iff play1 defeats
    play2."""
    ...

def printStats( plays, wins, losses, draws ):
    """Print the statistics for the sequence of games played."""
    ...

Implementing the Machine Play: The user specifies a play by entering a number. The machine must also make a play. Below is the code to implement the machine play. Be sure to copy this code exactly. (This doesn't count as one of your two required functions.) When you need in your code for the machine to return a play, just call machinePlay().
import random 
import sys

# If no 'oracle' is supplied on the command line, choose machine plays
# randomly.  Otherwise use the supplied oracle.
HAS_ORACLE = ( len(sys.argv) > 1 )
if HAS_ORACLE: MACHINE_ORACLE = sys.argv[1]

# This is a global variable that indexes into the 'oracle' to select
# the next machine play.
machinePlayCounter = 0

def machinePlay ():
    """The machine chooses one of the three moves randomly,
    unless there's an oracle passed on the command line.  Then
    we choose machine plays from that."""
    global machinePlayCounter
    if HAS_ORACLE and machinePlayCounter < len(MACHINE_ORACLE):
        play = MACHINE_ORACLE[ machinePlayCounter ]
        machinePlayCounter += 1
    else:
        play = random.choice(["1", "2", "3"])
    return play
You don't really need to understand the code above. But if you'd like to, here's what it does: If the program is run without an argument on the command line as, for example:
> python3 Project1.py
then each play by the machine is selected at random from the three possible legal responses. If, on the other hand, there is an argument given on the command line, e.g.,
> python3 Project1.py 12313223122131
then plays by the machine are chosen in order from the supplied string; arguments on the command line are always interpreted by Python as strings. In computing, an input like this that drives the computation is sometimes called an oracle. The oracle should contains only characters "1", "2", and "3". If it runs out, subsequent machine plays are chosen randomly.

None of your other code needs to take notice of the possibility of running the program in these two different ways. But being able to do this is useful; the TAs can supply an oracle to force your code to behave deterministically. This makes it much easier to write a grading script that doesn't have to account for random behavior.

Expected Output:

Below is a sample output for this program. You should match this exactly (for these inputs), except that the machine responses are randomly generated. Note that this is run from the command line. You can run yours from your IDE, but the TAs should be able to run it from the command line.

> python Project1.py
Welcome to a game of Rock, Paper, Scissors!

Choose your play:
  Enter 1 for rock;
  Enter 2 for paper;
  Enter 3 for scissors;
  Enter 4 to exit: exit
Illegal play entered. Try again!

Choose your play:
  Enter 1 for rock;
  Enter 2 for paper;
  Enter 3 for scissors;
  Enter 4 to exit: 18
Illegal play entered. Try again!

Choose your play:
  Enter 1 for rock;
  Enter 2 for paper;
  Enter 3 for scissors;
  Enter 4 to exit: 4
No games were completed.
Thanks for playing. Goodbye!

> python Project1.py
Welcome to a game of Rock, Paper, Scissors!

Choose your play:
  Enter 1 for rock;
  Enter 2 for paper;
  Enter 3 for scissors;
  Enter 4 to exit: 1
You played rock; your opponent played rock
There's no winner. Try again!

Choose your play:
  Enter 1 for rock;
  Enter 2 for paper;
  Enter 3 for scissors;
  Enter 4 to exit: 1
You played rock; your opponent played scissors
Congratulations, you won!

Choose your play:
  Enter 1 for rock;
  Enter 2 for paper;
  Enter 3 for scissors;
  Enter 4 to exit: 1
You played rock; your opponent played paper
Sorry, you lost!

Choose your play:
  Enter 1 for rock;
  Enter 2 for paper;
  Enter 3 for scissors;
  Enter 4 to exit: 4
Game statistics:
  Games played: 8
  You won:      1 (12.5%)
  You lost:     1 (12.5%)
  No winner:    6 (75.0%)
Thanks for playing. Goodbye!

>
BTW: the stats shown aren't correct because I omitted some games that were redundant.

Turning in the Assignment:

The program should be in a file named Project1.py. Submit the file via Canvas before the deadline shown at the top of this page. Submit it to the assignment project1 under the assignments sections by uploading your Python file. Make sure that you following good coding style and use comments.

Your file must compile and run before submission. It must also contain a header with the following format:

# File: Project1.py
# Student: 
# UT EID:
# Course Name: CS303E
# 
# Date: 
# Description of Program: 

Programming Tips:

Choose the correct type: The user inputs a number (1, 2, 3, 4), but it's read as a string. You could convert it to a int, but there's no reason to do that because you never do any arithmetic using these inputs. Just leave them as strings.

This is a common feature of programming: you need to choose the appropriate type for a value. Think about how it's going to be used. Don't make unnecessary conversions.

Using literal constants: If you are using the same literal data multiple times, it's a good idea to define it as a constant. For example, this program uses '1' to represent rock, '2' for paper, and '3' for scissors. It would be easy to forget this, or to type 1 when you mean '1'. A way to reduce such errors and make your code easier to read is to use symbolic constants. For example:

ROCK = '1'
PAPER = '2'
...
Then, we know for example that play1 defeats play2 if:
   play1 == PAPER and play2 == ROCK
among other possibilities. By convention, you should put your symbolic constants near the top of your program and use uppercase names. (From Python's perspective, they're just variables and you could re-assign them; the uppercase convention reminds you not to do that.)

Another place to use symbolic constants is for messages output by your program. If you define them once near the start of your program, you won't risk mistyping them later (as often happens):

GOODBYE_MESSAGE = "Thanks for playing. Goodbye!"
Then, instead of
   print( "Thanks for playing. Goodbye!" )
you'd type
   print( GOODBYE_MESSAGE )
That way, if you decide later to change the message (because you noticed that you put two spaces after the period instead of one) you just have to fix it in one place. Strictly speaking, there's no need to do this for a message you're only printing once, but it's probably still a pretty good idea. That way, you have all of the messages collected in one place and it's easy to change one or more without searching through the code. This may not seem so useful for a small program; it's absolutely crucial on large projects.