Exercise - Add behavior to your game
Now that you've added classes and data to your rock, paper, scissors game, it's time to add the last part, which is behavior. You'll add behavior in the form of methods. You'll add methods to your classes, and the end result will be a working game of rock, paper, scissors.
Implement behavior
You've been taught that to build a program in object-oriented programming (OOP) style, first you model, then you code. The modeling produced the output of a table representing what objects, data, and behavior your program seems to consist of. Here's that same table again.
Phase | Actor | Behavior | Data |
---|---|---|---|
Input | Participant | Chooses symbol | Symbol saved as choice on Participant(choice) |
Processing | GameRound | Compares choices against game rules | Result inspected |
Processing | GameRound | Awards points based on result value | Points added to winning Participant(point) |
Processing | Game | Check continue answer | Answer is true, continue, else quit |
Output | Game | New game round or game end credit |
This time, you'll focus on the Behavior
column and populate it with methods that will be added to your classes. Additionally, you'll add code to those methods so they work the way they should.
Here's your code so far. Let's extend it in the following steps:
You should have a file
rock-paper-scissor.py
. If not, then take the following steps:Create the file
rock-paper-scissor
and open the editor:touch rock-paper-scissor.py code .
and add the below code:
class Participant: def __init__(self): self.points = 0 self.choice = "" class GameRound: class Game: def __init__(self): self.endGame = False self.participant = Participant() self.secondParticipant = Participant()
Start a game
The first part of the game involves setting it up, which means to instantiate the game itself and take the game to a point where it's waiting for the participants to act.
Replace the content of
rock-paper-scissor.py
with this code and save the file:class Participant: def __init__(self, name): self.name = name self.points = 0 self.choice = "" def choose(self): self.choice = input("{name}, select rock, paper or scissor: ".format(name= self.name)) print("{name} selects {choice}".format(name=self.name, choice = self.choice)) class GameRound: def __init__(self, p1, p2): p1.choose() p2.choose() def compareChoices(self): print("implement") def awardPoints(self): print("implement") class Game: def __init__(self): self.endGame = False self.participant = Participant("Spock") self.secondParticipant = Participant("Kirk") def start(self): game_round = GameRound(self.participant, self.secondParticipant) def checkEndCondition(self): print("implement") def determineWinner(self): print("implement") game = Game() game.start()
You've added the methods from your table to each object. The changes you've made can be expressed by a table so it's easier to see what behavior led to what method being added.
Behavior Method Actor Chooses symbol choose() Participant Compares choices compareChoices() GameRound Awards points awardPoints() GameRound Check continue answer checkEndCondition() Game Game end credit determineWinner() Game Most of the behavior in the preceding table corresponds to methods with similar names. The exception is Game end credit, which becomes
determineWinner()
. The reason is that as part of ending a game, it's nice if you can check who won and print out that information. It's up to you if you want to name this method something else.Run the code by invoking
python3
:python3 rock-paper-scissor.py
The program produces an output like so:
Spock, select rock, paper or scissor:
That output means the program is waiting for you to act.
Ensure the program works by selecting
rock
and then selecting Enter. Selectpaper
, and select Enter again:Your output resembles this text:
Spock, select rock, paper or scissor: rock Spock selects rock Kirk, select rock, paper or scissor: paper Kirk selects paper
Note
You can find the solution for this exercise at Start a game - solution code.
Implement rules
From the problem description, you read that certain choices beat other choices. For example, rock beats scissors, scissors beats paper, and so on. It's tempting to write code that looks like so:
if choice1 == "rock" and choice2 == "scissor":
return 1
elif choice1 == "paper" and choice2 == "scissor":
return -1
else:
# something else
# and so on
It results in a substantial amount of code written and becomes a bit unwieldy. What if the game needs to expand its rule set, which might make the code even harder to maintain?
Fortunately, there's a better way. A better approach is to think about the rules as a matrix. The idea of using a matrix is to express what combination wins over other combinations. A winning move gets a 1
, a draw gets a 0
, and a losing move gets a -1
. The following matrix is for rock, paper, scissors:
Choice | Rock | Paper | Scissors |
---|---|---|---|
Rock | 0 | -1 | 1 |
Paper | 1 | 0 | -1 |
Scissor | -1 | 1 | 0 |
You can implement the preceding table in Python by using a multidimensional array, like so:
rules = [
[0, -1, 1],
[1, 0, -1],
[-1, 1, 0]
]
rules[0][1] # Rock vs Paper = -1, Paper wins over Rock
Locate the
Participant
class and add the methodtoNumericalChoice()
:def toNumericalChoice(self): switcher = { "rock": 0, "paper": 1, "scissor": 2 } return switcher[self.choice]
The preceding method will convert your command-line string input to an integer. It will make it easier to determine who won a round.
Tip
Be sure to position your cursor to indent the code correctly.
Locate the
GameRound
class, and update thecompareChoices()
method:def compareChoices(self, p1, p2): return self.rules[p1.toNumericalChoice()][p2.toNumericalChoice()]
The code will enable you to compare two choices and determine the winner.
In the same class, add the method
getResultAsString()
:def getResultAsString(self, result): res = { 0: "draw", 1: "win", -1: "loss" } return res[result]
This method will help determine the result and print an easy-to-understand text to the screen.
Still in the same class, replace the contents of
__init__()
with this code:def __init__(self, p1, p2): self.rules = [ [0, -1, 1], [1, 0, -1], [-1, 1, 0] ] p1.choose() p2.choose() result = self.compareChoices(p1,p2) print("Round resulted in a {result}".format(result = self.getResultAsString(result) ))
The preceding code introduces the field
rules
, which contains an implementation of the rules for rock, paper, scissors. Additionally, the call toself.compareChoices()
compares the two choices made. Lastly, there's a row that prints reader-friendly results to the screen.
Note
You can find the solution for this exercise at Implement rules - solution code.
Score the game
Scoring the game is about assigning points to the correct player after play has ended. The winning player gets one point, a draw, or no points in the case of a loss.
Locate the
Participant
class, and add the methodincrementPoint()
:def incrementPoint(self): self.points += 1
Tip
Introducing a method to change an attribute is the first step toward encapsulation. If you want, try changing the
points
field to__points__
to hide the member from the outside to practice encapsulation.Locate the
GameRound
class, and add the following code to the end of the__init()__
method:if result > 0: p1.incrementPoint() elif result < 0: p2.incrementPoint()
Note
You can find the solution for this exercise at Score game - solution code.
Add a continuation query
A continuation query is a question at the end of the game round asking the player if they want to continue. If the user chooses not to continue, it can output the current results and the winner, if any.
Locate the
Game
class, and update the methoddetermineWinner()
:def determineWinner(self): resultString = "It's a Draw" if self.participant.points > self.secondParticipant.points: resultString = "Winner is {name}".format(name=self.participant.name) elif self.participant.points < self.secondParticipant.points: resultString = "Winner is {name}".format(name=self.secondParticipant.name) print(resultString)
In the same class, update the method
checkEndCondition()
:def checkEndCondition(self): answer = input("Continue game y/n: ") if answer == 'y': GameRound(self.participant, self.secondParticipant) self.checkEndCondition() else: print("Game ended, {p1name} has {p1points}, and {p2name} has {p2points}".format(p1name = self.participant.name, p1points= self.participant.points, p2name=self.secondParticipant.name, p2points=self.secondParticipant.points)) self.determineWinner() self.endGame = True
In the same class, replace the code for the
start()
method with this code:def start(self): while not self.endGame: GameRound(self.participant, self.secondParticipant) self.checkEndCondition()
Save the file.
Run the command
python3 rock-paper-scissor.py
to test out your program:python3 rock-paper-scissor.py
Select
rock
andpaper
as inputs, and entern
when you're asked to continue.Your output looks similar to the following text:
Spock, select rock, paper or scissor: rock Spock selects rock Kirk, select rock, paper or scissor: paper Kirk selects paper Round resulted in a loss Continue game y/n: n Game ended, Spock has 0, and Kirk has 1 Winner is Kirk
Note
You can find the solution for this exercise at Continuation query - solution code.
Congratulations, you did it! You've implemented an OOP version of the game rock, paper, scissors.