Build a Match-3 Game using Python and Kivy

Lesson 3 of 8

Modeling the Board

Before drawing a single gem on screen, it's worth building the board as plain Python data, completely separate from Kivy. This keeps the game's rules testable and easy to reason about, the UI will just be a thin layer that reads and updates this data.

Representing the grid

An 8×8 board is a list of 8 rows, each row a list of 8 gems. Each gem is just a string naming its color:

import random

BOARD_SIZE = 8
GEM_COLORS = ['red', 'blue', 'green', 'yellow', 'purple']

def make_board():
    return [
        [random.choice(GEM_COLORS) for _col in range(BOARD_SIZE)]
        for _row in range(BOARD_SIZE)
    ]

board = make_board()
print(board[0])   # the top row, e.g. ['red', 'purple', 'blue', ...]
print(board[3][5])  # the gem at row 3, column 5

board[row][col] is the indexing convention used for the rest of this project, the outer list is rows (top to bottom), the inner list is columns (left to right) within that row.

Why keep the model separate from the UI?

It's tempting to store the gem's color as a property directly on a Kivy Button, and read it back from there whenever you need it. Resist that: keeping board as a plain Python list means:

  • You can write and test match-detection logic (next lesson) with no Kivy window involved at all, just print(board).
  • The UI's only job becomes "read board and draw it", and "translate taps back into changes to board", nothing about how a gem looks needs to leak into the rules for how gems match and fall.
def adjacent(pos1, pos2):
    r1, c1 = pos1
    r2, c2 = pos2
    return abs(r1 - r2) + abs(c1 - c2) == 1

print(adjacent((3, 4), (3, 5)))  # True, same row, next column
print(adjacent((3, 4), (4, 5)))  # False, diagonal, not adjacent

abs(r1 - r2) + abs(c1 - c2) is the Manhattan distance between two grid positions, it equals exactly 1 only when the two cells share a row or column and are right next to each other, exactly the "adjacent" rule a Match-3 swap needs, no diagonals allowed.

NOTE

random.choice(GEM_COLORS) doesn't check whether it accidentally created a match while building the initial board, that's fine for this project's prototype scope, a production game would re-roll any gem that would start pre-matched.

📝 Modeling the Board Quiz

Passing score: 70%
  1. 1.In this project's board representation, what does board[3][5] refer to?

  2. 2.Two grid positions are adjacent (for swapping purposes) if they are diagonal to each other.

  3. 3.abs(r1 - r2) + abs(c1 - c2) computes the ____ distance between two grid positions.