Hey guys, in this part of the Kivy series we’re going to implement the Game class. This is the class where most of the game’s logic will sit. But before we start, here’s some info for you.
*****
Book Info
And Now Let’s Move On…
We already created all the screens that our app will be using. In the previous part of the series we added two classes, Slug and Player. We will need the classes to create four slug objects and four player objects.
In our game there will always be four slugs. They will even have names: Speedster, Trusty, Iffy and Slowpoke. Regardless of any settings, there will always be all four.
As far as players are concerned, there may be one, two, three or four players, depending on what we choose in the Settings screen. However, to keep things simple, in our implementation there will always be four instances of the Player class, but if there are fewer players than four, not all of them will be used. Naturally, there are other ways of implementing it, but I decided to do it like that.
Now, the problem is that we need access to the slugs and players in multiple screens. There are widgets that need slug and player info in more than one place, so it would be reasonable to find a place where this info can be stored and can be accessed from any screen. For example player info must be available in the Settings screen, where the names and initial money of each player are set, in the Race screen in the Players’ Stats area, as well as in the Bets screen, Results screen or in the Game Over screen.
So, where are we going to put shared data? There are two major options. We can put the data in the SlugraceApp class. Then we will be able to access it from anywhere in the app. The second option is to put all the shared data in the root widget of our application, so in the class that is returned by the build method of the SlugraceApp class. We’ll be using both approaches to some extent, but the data and slug info will be stored in the root widget class, which is SlugraceScreenManager.
Let’s have a look at the SlugraceScreenManager class again. In the Python file we only have the declaration:
# File name: main.py
...
class SlugraceScreenManager(ScreenManager):
pass
class SlugraceApp(App):
def build(self):
return SlugraceScreenManager()
if __name__ == '__main__':
...
The definition of the class is now completely in the kv file:
# File name: slugrace.kv
<SlugraceScreenManager>:
SettingsScreen:
name: 'settingsscreen'
RaceScreen:
name: 'racescreen'
GameoverScreen:
name: 'gameoverscreen'
Now, as mentioned before, there will be four players and four slugs in the game. Let’s start with the former. In order to create an instance of the Player class, we need to import the Player class to the main.py file first. Then, inside the SlugraceScreenManager class, we will create the objects:
# File name: main.py
...
# We need the Player class to instantiate the four players.
from player import Player
...
class SlugraceScreenManager(ScreenManager):
# Now let's instantiate the four players.
player1 = Player()
player2 = Player()
player3 = Player()
player4 = Player()
class SlugraceApp(App):
...
We instantiated the four players and are ready to use them now. How about the slugs? Well, we actually defined the four slugs in the Race screen already. Have a look:
# File name: race.kv
#:import race race
...
<RaceScreen>:
...
### THE TRACK ###
...
# track
...
# the slugs
# Speedster
Slug:
body_image: 'speedsterBody'
eye_image: 'speedsterEye'
y_position: .875
# Trusty
Slug:
body_image: 'trustyBody'
eye_image: 'trustyEye'
y_position: .625
# Iffy
Slug:
body_image: 'iffyBody'
eye_image: 'iffyEye'
y_position: .375
# Slowpoke
Slug:
body_image: 'slowpokeBody'
eye_image: 'slowpokeEye'
y_position: .125
# winner
...
So, as you can see, the four slugs are there. We just need to tell the SlugraceScreenManager class how to access them. This shouldn’t be difficult. The slugs are defined in the Race screen, so we can use properties in the RaceScreen class to access the slugs. As we look at the implementation of the SlugraceScreenManager class in kv, we can easily access the Race screen by an id. So, indirectly we can access the four slugs in the SlugraceScreenManager class too.
Let’s start by adding the properties in the RaceScreen class:
# File name: race.kv
#:import race race
...
<RaceScreen>:
canvas:
...
# We'll access the four slugs by ids. To this end let's define
# four object properties.
speedster: _speedster
trusty: _trusty
iffy: _iffy
slowpoke: _slowpoke
BoxLayout:
...
### THE TRACK ###
...
# track
...
# the slugs
# Speedster
Slug:
id: _speedster
body_image: 'speedsterBody'
eye_image: 'speedsterEye'
y_position: .875
# Trusty
Slug:
id: _trusty
body_image: 'trustyBody'
eye_image: 'trustyEye'
y_position: .625
# Iffy
Slug:
id: _iffy
body_image: 'iffyBody'
eye_image: 'iffyEye'
y_position: .375
# Slowpoke
Slug:
id: _slowpoke
body_image: 'slowpokeBody'
eye_image: 'slowpokeEye'
y_position: .125
# winner
...
Now we need to modify the SlugraceScreenManager class so that it has access to the slugs:
# File name: slugrace.kv
<SlugraceScreenManager>:
# Here are the properties on the root widget class that we
# will use to access the slugs from outside the Race screen.
speedster: _racescreen.speedster
trusty: _racescreen.trusty
iffy: _racescreen.iffy
slowpoke: _racescreen.slowpoke
SettingsScreen:
name: 'settingsscreen'
RaceScreen:
# Let's add the id to be able to access the Race screen.
id: _racescreen
name: 'racescreen'
GameoverScreen:
name: 'gameoverscreen'
Now, one more thing before we continue. This is not necessary, but as the SlugraceScreenManager class is going to contain all the game data, like the players, the slugs, but also all sorts of other stuff that we are going to add to it in the following parts of the series, let’s rename it Game. Here’s the Python file:
# File name: main.py
...
class Game(ScreenManager):
player1 = Player()
player2 = Player()
player3 = Player()
player4 = Player()
class SlugraceApp(App):
def build(self):
return Game()
if __name__ == '__main__':
...
And here’s the kv file:
# File name: slugrace.kv
<Game>:
speedster: _racescreen.speedster
trusty: _racescreen.trusty
iffy: _racescreen.iffy
slowpoke: _racescreen.slowpoke
SettingsScreen:
name: 'settingsscreen'
RaceScreen:
id: _racescreen
name: 'racescreen'
GameoverScreen:
name: 'gameoverscreen'
Now, the last thing to do is make sure all screens have access to the Game class because they will need player info, slug info and other data defined in it. In case of the Settings, Race and Game Over screens, it’s very easy because you can use the manager property to access the Game class (which is the three classes’ screen manager after all). As far as the Bets and Results screens are concerned, it’s a bit more complicated, but if you look at how their screen manager is embedded in the Race screen and use the parent and manager properties, you will quickly figure it out.
For consistency, let’s add a game property to each of the screens and set it to the Game class. Here’s how you should do it in the Settings screen. Just add the following line of code below the canvas instructions in the SettingsScreen rule:
# File name: settings.kv
#:import settings settings
...
<SettingsScreen>:
canvas:
...
game: root.manager
BoxLayout:
...
Here root refers to the root widget, which is SettingsScreen, and manager is its screen manager, so the Game class.
Here’s the Race screen, which is very similar:
# File name: race.kv
#:import race race
...
<RaceScreen>:
canvas:
...
game: root.manager
speedster: _speedster
trusty: _trusty
iffy: _iffy
slowpoke: _slowpoke
BoxLayout:
...
And here’s the Game Over screen, which is also very similar:
# File name: gameover.kv
#:import gameover gameover
<GameoverScreen>:
canvas:
...
game: root.manager
BoxLayout:
...
And now let’s have a look at how the Bets and Results screens are embedded in the Race screen. Or rather their screen manager:
# File name: race.kv
#:import race race
...
<RaceScreen>:
...
BoxLayout:
...
### THE BETS ###
BoxLayout:
...
RaceScreenManager:
What we want to do is get access to the RaceScreen class and next to its manager. As you can see in the simplified hierarchy above, RaceScreen is RaceScreenManager’s parent’s parent’s parent. Keeping that in mind, we can access the Game class from the Bets screen like so:
# File name: bets.kv
#:import bets bets
...
<BetsScreen>:
canvas:
...
game: root.manager.parent.parent.parent.manager
BoxLayout:
...
Here’s a visualization that will make things clearer to you:
Similarly, in the Results screen we’ll have:
# File name: results.kv
#:import results results
...
<ResultsScreen>:
canvas:
...
game: root.manager.parent.parent.parent.manager
BoxLayout:
...
Now we can access the Game class from any screen using the game property.