Skip to content
Home » Kivy Part 19 – Slugrace – Settings Screen – Class Rules

Kivy Part 19 – Slugrace – Settings Screen – Class Rules

Spread the love

In the previous part we created a very basic Settings screen, which contains all the elements that are supposed to be there in the final version, naturally without any functionality added yet. Today we’re going to rewrite the code using class rules to make our lives easier.

But before we delve into the topic, here’s some info for you.

*****

Book Info

I just published my 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.

Kivy 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…

But first let’s have a look at what we have. Here’s the code again, first the Python file:

# File name: settings.py

import kivy
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout

class SettingsScreen(BoxLayout):
    pass

class SettingsApp(App):
    def build(self):
        return SettingsScreen()

if __name__ == '__main__':
    SettingsApp().run()

And here’s the kv file:

# File name: settings.kv

<SettingsScreen>:
    orientation: 'vertical'
    padding: 10
    spacing: 10
        
    ### SETTINGS LABEL ###
    Label:
        text: 'Settings'
        font_size: 28
        size_hint: (1, None)
        height: 30
        text_size: self.size
        halign: 'left'
        valign: 'center'

    ### THE PLAYERS ###  
    BoxLayout:
        orientation: 'vertical'
        padding: 10
        spacing: 10
        
        # Title         
        Label:
            text: 'The Players'
            font_size: 20
            size_hint: (1, None)
            height: 30
            text_size: self.size
            halign: 'left'
            valign: 'center'

        # Radiobuttons 
        BoxLayout:
            size_hint: (.4, None)
            height: 50
            
            # 1 player
            BoxLayout:
                CheckBox:
                    group: 'players'
                    size_hint: (.5, 1) 
                Label:
                    text: '1 player'
                    text_size: self.size
                    halign: 'left'
                    valign: 'center'

            # 2 players
            BoxLayout:
                CheckBox:
                    group: 'players'
                    size_hint: (.5, 1) 
                Label:
                    text: '2 players'
                    text_size: self.size
                    halign: 'left'
                    valign: 'center'

            # 3 players
            BoxLayout:
                CheckBox:
                    group: 'players'
                    size_hint: (.5, 1) 
                Label:
                    text: '3 players'
                    text_size: self.size
                    halign: 'left'
                    valign: 'center'

            # 4 players
            BoxLayout:
                CheckBox:
                    group: 'players'
                    size_hint: (.5, 1) 
                Label:
                    text: '4 players'
                    text_size: self.size
                    halign: 'left'
                    valign: 'center'

        # Player name and initial money setup 
        BoxLayout:
            orientation: 'vertical'

            # the headers row
            BoxLayout:
                Label:
                    text: ""
                    size_hint_x: None                
                    width: 80
                    text_size: self.size
                    halign: 'left'
                    valign: 'center'

                # name header
                Label:
                    text: "Name"
                    size_hint_x: None                
                    width: 700
                    text_size: self.size
                    halign: 'left'
                    valign: 'center'

                # money header
                Label:
                    text: "Initial Money"
                    text_size: self.size
                    halign: 'left'
                    valign: 'center'

            # the players rows
            # player 1
            BoxLayout:
                Label:
                    text: 'Player 1'
                    size_hint: None, None
                    width: 80
                    height: 30
                    text_size: self.size
                    halign: 'left'
                    valign: 'center'
                
                TextInput:
                    multiline: False
                    size_hint: None, None                    
                    width: 400 
                    height: 30

                BoxLayout:
                    Label:
                        text: ""
                        size_hint_x: None                
                        width: 280
                        text_size: self.size
                        halign: 'left'
                        valign: 'center'
                                      
                    Label:
                        text: "$"
                        size_hint: None, None
                        width: 20
                        height: 30
                        text_size: self.size
                        halign: 'left'
                        valign: 'center'

                    TextInput:
                        multiline: False
                        size_hint: None, None                        
                        width: 250 
                        height: 30

            # player 2
            BoxLayout:
                Label:
                    text: 'Player 2'
                    size_hint: None, None
                    width: 80
                    height: 30
                    text_size: self.size
                    halign: 'left'
                    valign: 'center'

                TextInput:
                    multiline: False
                    size_hint: None, None                    
                    width: 400 
                    height: 30

                BoxLayout:
                    Label:
                        text: ""
                        size_hint_x: None                
                        width: 280
                        text_size: self.size
                        halign: 'left'
                        valign: 'center'

                    Label:
                        text: "$"
                        size_hint: None, None
                        width: 20
                        height: 30
                        text_size: self.size
                        halign: 'left'
                        valign: 'center'

                    TextInput:
                        multiline: False
                        size_hint: None, None                        
                        width: 250 
                        height: 30

            # player 3
            BoxLayout:
                Label:
                    text: 'Player 3'
                    size_hint: None, None
                    width: 80
                    height: 30
                    text_size: self.size
                    halign: 'left'
                    valign: 'center'

                TextInput:
                    multiline: False
                    size_hint: None, None                    
                    width: 400 
                    height: 30

                BoxLayout:
                    Label:
                        text: ""
                        size_hint_x: None                
                        width: 280
                        text_size: self.size
                        halign: 'left'
                        valign: 'center'

                    Label:
                        text: "$"
                        size_hint: None, None
                        width: 20
                        height: 30
                        text_size: self.size
                        halign: 'left'
                        valign: 'center'

                    TextInput:
                        multiline: False
                        size_hint: None, None                        
                        width: 250 
                        height: 30

            # player 4
            BoxLayout:
                Label:
                    text: 'Player 4'
                    size_hint: None, None
                    width: 80
                    height: 30
                    text_size: self.size
                    halign: 'left'
                    valign: 'center'

                TextInput:
                    multiline: False
                    size_hint: None, None                    
                    width: 400 
                    height: 30

                BoxLayout:
                    Label:
                        text: ""
                        size_hint_x: None                
                        width: 280
                        text_size: self.size
                        halign: 'left'
                        valign: 'center'

                    Label:
                        text: "$"
                        size_hint: None, None
                        width: 20
                        height: 30
                        text_size: self.size
                        halign: 'left'
                        valign: 'center'

                    TextInput:
                        multiline: False  
                        size_hint: None, None                        
                        width: 250 
                        height: 30        

    ### ENDING CONDITIONS ###
    BoxLayout:
        orientation: 'vertical' 
        size_hint: (1, .4)   
        padding: 10
        spacing: 10   

        # title label
        Label:
            text: "Ending Conditions"
            font_size: 20
            size_hint: (1, None)
            height: 30
            text_size: self.size
            halign: 'left'
            valign: 'center'

        # radio buttons
        GridLayout:
            rows: 3
            spacing: 10

            # option 1: money
            CheckBox:
                group: 'conditions'
                size_hint_x: .05

            Label:
                text: "The game is over when there is only one player with any money left."
                text_size: self.size
                halign: 'left'
                valign: 'center'

            # option 2: races
            CheckBox:
                group: 'conditions'
                size_hint_x: .05

            BoxLayout:                
                Label:
                    text: "The game is over not later than after a given number of races."
                    text_size: self.size
                    halign: 'left'
                    valign: 'center'

                TextInput:
                    multiline: False
                    size_hint: None, None                    
                    width: 250
                    height: 30

            # option 3: time
            CheckBox:
                group: 'conditions'
                size_hint_x: .05

            BoxLayout:                
                Label:
                    text: "The game is over not later than the total racing time has elapsed."
                    text_size: self.size
                    halign: 'left'
                    valign: 'center'

                TextInput:
                    multiline: False
                    size_hint: None, None                    
                    width: 250
                    height: 30

    ### READY BUTTON ###
    Button:
        text: 'Ready'
        size_hint: (None, None)        
        size: 200, 40
        pos_hint: {'center_x': 0.5}

Pretty long, right? Well, over 350 lines of code, including comments and blank lines. If you look closer at the code you can see right away that it’s very repetitive. This is definitely a drawback. We’re going to reduce the amount of code by extracting repetitive code into class rules. But before we do that, let’s run the program again to see what the Settings screen looks like at this stage:

settings screen now

Naturally, it’s not the final version yet, but today we’re not going to change anything in the visual representation of the screen, but rather just refactor the code.

Class Rules

So, let’s get to work. You already know what class rules are. We used the rule class notation when we were talking about custom widgets. So, the idea is that you can define a class rule above the root widget and then use it inside the root widget just like any other widget. I think it will be much easier to understand if we work on an example.

Your Panda3D Magazine

Make Awesome Games and Other 3D Apps

with Panda3D and Blender using Python.

Cool stuff, easy to follow articles.

Get the magazine here (PDF).

Custom Labels

If you look at the app window, shown in the image above, you can see there’s a lot of text. This means there are a lot of labels. As far as font size is concerned, there are three sizes of text:

– most of the text is pretty small

– the two titles (The Players and Ending Conditions) are larger

– the screen title (Settings) is even larger

Now let’s have a look at how these labels are represented in code. Here are some examples of the small font size labels:

– the label that reads ‘1 player’ in the Radiobuttons section:

Label:
    text: '1 player'
    text_size: self.size
    halign: 'left'
    valign: 'center'

– the name header:

Label:
    text: "Name"
    size_hint_x: None                
    width: 700
    text_size: self.size
    halign: 'left'
    valign: 'center'

– one of the labels in the Ending Conditions area:

Label:
    text: "The game is over not later than after a given number of races."
    text_size: self.size
    halign: 'left'
    valign: 'center'

As you can see, all these labels share some properties:

text_size: self.size
halign: 'left'
valign: 'center'

They also have the text property, but it’s set to a different value for each label. Also, the name header label contains some more properties like size_hint_x and width. So, we can extract the three shared properties listed above into a custom label, which is a subclass of label. As this is going to be the most used label in our code, let’s name it RegularLabel. Now, above the root widget, type the following class rule that will create our custom RegularLabel class:

# File name: settings.kv

# RegularLabel inherits from Label.
<RegularLabel@Label>:
    text_size: self.size
    halign: 'left'
    valign: 'center'

<SettingsScreen>:
...

Now, with the RegularLabel defined, we can now use this class instead of Label in our code, so the snippets above will look like so:

– the label that reads ‘1 player’ in the Radiobuttons section:

RegularLabel:
    text: '1 player'

– the name header:

RegularLabel:
    text: "Name"
    size_hint_x: None                
    width: 700

– one of the labels in the Ending Conditions area:

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

So, all the repetitive code is gone and only the code that makes each label unique remains.

And now have a look at the larger font size labels used for the titles:

– The Players label:

Label:
    text: 'The Players'
    font_size: 20
    size_hint: (1, None)
    height: 30
    text_size: self.size
    halign: 'left'
    valign: 'center'

– Ending Conditions label:

Label:
    text: "Ending Conditions"
    font_size: 20
    size_hint: (1, None)
    height: 30
    text_size: self.size
    halign: 'left'
    valign: 'center'

The first thing you notice is that they also contain the code shared in the RegularLabel class, so we can create a new class that inherits from RegularLabel. Besides, there are three properties with the same values: font_size, size_hint and height, so we can move them to the new class. So, let’s add a new class rule above the root widget. The name TitleLabel sounds fine to me, so I’ll use it:

# File name: settings.kv

# RegularLabel inherits from Label.
<RegularLabel@Label>:
    text_size: self.size
    halign: 'left'
    valign: 'center'

# TitleLabel inherits from RegularLabel.
<TitleLabel@RegularLabel>:
    font_size: 20
    size_hint: (1, None)
    height: 30

<SettingsScreen>:
...

Now we can rewrite the title labels like so:

– The Players label:

TitleLabel:
    text: 'The Players'    

– Ending Conditions label:

TitleLabel:
    text: "Ending Conditions"    

Much more concise, isn’t it?

Now, as for the largest label with the screen title ‘Seetings’, we could create a class for it too, but let’s take a different approach this time. This label actually only differs from the title label by font size. If you set a property on a widget instance to a different value than defined in the class rule, it will overwrite it. So, here is the label as it is now in the code:

### SETTINGS LABEL ###
Label:
    text: 'Settings'
    font_size: 28
    size_hint: (1, None)
    height: 30
    text_size: self.size
    halign: 'left'
    valign: 'center'

Let’s use our TitleLabel instead:

TitleLabel:
    text: 'Settings'
    font_size: 28

So, although the default font size defined in the TitleLabel class is 20, in this particular TitleLabel it is set to 28, which makes the text larger. This is how you can easily overwrite values for particular widgets.

If you scan the code carefully from top to bottom, you will see that there are some RegularLabels that still share some properties. These are:

1) the four RegularLabels with the generic names of the players, like this one:

RegularLabel:
    text: 'Player 1'
    size_hint: None, None
    width: 80
    height: 30

Here only the text should be different, so we can create a custom label with all the other properties. A characteristic feature of these labels is that they have a specific width and height, so let’s call the class Regular80x30Label. Here’s the class rule:

<Regular80x30Label@RegularLabel>:
    size_hint: None, None
    width: 80
    height: 30

Now we can replace each occurrence of the code above with a Regular80x30Label like this:

Regular80x30Label:
    text: 'Player 1'

2) the four RegularLabels with the dollar sign:

RegularLabel:
    text: "$"
    size_hint: None, None
    width: 20
    height: 30

They all have the same width and height and besides they even have the same text. So, why not create a DollarLabel class? We could derive it from RegularLabel or Regular80x30Label. Maybe the former would make more sense as the characteristic feature of the latter is its size, which is even contained in its name, but the DollarLabel and the Regular80x30Label have more properties in common, which will make the code slightly shorter, so let’s opt for the Regular80x30Label class and overwrite the width. Let’s add the following class rule:

# DollarLabel inherits from Regular80x30Label.
<DollarLabel@RegularLabel>:
    text: "$"    
    width: 20

If we decided to subclass the RegularLabel class instead, the code would be:

# ... if DollarLabel inherited from RegularLabel...
<DollarLabel@RegularLabel>:
    text: "$"
    size_hint: None, None
    width: 20
    height: 30

As all four DollarLabels share all the properties and there are no differences between them, it’s enough to use them like this:

DollarLabel:

Now we can rewrite the whole kv file using our custom labels. Wondering how much shorter the code will become? Have a look yourself:

# File name: settings.kv

# RegularLabel inherits from Label.
<RegularLabel@Label>:
    text_size: self.size
    halign: 'left'
    valign: 'center'

# TitleLabel inherits from RegularLabel.
<TitleLabel@RegularLabel>:
    font_size: 20
    size_hint: (1, None)
    height: 30

# Regular80x30Label inherits from RegularLabel.
<Regular80x30Label@RegularLabel>:
    size_hint: None, None
    width: 80
    height: 30

# DollarLabel inherits from Regular80x30Label.
<DollarLabel@Regular80x30Label>:
    text: "$"    
    width: 20

<SettingsScreen>:
    orientation: 'vertical'
    padding: 10
    spacing: 10
        
    ### SETTINGS LABEL ###
    TitleLabel:
        text: 'Settings'
        font_size: 28

    ### THE PLAYERS ###  
    BoxLayout:
        orientation: 'vertical'
        padding: 10
        spacing: 10
        
        # Title         
        TitleLabel:
            text: 'The Players'
            font_size: 20

        # Radiobuttons 
        BoxLayout:
            size_hint: (.4, None)
            height: 50
            
            # 1 player
            BoxLayout:
                CheckBox:
                    group: 'players'
                    size_hint: (.5, 1) 
                RegularLabel:
                    text: '1 player'

            # 2 players
            BoxLayout:
                CheckBox:
                    group: 'players'
                    size_hint: (.5, 1) 
                RegularLabel:
                    text: '2 players'

            # 3 players
            BoxLayout:
                CheckBox:
                    group: 'players'
                    size_hint: (.5, 1) 
                RegularLabel:
                    text: '3 players'

            # 4 players
            BoxLayout:
                CheckBox:
                    group: 'players'
                    size_hint: (.5, 1) 
                RegularLabel:
                    text: '4 players'

        # Player name and initial money setup 
        BoxLayout:
            orientation: 'vertical'

            # the headers row
            BoxLayout:
                RegularLabel:
                    text: ""
                    size_hint_x: None                
                    width: 80

                # name header
                RegularLabel:
                    text: "Name"
                    size_hint_x: None                
                    width: 700

                # money header
                RegularLabel:
                    text: "Initial Money"

            # the players rows
            # player 1
            BoxLayout:
                Regular80x30Label:
                    text: 'Player 1'
                
                TextInput:
                    multiline: False
                    size_hint: None, None                    
                    width: 400 
                    height: 30

                BoxLayout:
                    RegularLabel:
                        text: ""
                        size_hint_x: None                
                        width: 280
                                      
                    DollarLabel:

                    TextInput:
                        multiline: False
                        size_hint: None, None                        
                        width: 250 
                        height: 30

            # player 2
            BoxLayout:
                Regular80x30Label:
                    text: 'Player 2'

                TextInput:
                    multiline: False
                    size_hint: None, None                    
                    width: 400 
                    height: 30

                BoxLayout:
                    RegularLabel:
                        text: ""
                        size_hint_x: None                
                        width: 280

                    DollarLabel:

                    TextInput:
                        multiline: False
                        size_hint: None, None                        
                        width: 250 
                        height: 30

            # player 3
            BoxLayout:
                Regular80x30Label:
                    text: 'Player 3'

                TextInput:
                    multiline: False
                    size_hint: None, None                    
                    width: 400 
                    height: 30

                BoxLayout:
                    RegularLabel:
                        text: ""
                        size_hint_x: None                
                        width: 280

                    DollarLabel:

                    TextInput:
                        multiline: False
                        size_hint: None, None                        
                        width: 250 
                        height: 30

            # player 4
            BoxLayout:
                Regular80x30Label:
                    text: 'Player 4'

                TextInput:
                    multiline: False
                    size_hint: None, None                    
                    width: 400 
                    height: 30

                BoxLayout:
                    RegularLabel:
                        text: ""
                        size_hint_x: None                
                        width: 280

                    DollarLabel:

                    TextInput:
                        multiline: False  
                        size_hint: None, None                        
                        width: 250 
                        height: 30        

    ### ENDING CONDITIONS ###
    BoxLayout:
        orientation: 'vertical' 
        size_hint: (1, .4)   
        padding: 10
        spacing: 10   

        # title label
        TitleLabel:
            text: "Ending Conditions"

        # radio buttons
        GridLayout:
            rows: 3
            spacing: 10

            # option 1: money
            CheckBox:
                group: 'conditions'
                size_hint_x: .05

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

            # option 2: races
            CheckBox:
                group: 'conditions'
                size_hint_x: .05

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

                TextInput:
                    multiline: False
                    size_hint: None, None                    
                    width: 250
                    height: 30

            # option 3: time
            CheckBox:
                group: 'conditions'
                size_hint_x: .05

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

                TextInput:
                    multiline: False
                    size_hint: None, None                    
                    width: 250
                    height: 30

    ### READY BUTTON ###
    Button:
        text: 'Ready'
        size_hint: (None, None)        
        size: 200, 40
        pos_hint: {'center_x': 0.5}

Looks like we reduced the code from about 350 to about 270 lines of code. Not bad. But we can reduce it even more because the labels are not the only widgets that share the same values of properties.

Python Jumpstart Course

Learn the basics of Python, including OOP.

with lots of exercises, easy to follow

The course is available on Udemy.

Custom Text Inputs

In the Settings screen there are several text inputs. There are two types of them actually:

1) the text inputs where we can enter the players’ names:

TextInput:
    multiline: False
    size_hint: None, None                    
    width: 400 
    height: 30

2) the text inputs where we can enter numbers like the players’ initial money or the number of races or the time of the game in the Ending Conditions area:

TextInput:
    multiline: False
    size_hint: None, None                        
    width: 250 
    height: 30

For the sake of simplicity, let’s just create two classes, NameInput and NumInput, both inheriting directly from TextInput. Here are the two class rules:

# NameInput inherits from TextInput
<NameInput@TextInput>:
    multiline: False
    size_hint: None, None
    height: 30
    width: 400 

# NumInput inherits from TextInput
<NumInput@TextInput>:
    multiline: False
    size_hint: None, None
    height: 30
    width: 250

Now the code will be simplified to:

NameInput:

and

NumInput:

respectively.

If we replace all the occurrences of text inputs in the code, the kv file will look like so:

# File name: settings.kv

# RegularLabel inherits from Label.
<RegularLabel@Label>:
    text_size: self.size
    halign: 'left'
    valign: 'center'

# TitleLabel inherits from RegularLabel.
<TitleLabel@RegularLabel>:
    font_size: 20
    size_hint: (1, None)
    height: 30

# Regular80x30Label inherits from RegularLabel.
<Regular80x30Label@RegularLabel>:
    size_hint: None, None
    width: 80
    height: 30

# DollarLabel inherits from Regular80x30Label.
<DollarLabel@Regular80x30Label>:
    text: "$"    
    width: 20

# NameInput inherits from TextInput
<NameInput@TextInput>:
    multiline: False
    size_hint: None, None
    height: 30
    width: 400 

# NumInput inherits from TextInput
<NumInput@TextInput>:
    multiline: False
    size_hint: None, None
    height: 30
    width: 250

<SettingsScreen>:
    orientation: 'vertical'
    padding: 10
    spacing: 10
        
    ### SETTINGS LABEL ###
    TitleLabel:
        text: 'Settings'
        font_size: 28

    ### THE PLAYERS ###  
    BoxLayout:
        orientation: 'vertical'
        padding: 10
        spacing: 10
        
        # Title         
        TitleLabel:
            text: 'The Players'
            font_size: 20

        # Radiobuttons 
        BoxLayout:
            size_hint: (.4, None)
            height: 50
            
            # 1 player
            BoxLayout:
                CheckBox:
                    group: 'players'
                    size_hint: (.5, 1) 
                RegularLabel:
                    text: '1 player'

            # 2 players
            BoxLayout:
                CheckBox:
                    group: 'players'
                    size_hint: (.5, 1) 
                RegularLabel:
                    text: '2 players'

            # 3 players
            BoxLayout:
                CheckBox:
                    group: 'players'
                    size_hint: (.5, 1) 
                RegularLabel:
                    text: '3 players'

            # 4 players
            BoxLayout:
                CheckBox:
                    group: 'players'
                    size_hint: (.5, 1) 
                RegularLabel:
                    text: '4 players'

        # Player name and initial money setup 
        BoxLayout:
            orientation: 'vertical'

            # the headers row
            BoxLayout:
                RegularLabel:
                    text: ""
                    size_hint_x: None                
                    width: 80

                # name header
                RegularLabel:
                    text: "Name"
                    size_hint_x: None                
                    width: 700

                # money header
                RegularLabel:
                    text: "Initial Money"

            # the players rows
            # player 1
            BoxLayout:
                Regular80x30Label:
                    text: 'Player 1'
                
                NameInput:

                BoxLayout:
                    RegularLabel:
                        text: ""
                        size_hint_x: None                
                        width: 280
                                      
                    DollarLabel:

                    NumInput:

            # player 2
            BoxLayout:
                Regular80x30Label:
                    text: 'Player 2'

                NameInput:

                BoxLayout:
                    RegularLabel:
                        text: ""
                        size_hint_x: None                
                        width: 280

                    DollarLabel:

                    NumInput:

            # player 3
            BoxLayout:
                Regular80x30Label:
                    text: 'Player 3'

                NameInput:

                BoxLayout:
                    RegularLabel:
                        text: ""
                        size_hint_x: None                
                        width: 280

                    DollarLabel:

                    NumInput:

            # player 4
            BoxLayout:
                Regular80x30Label:
                    text: 'Player 4'

                NameInput:

                BoxLayout:
                    RegularLabel:
                        text: ""
                        size_hint_x: None                
                        width: 280

                    DollarLabel:

                    NumInput:       

    ### ENDING CONDITIONS ###
    BoxLayout:
        orientation: 'vertical' 
        size_hint: (1, .4)   
        padding: 10
        spacing: 10   

        # title label
        TitleLabel:
            text: "Ending Conditions"

        # radio buttons
        GridLayout:
            rows: 3
            spacing: 10

            # option 1: money
            CheckBox:
                group: 'conditions'
                size_hint_x: .05

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

            # option 2: races
            CheckBox:
                group: 'conditions'
                size_hint_x: .05

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

                NumInput:

            # option 3: time
            CheckBox:
                group: 'conditions'
                size_hint_x: .05

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

                NumInput:

    ### READY BUTTON ###
    Button:
        text: 'Ready'
        size_hint: (None, None)        
        size: 200, 40
        pos_hint: {'center_x': 0.5}

Now we reduced the code from about 270 to about 240 lines of code.

Blender Jumpstart Course

Learn the basics of 3D modeling in Blender.

step-by-step, easy to follow, visually rich

The course is available on Udemy and on Skillshare.

Custom Radio Buttons

Another pieces of repetitive code are the two radio button groups. There are radio buttons where you can choose the number of players in the Players area and radio buttons which you can use to choose an ending condition. As you know, radio buttons are just check boxes that all have the group property set to the same value.

Let’s have a look at the radio buttons as they are right now. Here’s the Players radio button:

CheckBox:
    group: 'players'
    size_hint: (.5, 1) 

and here’s the Ending Conditions radio button:

CheckBox:
    group: 'conditions'
    size_hint_x: .05

There are four Players radio buttons and three Ending Conditions radio buttons. Let’s create custom radio buttons that inherit from CheckBox. Here are the two class rules:

# PlayerRadioButton and ConditionRadioButton inherit from CheckBox.
<PlayerRadioButton>:
    group: 'players'  
    size_hint: (.5, 1)

<ConditionRadioButton>:
    group: 'conditions'
    size_hint_x: .05

Now we can use the new classes in code:

PlayerRadioButton:

and

ConditionRadioButton:

Here’s the full kv code:

# File name: settings.kv

# RegularLabel inherits from Label.
<RegularLabel@Label>:
    text_size: self.size
    halign: 'left'
    valign: 'center'

# TitleLabel inherits from RegularLabel.
<TitleLabel@RegularLabel>:
    font_size: 20
    size_hint: (1, None)
    height: 30

# Regular80x30Label inherits from RegularLabel.
<Regular80x30Label@RegularLabel>:
    size_hint: None, None
    width: 80
    height: 30

# DollarLabel inherits from Regular80x30Label.
<DollarLabel@Regular80x30Label>:
    text: "$"    
    width: 20

# NameInput inherits from TextInput
<NameInput@TextInput>:
    multiline: False
    size_hint: None, None
    height: 30
    width: 400 

# NumInput inherits from TextInput
<NumInput@TextInput>:
    multiline: False
    size_hint: None, None
    height: 30
    width: 250

# PlayerRadioButton and ConditionRadioButton inherit from CheckBox.
<PlayerRadioButton@CheckBox>:
    group: 'players'  
    size_hint: (.5, 1)

<ConditionRadioButton@CheckBox>:
    group: 'conditions'
    size_hint_x: .05

<SettingsScreen>:
    orientation: 'vertical'
    padding: 10
    spacing: 10
        
    ### SETTINGS LABEL ###
    TitleLabel:
        text: 'Settings'
        font_size: 28

    ### THE PLAYERS ###  
    BoxLayout:
        orientation: 'vertical'
        padding: 10
        spacing: 10
        
        # Title         
        TitleLabel:
            text: 'The Players'
            font_size: 20

        # Radiobuttons 
        BoxLayout:
            size_hint: (.4, None)
            height: 50
            
            # 1 player
            BoxLayout:
                PlayerRadioButton:

                RegularLabel:
                    text: '1 player'

            # 2 players
            BoxLayout:
                PlayerRadioButton:

                RegularLabel:
                    text: '2 players'

            # 3 players
            BoxLayout:
                PlayerRadioButton:

                RegularLabel:
                    text: '3 players'

            # 4 players
            BoxLayout:
                PlayerRadioButton:

                RegularLabel:
                    text: '4 players'

        # Player name and initial money setup 
        BoxLayout:
            orientation: 'vertical'

            # the headers row
            BoxLayout:
                RegularLabel:
                    text: ""
                    size_hint_x: None                
                    width: 80

                # name header
                RegularLabel:
                    text: "Name"
                    size_hint_x: None                
                    width: 700

                # money header
                RegularLabel:
                    text: "Initial Money"

            # the players rows
            # player 1
            BoxLayout:
                Regular80x30Label:
                    text: 'Player 1'
                
                NameInput:

                BoxLayout:
                    RegularLabel:
                        text: ""
                        size_hint_x: None                
                        width: 280
                                      
                    DollarLabel:

                    NumInput:

            # player 2
            BoxLayout:
                Regular80x30Label:
                    text: 'Player 2'

                NameInput:

                BoxLayout:
                    RegularLabel:
                        text: ""
                        size_hint_x: None                
                        width: 280

                    DollarLabel:

                    NumInput:

            # player 3
            BoxLayout:
                Regular80x30Label:
                    text: 'Player 3'

                NameInput:

                BoxLayout:
                    RegularLabel:
                        text: ""
                        size_hint_x: None                
                        width: 280

                    DollarLabel:

                    NumInput:

            # player 4
            BoxLayout:
                Regular80x30Label:
                    text: 'Player 4'

                NameInput:

                BoxLayout:
                    RegularLabel:
                        text: ""
                        size_hint_x: None                
                        width: 280

                    DollarLabel:

                    NumInput:       

    ### ENDING CONDITIONS ###
    BoxLayout:
        orientation: 'vertical' 
        size_hint: (1, .4)   
        padding: 10
        spacing: 10   

        # title label
        TitleLabel:
            text: "Ending Conditions"

        # radio buttons
        GridLayout:
            rows: 3
            spacing: 10

            # option 1: money
            ConditionRadioButton:

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

            # option 2: races
            ConditionRadioButton:

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

                NumInput:

            # option 3: time
            ConditionRadioButton:

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

                NumInput:

    ### READY BUTTON ###
    Button:
        text: 'Ready'
        size_hint: (None, None)        
        size: 200, 40
        pos_hint: {'center_x': 0.5}

This time the code didn’t get shorter, it’s still about 240 lines of code, because although the code was simplified, we added the class rules at the beginning, so the overall number of lines is more or less the same. But still, it was worth doing because we got rid of the code repetitiveness.

If you run the program now, after making all those changes, you should see the same window as before. I’m going to wrap it up now, but I’ll come back to the Settings screen and simplify it even more when we cover Kivy properties and ids. So, if you still can see some repetitiveness, don’t worry about that, we’ll fix that soon.

In the next part we’ll build the screen where we’re going to spend most of the time during the game, the Race screen.


Spread the love
Tags:

Leave a Reply