Build a Match-3 Game using Python and Kivy

Lesson 4 of 8

Displaying the Board

With make_board() producing the data and GridLayout able to hold an 8×8 grid of buttons, this lesson connects the two: turning the plain Python board into actual colored gems on screen.

Mapping colors to RGBA

GEM_RGBA = {
    'red':    (0.9, 0.2, 0.2, 1),
    'blue':   (0.2, 0.4, 0.9, 1),
    'green':  (0.2, 0.8, 0.3, 1),
    'yellow': (0.95, 0.85, 0.2, 1),
    'purple': (0.6, 0.2, 0.8, 1),
}

Every gem color string from board needs a matching entry here, this dictionary is the only place that translates the game's data (plain strings) into something Kivy can actually render.

Building the grid of buttons

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

class Match3App(App):
    def build(self):
        self.board = make_board()
        self.grid = GridLayout(cols=BOARD_SIZE)
        self.buttons = {}  # (row, col) -> Button, so taps can be mapped back to a position

        for row in range(BOARD_SIZE):
            for col in range(BOARD_SIZE):
                gem = Button(text='', background_normal='')
                self.buttons[(row, col)] = gem
                self.grid.add_widget(gem)

        self.render_board()
        return self.grid

    def render_board(self):
        for row in range(BOARD_SIZE):
            for col in range(BOARD_SIZE):
                color = self.board[row][col]
                self.buttons[(row, col)].background_color = GEM_RGBA[color]

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

A few things worth noticing:

  • self.buttons is a dictionary keyed by (row, col), created once in build(). Every later lesson looks up "which button is at this position?" through this dictionary rather than re-creating widgets, Kivy widgets are relatively expensive to create, so the board reuses the same 64 buttons for the entire game.
  • render_board() is deliberately a separate method from build(): it only reads self.board and updates colors, it never creates new widgets. That means any later change to self.board (a swap, a match being cleared, gems falling) can be shown on screen just by calling self.render_board() again.
  • Note the important order: rows are added top to bottom, and within each row, columns left to right, matching GridLayout(cols=BOARD_SIZE)'s own top-to-bottom, left-to-right fill order, so self.buttons[(row, col)] really does end up in the right visual position.

TIP

Separating "update the data" from "redraw from the data" (the same idea as Pygame's update/draw split) is what makes the rest of this game's logic straightforward: every lesson from here on just changes self.board and then calls self.render_board().

📝 Displaying the Board Quiz

Passing score: 70%
  1. 1.Why does the app store buttons in a self.buttons dictionary keyed by (row, col) instead of recreating them?

  2. 2.render_board() creates new Button widgets every time it runs.

  3. 3.background_color expects an ____ tuple with each channel between 0 and 1.