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))
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