Tag: Programming

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

Advertisement
Rock, Paper, Scissors Game in Python: Part I

Rock, Paper, Scissors Game in Python: Part I

You probably have played the little hand game Rock, Paper, Scissors one in a while. Essentially two players bring up a fist, a flat hand ot two fingers to represent rock, paper or scissors. The winner is based on these three simple rules:

  • Rock breaks scissors
  • Scissors cuts paper
  • Paper covers rock

In the case of a tie, no one wins.

This little game is easy to create in Python using the keyboard to enter R, P or S, or Q to quit. The second player is the program itself which uses a random number generator to choose one of the three outcomes. This little game is sometimes given as an early assignment, but beginners usually miss some of the really clever features of Python that can make the game more elegant and succinct. We’ll illustrate tuples, classes and subclasses, and dictionaries in this simple article.

We can start by using a tuple to represent the three possible moves:

moves=("P", "R", "S")   #tuple of legal play characters

These are the only three characters that the player should be allowed to enter. So we can create a simple entry loop that repeats until one of those three characters is entered. We use the upper method to make sure the entry is uppercase. Note that the code “play in moves” returns True if the character entered is P, R or S and otherwise returns False.

legal = False
while not legal:
    play = input("enter play (P, R, S): ").upper()
    legal = play in moves 

 And, if you want to allow the player to enter “Q” to quit the game you could check for that Q and set a quit flag:

def playit(self):
    legal = False
    while not legal:
        play = input("enter play (P, R, S): ").upper()
        if play=="Q":
            quit = True
        legal = play in Player.moves or quit

 As you can see, this is now a function.  But why not make it part of a class?

You may recall that classes are a major part of Python. Classes are components that can contain data, and can exist in many instances with differing data in each.

A Player class

Our complete player class has the name of the player and a play variable containing the latest play. It also keeps the state of the quit flag and the legal flag.

class Player():
    moves=("P", "R", "S")   #tuple of legal play characters

    def __init__(self, name):
        self.name = name    #initialize name,quit, and count
        self.quit = False
        self.wincount = 0   #counter of wins

    def playit(self):
        legal = False
        while not legal:
            self.play = input("enter play (P, R, S): ").upper()
            if self.play=="Q":
                self.quit = True

            legal = self.play in Player.moves or self.quit
 # the number of wins is counted here
def countWin(self):
    self.wincount += 1
 

So, as you can see, the Player class has a playit method which sets the play variable to P, R or S. It also can be set to Q, and the quit flag indicates that this is not a real play. Note that the moves tuple is a class variable. This means it is part of all classes of this type and you can access it by Player.moves.

But, what about the other player? It is the computer program itself, and it uses the randint method to select 0, 1 or 2 and these indices indicate which of the three plays has been choses. So, we need another class like Player for the computer player.

As you may recall, you can derive new classes from old ones, so we create an AutoPlayer class derived from Player. It only contains the new playit method. All the other variables and methods are in the base Player class. So, our simple AutoPlayer class is just

class AutoPlayer(Player):
    def __init__(self, name):
        super().__init__(name) # pass the name into the parent class

    def playit(self):
        playval = randint(0, 2)     # select 0, 1 or 2
        self.play = Player.moves[playval] # get that play from the tuple
        # print out what it has selected
        print(self.name + " selects " + self.play)

So, now, we see how we can write a loop for you to play the game:

player1 = Player("You")
player2 = AutoPlayer("Auto")
while not player1.quit: #loop until a 'Q' is entered
    player1.playit()
    if not player1.quit:
        player2.playit()

Picking a Winner

But how do we know who won? This is really simple, since there are only 3 cases (plus the tie) to check for.  We can create a Dictionary of these cases and the resulting message you print out:

game= {"RS": "rock breaks scissors",
       "SP": "scissors cuts paper",
       "PR": "paper covers rock"
       }

We only need to check for ties and check for who wins, by combining the string for the two players.We do all this within a Winner class which then returns the outcome:  First we initialize the variables:

class Winner():
    game= {"RS": "rock breaks scissors",
           "SP": "scissors cuts paper",
           "PR": "paper covers rock"
           }

    def __init__(self, p1, p2):
        # copy variables into class
        # to make this simpler to read
        self.p1 = p1
        self.p2 = p2

Note that the game dictionary is also a class variable.

Then, the findWin method checks for ties and if there is no tie, it calls checkWin to check if p1 beat p2 or p2 beat p1:

# A winner matches on of the three
# game dictionary entries abouve
# we check p1+2 and p2+p1
def checkWin(self, p1, p2):
    # here are the two outcomes
    # for both player orders
    match1 = p1.play + p2.play
    match2 = p2.play + p1.play
    mesg = Winner.game.get(match1)
    if mesg != None:
        p1.countWin()
        outcome = p1.name + " win -- "+mesg
    else:
        mesg = Winner.game.get(match2)
        p2.countWin()
        outcome =  p2.name + " win -- " + mesg
    return outcome

def findWin(self):
    if self.p1.play == self.p2.play:
       return "Tie, no winner"
    else:
        return self.checkWin(self.p1,self.p2)

Note that Player class contains a countWin method that increments the winner count of that player won. Then, when you finally type ‘Q’ the program can print out the final score.

player1 = Player("You")
player2 = AutoPlayer("Auto")
while not player1.quit: #loop until a 'Q' is entered
    player1.playit()
    if not player1.quit:
        player2.playit()
        # compute the winner
        winner = Winner(player1, player2)
        print(winner.findWin()) # print winner

# print out the wins for each player
# when you type "q" for quit
print(player1.name, player1.wincount)
print(player2.name, player2.wincount)

The final output

Here is how the program works:

enter play (P, R, S): r
Auto selects R
Tie, no winner
enter play (P, R, S): r
Auto selects S
You win -- rock breaks scissors
enter play (P, R, S): q
You 2
Auto 2

Summary

We used the tuple to check for legal input (P,R or S) and then created a Player class which has a playit method and contains a play variable which contains the charact representing that play. Then we created a derived AutoPlayer class which has the same internal variables and methods, but where the playit method uses a random number function to generate 0, 1 or 2. These are then indexes into the tuple  to find the character representing that play.

Finally, we created a dictionary of the three possible plays and the message describing the winning cases and used the Winner class to find if p1+p2 won or id p2+p1 won. Thus, in this simple example we used the tuple, the dictionary, classes and a derived class to write a simple game program.

The complete code for this and the programs in the following articles is available at GitHub under jameswcooper/articles

Improving the Radiobuttons in Python Qt5

Improving the Radiobuttons in Python Qt5

PyQt5 is an alternative GUI interface for Python that you can use instead of Tkinter. Both systems provide ways to create buttons, listboxes, tables, checkboxes and radiobuttons. PyQt5 has a number of advantages, though, including built-in Tooltips. Coding for PyQt is in general as easy or easier than for tkinter, but there are some quirks.

One place where you might find it more troublesome is in the way that it handles Radiobuttons. So, in this article, we show you how to make the QRadioButton class a little friendlier.

Now, the idea of a Radiobutton is that you can only select one button, just like old car radios. This interface is now displayed on some screen in your car, rather than by actual push buttons. But the idea is that if you pick one, any other selected button is unselected.

If you have more than one group of Radiobuttons on a page, you want to find a way to group them so that clicking on a member of one group doesn’t affect the other group. In Tkinter, you do this by associating all the members of one group with a single external variable. Then, whatever button you click changes the value of that variable. So, if you have three buttons, the variable might take on the value 0, 1 or 2.

In Qt5, you group the variables by putting them inside a frame or Groupbox. And how do you find out which on was clicked? You have to run through them all to look for which one’s isChecked() status is true. Now, if there are only two buttons this is simple: you only need to check one button. If its status is false, then the other one must be true. 

But what if you have six or more buttons like in this interface for storing cast members in an operetta?

Figure 1: Six radio buttons used in generating a cast table.

In the tkinter approach, you just take the value of that external variable. In PyQt5, you would have to run through them individually or put them in an array (or List) and run through that.

But here is where we have a cooler solution. The QRadioButton is a first class object and you can create derived classes from it really easily. So, all we need to do, is create a RlRadioButton derived from QRadiobutton which contains an index value for each instance of the button. So, we could write

Lead = RlRadioButton(“Lead”, 0)
MinorLead = RlRadioButton(“Minor lead”, 1)

And so forth.  We can then keep the index of the each button in an instance variable: self.index.

class RlRadioButton(QRadioButton):|
    clickIndex = 0    # key of last button selected stored here

    def __init__(self, label, index):
        super().__init__(label)
        self.index= index

Note that the variable clickIndex is a class-level variable There is only one copy of this variable, shared by all six instances of the RlRadioButton class. But how does this variable get set?

It gets set when you click on that RadioButton. We connect the click event for each button to the same method within the button class.

self.toggled.connect(self.onClicked) #connect click to onClicked

The toggled event occurs whenever you click on a button. The event occurs on the button you click on AND on the button which becomes deselected. So, you must check to see whether the button is selected. If it is selected, this method copies the index of that button in that instance into the class variable clickIndex.

#store index of selected button in class variable
def onClicked(self):
    radio = self.sender()
    if radio.isChecked():   #if it is checked, store that index
       
RlRadioButton.clickIndex = radio.getIndex()

So, what is happening is that there are six instances of RlRadiobutton, one for each button. Each instance has a different index number, and if the button for that instance is clicked, it copies its index into the class variable clickIndex they all hold in common. Then, to find out which was selected you simply check the variable RlRadioButton.clickIndex from anywhere in the program.

We illustrate these instances of the RlRadioButton in Figure 2 below, where button 1 was selected.

Figure 2: Three instances of the RlRadioBtton class, showing that they all have access to the same clickIndex class variable.

This shows that while there are three instances of RlRadioButton with three different indexes, there is only one copy of clickIndex that all instances of the RlRadioButton class share.

In Figure 1, you can click on the Status button to see which Radiobutton was selected. The program then fetches that value from RlRadioButton.clickIndex and displays it in a message box using this somewhat verbose message box code:

msg = QMessageBox()
msg.setIcon(QMessageBox.Information)
msg.setText("Role index: "
        + str(RlRadioButton.clickIndex))
msg.setWindowTitle("Status")
msg.setStandardButtons(QMessageBox.Ok )
retval = msg.exec_()

and displays the result in that message box.

Figure 3: The status message box.

So, to conclude, the best way to query a large list of QRadioButtons is by deriving a class which can save the current index and asking the class for the index of the last selected button.