Thursday, 16 May 2013

Python - Rock Paper Scissors inside a GUI

In a previous blog post I explained how to write a program to play Rock Paper and Scissors against a computer. This worked very well, and probably provided hours of entertainment ;-), but it did not look very pretty. Everything was displayed by text scrolling in a shell window.

It would be much nicer if we could put this game into a GUI (Graphical User Interface)

Well it turns out this is easier than you think! There are a few tools available in Python to allow you to program a GUI. In this blog I will show you how to use one particular tool called Tkinter to do this.

We can re-use a lot of code from our RockPaperScissors.py program, so lets start with that as our base code. If you need a copy of the old program you can download it from the link below.

Download RockPaperScissors.py

So how does Tkinter work?

Well in our previous RockPaperScissors.py program we interacted with the program from a shell window. We typed 'R' to select Rock, 'P' for Paper, and 'S' for Scissors. Pressing return set off a chain of events which caused the computer to randomly select one of Rock, Paper or Scissors, which then went on to compare the two and finally declare a winner.

Instead of typing into a text window, by using Tkinter we can assign widgets such as buttons or radio buttons to input our options, and to set the program running.

The program is doing the same thing, but we are communicating with it in a slightly different way.

At first sight, it looks complicated, but it's not.

The first thing we need to do is to import the tkinter libraries into our program. So at the very top of the program, above import random and import sys add the following lines.

from Tkinter import *
from ttk import *



The next thing we need to do is to define our GUI window. Right at the bottom of the RockPaperScissors code type the following.

root = Tk()
root.title ('Rock Paper Scissors')



The first line defines the root window, and the second line adds a title into the window. Rock Paper Scissors seems like a good enough title to me.

Now within our window we need to have a frame. A frame can split the window into different areas. We only need one frame. These next few lines defines how this frame fits within our window. Type the following below the lines you have just added.

mainframe = Frame(root, padding = '3 3 12 12')
mainframe.grid(column=0, row = 0, sticky=(N,W,E,S))
mainframe.columnconfigure(0, weight=1)
mainframe.rowconfigure(0,weight=1)




The first row defines the padding, or space between our frame and window. We may modify this later to improve the look of our game. The next three lines help determine the layout style of the window.

In TKinter there are several methods you can use for layout. We are going to use the grid option in this game. The grid system gives more control than some of the others.

So what is the grid system? Well imagine splitting your window up into columns and rows. The top row would be row 0 and the left column would be column 0. If I wanted to put something such as a button in the top left I would place it in column = 0 and row = 0. If I wanted to put something to the right of this it would be column = 1 and row = 0. Below the original item would be row 1 and column 0. Very similar to a graph axis or map co-ordinates, with top left being row 0 and column 0.

If for example you didn't have anything in row 0, or column 0, then the program would ignore these, until something was put in there. So if I put my first item in row 1 and column 1 that would be in the top left.

Lets do just that now.

I am going to put a label in the top left of the window. A label is a Tkinter widget, that allows you to display text. Probably the simplest of widgets available.

Label(mainframe, text='Player').grid(column=1, row = 1, sticky = W)



Looks complicated right? Well no... remember with programming to break things down into little bits, and that's the same with looking at code.

Label - tells the program you want to add a label widget.
mainframe - you want the widget in the mainframe. We only have one frame in this program, but its feasible you could have others.
text - 'Player', This allows us to input what our label should say. Put your name in here if you want to.
grid - remember we are using the grid layout system. grid and then the items in the bracket allow us to define where we want the label in our grid.
column = 1 - We want to place our widget in column 1
row = 1 - We want to place our widget in row 1
sticky = W Says we want to align our text with the left side of the box. Tkinter uses points of a compass (N,S,E,W) to describe where we want to align our items.

There we go, see I told you it wasn't complicated!

Should we test this now? Well before we do lets comment out everything in our code from While True: to print " "

To do this highlight everything you want to comment out by dragging the mouse over it and then click on Format, and then Comment Out Region. You could highlight the area and press Alt and 3 to do the same thing. The lines commented out will be shown in red.



Why do we do this? Well as I said previously Tkinter calls things in a different way, by using buttons and other widgets. The GUI also has its own eternal loop running, so the program doesn't quit. Therefore we do not need to have a while loop to keep our program running.

lets add root.mainloop() right at the bottom of the program.

root.mainloop()

This is responsible for telling the program to run the GUI.

Press F5 and save the file as RockPaperScissorsGUI.py when asked.

Does your window look like this?



How cool is that? Ok its not massively inspiring, but it is the building blocks for greater things!!

So how should we structure the rest of the GUI?

My plan is underneath the player label we will have the option to chose either Rock, Paper or Scissors. There are a few ways to do this, but I like the option of Radiobuttons. A Radiobutton allows you to select one, and only one, of a few different options. As we only want the Player to select one of Rock, Paper or Scissors sounds like a good option.

So lets sort out the three Radiobuttons.

The radiobuttons use mainly the same options as we saw in the label with three exceptions.

Radiobutton(mainframe, text ='Rock', variable = player_choice, value = 'Rock').grid(column=1, row=2, sticky=W)
Radiobutton(mainframe, text ='Paper', variable = player_choice, value = 'Paper').grid(column=1, row=3, sticky=W)
Radiobutton(mainframe, text ='Scissors', variable = player_choice, value = 'Scissors').grid(column=1, row=4, sticky=W)



Lets look at the Radiobutton widget and see how this varies when compared to the Label widget.

Radiobutton - This has changed from Label to Radiobutton, which is obvious, as we are calling a different type of widget.
text - defines the text next to the Radiobutton so you know what each one refers to.
variable - This is new to Radiobutton. You use the variable to store some information to indicate which Radiobutton you have selected.
value - defines what you store in that variable. We are using the strings Rock, Paper and Scissors.
grid onwards remains the same as for Label and defines where the radiobuttons are positioned. In this case they are all in column 1, but below each other in Row 2, 3 and 4.

Before we run the program to have a look at it we need to create the variable we refer to which is called player_choice. This is the variable which will hold either Rock, Paper or Scissors, depending on the Radiobutton selected.

I try to keep all my variables together, and put them before the start of the layout code. Therefore above the line of code specifying the first label you created put the following line.

player_choice = StringVar()



This defines the variable player_choice, as a String Variable. Tkinter is able to refer back to this variable at any time.

Again press F5 to save and run your program. Does it look like this?



Try clicking on the Radio Buttons. Notice how they light up as you pick each one. The program is really coming together now.

We want to do the same thing for the computer. So try and create the code to show a label named as Computer and to reference a variable called computer_choice for the Radiobuttons. Ensure these labels and buttons are in column 3.

Did your code look like this?

Label(mainframe, text='Computer').grid(column=3, row = 1, sticky = W)
Radiobutton(mainframe, text ='Rock', variable = computer_choice, value = 'Rock').grid(column=3, row=2, sticky=W)
Radiobutton(mainframe, text ='Paper', variable = computer_choice, value = 'Paper').grid(column=3, row=3, sticky=W)
Radiobutton(mainframe, text ='Scissors', variable = computer_choice, value = 'Scissors').grid(column=3, row=4, sticky=W)

Did you also remember to create a new String Variable called computer_choice?

computer_choice = StringVar()


OK we have Radiobuttons and labels for both the computer and for the player. We now need a button to tell the program we have made our choice, and we want to play against the computer.

Lets put the button between the existing columns i.e. into column 2.

Button(mainframe, text="Play", command = play).grid(column = 2, row = 2, sticky = W)



The button widget is again like the other widgets. It is in the mainframe, it has text to label the button and it has some information in the grid section to describe the layout of where we want the button. Additionally it has command = play.

When we press the button we obviously want it to do something, and it is this command statement which allows us to tell the program what we want it to do. In this case we are saying command = play, which tells our program that when the button is pressed we want to run the function called play.

Now in our code, we do not have a play function. Before we started to work on the GUI our program used a while loop to continuously run the program. The play function we need to write is carrying out a lot of the work which was done in our while loop. It requires us to get the human choice, the computer choice and compare them, then declare a winner. Therefore we can modify the while loop to become our play function.

First uncomment out the While loop. To do this highlight the text you commented out earlier, and select Format and then Uncomment Region. Alt and 4 is a the shortcut keys to do this.

Now change
While True:

to

def play():



This creates a function called play using the code from the while loop, as we want this to be the basis of the play function which the button calls when pressed.

Now the first line in our new play function asks you to call the function makeYourChoice, and stores the result of this as a variable called human_choice. However the makeYourChoice function explained to the user what key they should press to make their choice of Rock, Paper or Scissors. We don't need any of that with our program, as we are using the Radio Buttons to do the same task. So we can delete the makeYourChoice function and all its contents.

Instead the humanChoice variable needs to be the choice of the RadioButtons we created earlier. Remember we stored the selected choice in a variable called player_choice? Therefore we should be able to just call this variable and get the information from inside it.

To get the info from inside player_choice we use the get command.

humanChoice = player_choice.get()




This line gets the information stored in player_choice, and stores it in a variable called humanChoice.

The next line calls computerRandom.

We we still want to the computer to pick a random choice, but we also want to store that random choice in the StringVar called computer_choice. This will change the computer Radiobuttons to match the choice the computer has made.

Lets make that change inside of the computerRandom function.

Inside the computerRandom function, after the line randomchoice = random.randomint(0,2) add the following line

computer_choice.set(options[randomChoice])



This line will set the value stored in computer_choice as either Rock, Paper or Scissors depending on the random choice created by the previous line.

That is the only modification we need to make to the computerRandom function so we can go back into the play function.

We see the next two lines are printing the choice of the human and the computer. We don't need these any more, as our choices are displayed graphically in the GUI, so these lines can be deleted.

The result line is the same. We still want to compare the humanChoice against the computerChoice.

Now we get into reporting the result. We don't just want to print to the shell window as we did previously, it would be much nicer if we could report back the result into the GUI.

Well we can by using a Label widget again.

Under where you created the Button widget add a new Label widget

Label(mainframe, textvariable = result_set).grid(column = 1, row = 5, sticky =W, columnspan = 2)



This is the same as the previous Label widgets you have created with two exceptions.

The first is instead of text you have textvariable = result_set. This is because you want the text to change depending on who won. So the label will be populated with whatever text is stored in the result_set variable.

The second change is we have added columnspan = 2. What this is saying is although we have defined our text to be in column = 1, if it doesn't fit, allow it to span into the next column.

OK before we can start to populate the variable result_set we have to create it first.

Head back to the area where all the variables are defined and add

result_set = StringVar() underneath the two already defined.



We can then finish off the play function by populating the results_set variable with the result.

This is done by the command result_set.set("text goes here")

We need to report back a Rock, Paper or Scissors.

By modifying the text which previously printed the result into the Python shell window we can update the label we have just created to report back the result.

To do that instead of printing we need to set the data in the result_set variable.

if result == "Draw":
        result_set.set("Its a draw")
    elif result == "Computer Wins":
        result_set.set("Unlucky you lost!")
    else:  result_set.set("Well done you won!")



Delete the print " " line.

Lets save and test your program by pressing F5.

Does this work as you expected?

One other thing I notice is that the Player doesn't have a choice to begin with. Which is fine, but what happens if the player doesn't choose anything? The program states that the Player is the winner. Hardly seems fair that the player can win without making a choice, so lets think how we can fix that.

There are two ways I can think of immediately.
  • We could ensure the player has a default option picked.
  • We could check to ensure there is an option picked and report back if there isn't.
To set the default option for the player we can set the player_choice variable to be Rock by default.

Under where you set your variables, we will assign Rock to player_choice.

player_choice.set("Rock")




Running the program shows that Rock is chosen by default.

Or if you would prefer modify the play() function to check that a player_choice has been set.

if player_choice.get() != '':

Then indent the remainder of the function adding the following at the end.

    else:
result_set.set("Please choose an option")



I will let you decide which one of those you would like to include in your program, if any!

Hope you enjoyed your first venture into TKinter. I would suggest playing around with it a little so you can see what changes.

For those of you who would like the source code you can down load it from the link below. I have left the code, which I have said delete, commented out so you can see the changes. The code has both options for overcoming the fact the player could win without selecting a choice, but they are both commented out. You can choose which one you want to implement.

RockPaperScissors-GUI.py