Skip to content
Home » Kivy Part 50 – Slugrace – Settings Screen Events

Kivy Part 50 – Slugrace – Settings Screen Events

Spread the love

Hey guys, in the previous two parts of the series we were talking about events in Kivy. In this part we’ll put some of this knowledge into practice.

There are going to be events everywhere throughout our project. For now let’s concentrate on the Settings screen. We will handle the other screens in the following parts. But before we start, here’s some info for you.

Book Info

I published a Kivy book, GUI Programming with Python and Kivy. It’s pretty long (over 800 pages) and comprehensive. And, which also counts, easy to read. The book contains lots of illustrations.

This book covers all the basics that you need to know to start programming GUI applications with Python and Kivy. Throughout the book we are building a GUI application from scratch, a fully functional game using all kinds of tools that Kivy has to offer. It’s our Slugrace project, but covered in a much more in-depth manner.

Each part of the book starts with a theoretical introduction of a topic or idea that we then implement in the project. I assume you have no prior knowledge of the Kivy library, but you should have at least some basic knowledge of the Python programming language, including the object-oriented programming paradigm as this is what we will be using a lot in this book.

The book covers all the basic elements of Kivy that you have to know, like widgets, layouts, Kivy ids and properties, graphics, screens, animation, sound. Finally we’ll deploy the app to Windows. It is pretty comprehensive and after you finish it, I’m sure you’ll be able to create your own awesome GUI apps of any kind, not just games.

I hope you will have at least as much fun reading the book as I had writing it.

As far as this Kivy series is concerned, the following parts will contain the most important elements covered in the book. However, some elements will be presented in a simplified way here on my blog or omitted completely.

____________

If you are interested, you can purchase the book in four versions. Here are the links:

1) ebook – pdf version on my website – in full color

Here you can see the description of the book, sample graphics from the book and the full table of contents.

2) ebook – Kindle version on Amazon – in full color

3) paperback version on Amazon – in black and white

4) paperback version on Amazon – in full color

*****

And Now Let’s Move On…

So, let’s open the main.py file and run the app. This is what we see:

settings screen

There are lots of places here where events may be triggered. Let’s have a look at them:

– First of all there are the four radio buttons where you can choose the number of players. Checking a radio button will trigger an event.

– There are also radio buttons down in the Ending Conditions area. Checking them will also trigger events.

– There are quite a few text inputs here. When we enter text in them, we want the text to be validated, so when the text changes, another event will be triggered. We are not going to handle input validation in this part of the series, though.

– Finally, when we press the Ready button, another event will be triggered.

In this part we’re going to handle the Player radio button events and the Ending Conditions radio button events. The Ready button’s on_press event will be handled in the next part. So, let’s start with the Player radio buttons.

Player Radio Button Events

First of all, we’ll add a NumericProperty to the Game class that will hold the number of players. As you remember, there may be up to four players. The default number of players when the game begins will be 2. We will put the two players in a list. If the number of players should be different, there will be a method that will take care of it, which we will add in a minute. Don’t forget to import the NumericProperty class first. Here’s the code:

# File name: main.py

...

# We need a NumericProperty for the number of players.
from kivy.properties import NumericProperty

...

class Game(ScreenManager):
  
    # The players
    player1 = Player()
    player2 = Player()
    player3 = Player()
    player4 = Player()

    # This is the number of players that we set in the Settings screen.
    number_of_players = NumericProperty(2)

    # When you start the game, there are two players by default.
    players = [player1, player2]

class SlugraceApp(App):
    ...

Now, in the settings.kv file we will add the on_active events to the four radio buttons. They are triggered if the state of a radio button changes, so if we check or uncheck it. Here the code is going to be pretty simple. It will just set the number_of_players property that we just defined in the Game class to an appropriate number.

We also must modify the PlayerCount rule so that it has the active property. You’ll find the explanation in the comments.

As you remember, we now have access to the Game class from each screen. In the Settings screen we reference it by the game property on the root widget. Here’s the settings.kv file:

# File name: settings.kv
#:import settings settings

<PlayerCount>:
    # The PlayerCount widget is not a CheckBox, but rather
    # a custom widget that consists of other widgets. So,
    # what does it mean for it to be active? Well, when
    # we say the PlayerCount widget is active, we mean by
    # that that the checkbox it contains (PlayerRadioButton)
    # is active. So, we need to add an id to the radio button
    # and use it to access the active property on the radio
    # button. 
    active: _player_radio_button.active
    PlayerRadioButton:
        id: _player_radio_button

        # The player should be in the same state as the root widget.
        active: root.active
    RegularLabel:
        text: root.count_text

<PlayerSettings>:
    ...

<SettingsScreen>:
    ...

    game: root.manager

    ...

            # Radiobuttons 
            BoxLayout:
                size_hint: (.4, None)
                height: 50
                
                # 1 player
                PlayerCount:
                    count_text: '1 player'

                    # When this radio button is checked (so active),
                    # it means there's going to be just one player
                    # in the game, so we want to set the number_of_players
                    # property in the Game class to 1.
                    on_active: root.game.number_of_players = 1

                # 2 players
                PlayerCount:
                    # This is the default number of players, so this 
                    # radio button should be active.
                    active: True

                    count_text: '2 players'

                    # Similarly, when this radio button is active, the
                    # number_of_players property should be set to 2.
                    on_active: root.game.number_of_players = 2
            
                # 3 players
                PlayerCount:
                    count_text: '3 players'

                    # If this radio button is active, there are going to
                    # be three players.
                    on_active: root.game.number_of_players = 3

                # 4 players
                PlayerCount:
                    count_text: '4 players'

                    # If this radio button is active, there will be 4 players.
                    on_active: root.game.number_of_players = 4

            # Player name and initial money setup 
            ...

Now, whenever we check one of the radio buttons, the number_of_players property will be changed. But this is not all. We also want the appropriate players to be moved into or out of the players list in the Game class. Using the naming convention with the on_ prefix, we will create a method in the Game class that will take care of it. The method on_number_of_players will be called each time the number_of_players property changes and it will handle the players list accordingly. Here’s the code:

# File name: main.py

...

class Game(ScreenManager):
  
    # The players
    player1 = Player()
    player2 = Player()
    player3 = Player()
    player4 = Player()

    number_of_players = NumericProperty(2)
    players = [player1, player2]

    # This method will be called each time the number_of_players property changes.
    def on_number_of_players(self, instance, value):
        if self.number_of_players == 1:
            self.players = [self.player1]
        elif self.number_of_players == 2:
            self.players = [self.player1, self.player2]
        elif self.number_of_players == 3:
            self.players = [self.player1, self.player2, self.player3]
        elif self.number_of_players == 4:
            self.players = [self.player1, self.player2, self.player3, self.player4]
        

class SlugraceApp(App):
    ...

To make sure it works, let’s add just this one line of code at the end of the on_number_of_players method:

# File name: main.py

...

class Game(ScreenManager):
  
    ...

    def on_number_of_players(self, instance, value):
        if self.number_of_players == 1:
            self.players = [self.player1]
        elif self.number_of_players == 2:
            self.players = [self.player1, self.player2]
        elif self.number_of_players == 3:
            self.players = [self.player1, self.player2, self.player3]
        elif self.number_of_players == 4:
            self.players = [self.player1, self.player2, self.player3, self.player4]

        print(self.number_of_players, self.players)
        
class SlugraceApp(App):
    ...

Now run the app and play with the radio buttons. In the terminal the current number of players should be printed as well as the list of players:

3 [<player.Player object at 0x00000150229134A8>, <player.Player object at 0x0000015022913518>, <player.Player object at 0x0000015022913588>]
4 [<player.Player object at 0x00000150229134A8>, <player.Player object at 0x0000015022913518>, <player.Player object at 0x0000015022913588>, <player.Player object at 0x00000150229135F8>]
1 [<player.Player object at 0x00000150229134A8>]
2 [<player.Player object at 0x00000150229134A8>, <player.Player object at 0x0000015022913518>]
4 [<player.Player object at 0x00000150229134A8>, <player.Player object at 0x0000015022913518>, <player.Player object at 0x0000015022913588>, <player.Player object at 0x00000150229135F8>]
3 [<player.Player object at 0x00000150229134A8>, <player.Player object at 0x0000015022913518>, <player.Player object at 0x0000015022913588>]
2 [<player.Player object at 0x00000150229134A8>, <player.Player object at 0x0000015022913518>]
1 [<player.Player object at 0x00000150229134A8>]

And now let’s implement the Ending Conditions radio button events.

Ending Conditions Radio Button Events

We’ll need three BooleanProperties in the Game class, end_by_money, end_by_races and end_by_time. The first one should default to True, the other two to False. The names of the properties tell us which ending condition we choose. If the game should be over when there’s only one player left with any amount of money, the end_by_money property should be set to True. If the game should end after a given number of races, the end_by_races property should be set to True. Finally, if we want to set a time after which the game will be over, the end_by_time property should be set to True. This means that only one of the three properties may be set to True. Here are the three properties added to the Game class:

# File name: main.py
...

# We need a BooleanProperty for the ending conditions.
from kivy.properties import NumericProperty, BooleanProperty
...
class Game(ScreenManager):

    # The players
    ...

    # We will need these properties for the ending conditions.
    end_by_money = BooleanProperty(True)
    end_by_races = BooleanProperty(False)
    end_by_time = BooleanProperty(False)

    # Callback Methods
    def on_number_of_players(self, instance, value):
        ...

As just mentioned, only one of the three properties may be set to True. Let’s create a method that will take care of it. The method will then be called whenever one of the radio buttons is checked. So, in the Game class let’s create the set_ending_condition method. It should take a condition parameter, which will be a string. In the method we will need some conditional code. If the condition parameter is set to ‘money’, the end_by_money property should be set to True and the other two to False. If the condition parameter is ‘races’, the end_by_races property should be set to True and the other two to False. Finally, if the condition parameter is ‘time’, the end_by_time property should be set to True and the other two to False. Here’s how we can implement it:

# File name: main.py

...
class Game(ScreenManager):
  ...
    def on_number_of_players(self, instance, value):
        ...

    # This method will actually set the ending condition.
    def set_ending_condition(self, condition):
        if condition == 'money':
            self.end_by_money = True
            self.end_by_races = False
            self.end_by_time = False            
        elif condition == 'races':
            self.end_by_money = False
            self.end_by_races = True
            self.end_by_time = False
        elif condition == 'time':
            self.end_by_money = False
            self.end_by_races = False
            self.end_by_time = True
       
class SlugraceApp(App):
    ...

Now let’s go to the settings.kv file and locate the code where the Ending Conditions radio buttons are instantiated. To each of the three ConditionRadioButtons we’ll add the active property and set it to the same value as the corresponding property in the Game class, so for example the active property on the first radio button should be the same value as the end_by_money property, and so on:

# File name: settings.kv
#:import settings settings

...

<SettingsScreen>:
    ...  

    game: root.manager

    ...
                
        ### ENDING CONDITIONS ###
        BoxLayout:
            ...

            # radio buttons
            GridLayout:
                rows: 3
                spacing: 10

                # option 1: money
                ConditionRadioButton:
                    # This radio button should be active if the end_by_money
                    # property is set to True.
                    active: root.game.end_by_money
                    
                RegularLabel:
                    text: "The game is over when there is only one player with any money left."

                # option 2: races
                ConditionRadioButton:
                    # This radio button should be active if the end_by_races
                    # property is set to True.
                    active: root.game.end_by_races
                    
                BoxLayout:                
                    RegularLabel:
                        text: "The game is over not later than after a given number of races."

                    NumInput:

                # option 3: time
                ConditionRadioButton:
                    # This radio button should be active if the end_by_time
                    # property is set to True.
                    active: root.game.end_by_time
                    
                BoxLayout:                
                    RegularLabel:
                        text: "The game is over not later than the total racing time has elapsed."

                    NumInput:

        ### READY BUTTON ###
        ...

Still in the kv file, let’s add the on_active event to all three ConditionRadioButtons. This event is triggered whenever the active property changes, so both when a radio button is checked and when it’s unchecked. But we want it to be triggered only when the button is checked, so we will need a condition. So, as far as the first button is concerned, for example, if the active property is True, so if the radio button is checked, the set_ending_condition method in the Game class should be called with the argument ‘money’. Similarly, when the state of the other two radio buttons changes from unched to checked, the set_ending_condition method should be called, with the argument ‘races’ and ‘time’ respectively:

# File name: settings.kv
#:import settings settings
...
<SettingsScreen>:
    ...  

    game: root.manager
    ...                
        ### ENDING CONDITIONS ###
        BoxLayout:
            ...
            # radio buttons
            GridLayout:
                ...
                # option 1: money
                ConditionRadioButton:
                    active: root.game.end_by_money
                    
             # When this radio button is checked, the set_ending_condition
                    # method should be called with the argument 'money'. We want
                    # the event to be triggered only when the radio button is 
                    # checked, not when it's unchecked, hence the conditional
                    # code. The same conditional code will be used for the other
                    # two radio buttons.
                    on_active: if self.active: root.game.set_ending_condition('money') 

                RegularLabel:
                    text: "The game is over when there is only one player with any money left."

                # option 2: races
                ConditionRadioButton:
                    active: root.game.end_by_races
                    
                    # When this radio button is checked, the set_ending_condition
                    # method should be called with the argument 'races'. 
                    on_active: if self.active: root.game.set_ending_condition('races') 

                BoxLayout:                
                    RegularLabel:
                        text: "The game is over not later than after a given number of races."

                    NumInput:

                # option 3: time
                ConditionRadioButton:
                    active: root.game.end_by_time
                    
                    # When this radio button is checked, the set_ending_condition
                    # method should be called with the argument 'time'.
                    on_active: if self.active: root.game.set_ending_condition('time') 

                BoxLayout:                
                    RegularLabel:
                        text: "The game is over not later than the total racing time has elapsed."

                    NumInput:

        ### READY BUTTON ###
        ...

Now, this should be working. We’re done with the radio button events in the Settings screen.


Spread the love

Leave a Reply