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
boardand draw it", and "translate taps back into changes toboard", 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.