Build a Match-3 Game using Python and Kivy

Lesson 2 of 8

Layouts and Widgets

A Match-3 board is fundamentally a grid, so before writing any game logic, this lesson covers the Kivy widgets that will hold it: layouts, which arrange other widgets, and buttons, which will represent each gem.

Layouts arrange widgets, they don't draw anything themselves

from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.uix.label import Label

class DemoApp(App):
    def build(self):
        root = BoxLayout(orientation='vertical')
        root.add_widget(Label(text='Score: 0', font_size=24))
        root.add_widget(Button(text='Shuffle'))
        return root

if __name__ == '__main__':
    DemoApp().run()
  • BoxLayout stacks its children in a single row or column, orientation='vertical' stacks them top to bottom.
  • add_widget(...) is how every widget, layout or not, gets a child added to it. Layouts just also decide where their children end up.

GridLayout: the board itself

An 8×8 Match-3 board is a perfect fit for GridLayout, which arranges children into a fixed number of columns (and, implicitly, however many rows the number of children requires):

from kivy.uix.gridlayout import GridLayout
from kivy.uix.button import Button

class BoardDemoApp(App):
    def build(self):
        grid = GridLayout(cols=8)
        for i in range(8 * 8):
            grid.add_widget(Button(text=str(i)))
        return grid

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

Setting cols=8 and then adding 64 buttons is all it takes, GridLayout automatically wraps to a new row every 8 widgets, giving you an 8×8 grid with zero manual positioning math.

Buttons as gems

Each cell in the board will be a Button, gems don't need any text, just a solid background color, which Kivy exposes through two properties together:

from kivy.uix.button import Button

gem = Button(text='', background_normal='', background_color=(0.9, 0.2, 0.2, 1))  # solid red
  • background_color is an RGBA tuple, each channel from 0 to 1 (not 0-255 like most other tools).
  • background_normal='' is required to actually see that color, by default Kivy's Button draws its built-in gray button-image on top of background_color, setting it to an empty string turns that image off so the flat color shows through.

Responding to taps

def on_gem_pressed(instance):
    print('tapped a gem!')

gem.bind(on_press=on_gem_pressed)

bind(on_press=...) connects a function to the button's press event, Kivy calls it with the widget instance itself as the only argument, which is exactly how the next lesson will know which gem was tapped.

TIP

size_hint (a tuple like (1, 1)) controls how much of the available space a widget claims relative to its siblings, the default (1, 1) already makes every gem button fill its grid cell evenly, so you won't need to touch it for this project.

📝 Layouts and Widgets Quiz

Passing score: 70%
  1. 1.Which layout automatically arranges widgets into a fixed number of columns?

  2. 2.Setting background_normal='' on a Button is necessary to see its background_color clearly.

  3. 3.button.____(on_press=handler) connects a function to run whenever the button is tapped.