Rock, Paper Scissors in Python: Part II,  Building a GUI

Rock, Paper Scissors in Python: Part II, Building a GUI

In our first article, we designed a Rock, Paper, Scissors game that works from the keyboard. It printed out messages and shows the total score when you quit. But even a game this simple will benefit from a graphical user interface (GUI). Here we show how to use the Tkinter toolkit to create that simple interface. Specifically, we are going to use the tkinter.ttk toolkit, because it has a nice LabelFrame widget to hold the three RadioButtons.  It looks like this:

You can select the three possible plays from the radio buttons in the “Plays” LabelFrame, and then click on the Play button to have the program player select its move and tell you who won.  The messages from the program are shown in the Listbox on the right, showing what the Auto player has selected, and who wins. The total score for the two players is shown in the blue label above the Listbox.

Creating the GUI

For simple programs like this, we usually create a Builder class with a build method and a few other methods as needed. Here the only method we’ll need is the playit method, that triggers one round of play.

 Here is the program’s outline:

import tkinter as tk
from random import randint
from tkinter import *
from tkinter.ttk import *

class Builder():
    # play one game
    def playgame(self):
        # players play game

    # create window     
    def build(self):
       root = tk.Tk()
       root.geometry("300x200")
       root.title("Rock paper scissors")
        #UI creation code   
        mainloop()     # start the window

# Program starts here
if __name__== "__main__":
    bld = Builder()
    bld.build()

Adding GUI Controls

We use the Grid layout to arrange the GUI widgets, with the LabelFrame and Play  button in column 0 and the score label and Listbox in column 1. The three radio buttons are inserted into the label frame inside their own three line grid.

The only sneaky part is the way the Radio Buttons work. All of radio buttons in a group use the same variable to tell you which button has been selected. That variable is an IntVar, an integer wrapped in an object that is required by the underlying tk toolkit. So, when you create each Radiobutton, you have to pass it a reference to that IntVal variable, and the value that that button will pass to the IntVar, usually small integers, such 0, 1 and 2.

The design question we have to solve is where that IntVar variable will be located so each of the RadioButtons can access it. The obvious place is right inside the radio button code itself. So we derive a new ChoiceButton from Radiobutton that contains the class level variable gvar. Now all instances of the ChoiceButton have access to that same variable, and can change it to 0, 1 or 2 depending on which button is clicked. Here’s the whole new ChoiceButton class.

class ChoiceButton(Radiobutton):
    gvar = None  # will be set to an IntVar

    def __init__(self, rt, label, index):
        super().__init__(rt, text = label,
                         variable =ChoiceButton.gvar,
                         value=index)
        self.text = label
        self.index=index

When we create the GUI, we will set that gvar to an IntVar. The complete GUI code is

def build(self):
    root = tk.Tk()
    root.geometry("300x200")
    root.title("Rock paper scissors")
    # create a label frame for the radio buttons
    lbf = LabelFrame(root, text="Plays")
    lbf.grid(row=0, column=0, rowspan=3, pady=10, padx=10)
    
# create 3 radio buttons, but set none as selected

    ChoiceButton.gvar = IntVar()
    ChoiceButton.gvar.set(-1)   # none selected
    ChoiceButton(lbf, "Paper", 0).grid(row=0, column=0, sticky=W)
    ChoiceButton(lbf,"Rock", 1).grid(row=1, column=0,sticky=W)
    ChoiceButton(lbf, "Scissors", 2).grid(row=2,column=0, sticky=W)

    # create Play button - calls playgame method
    playButton = Button(root,text="Play", command=self.playgame)
    playButton.grid(row=3,column=0,pady=20)

    # create score label
    self.scoreLabel = Label(text="scores", foreground="blue")
    self.scoreLabel.grid(row=0, column=1)

    # create listbox
    self.mesgList = Listbox(root, width=30)
    self.mesgList.grid(row=1, column=1, rowspan=3)

    # create two players
    self.player1 = Player("You")
    self.player2 = AutoPlayer("Auto", self.mesgList)
    mainloop()

Note that we use the same Player and AutoPlayer classes as in the previous example;. The only difference is that the AutoPlayer adds a message to the Listbox instead of printing it on the console:

class AutoPlayer(Player):
    def __init__(self, name, list):
        super().__init__(name)
        self.list = list

    def playit(self):
        playval = randint(0, 2)
        self.play = Player.moves[playval]
        self.list.insert(END, self.name + " selects " + self.play)
        self.list.yview(END)

The Player class differs only in its playit method, which obtains the selected Radiobutton from the index in gvar. Note the yview(END) method on the Listbox, which scrolls to the bottom of the list so that the last line always shows.

# play one game
def playit(self):
    playval = ChoiceButton.gvar.get()
    index  = int(ChoiceButton.gvar.get())
    self.play = Player.moves[index]

And the playGame method of the Builder class differs only in that the winner is added to the Listbox, and the label is updated to show the current score:

def playgame(self):
   self.player1.playit()    # read buttons
   self.player2.playit()    # calc random play  
# compute winner
   winner = Winner(self.player1, self.player2)
   self.mesgList.insert(END, winner.findWin())  # print winner
   self.mesgList.yview(END) #move to bottom of list 
# show current score
   self.scoreLabel.config(text="You: "
                            +str(self.player1.wincount)+"--- Auto: "
                            +str(self.player2.wincount))

Conclusion

The differences in the console game and the GUI version are pretty small. The Player class gets your play from the radio button selected, and all the messages are added to the Listbox. The running score is changed in the scoreLabel each time.

All of the code for this example can be found in Github at jameswcooper/articles/rockpaper

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s