Selecting and Swapping Gems
With gems visible on screen, the next step is making them tappable: select one gem, tap an adjacent one, and swap them.
Tracking the current selection
class Match3App(App):
def build(self):
self.board = make_board()
self.selected = None # will hold a (row, col) tuple, or None
self.grid = GridLayout(cols=BOARD_SIZE)
self.buttons = {}
for row in range(BOARD_SIZE):
for col in range(BOARD_SIZE):
gem = Button(text='', background_normal='')
gem.bind(on_press=self.make_gem_handler(row, col))
self.buttons[(row, col)] = gem
self.grid.add_widget(gem)
self.render_board()
return self.grid
def make_gem_handler(self, row, col):
def handler(instance):
self.on_gem_tapped(row, col)
return handler
bind(on_press=...) only passes Kivy the widget instance that was pressed, not which row/col it represents. make_gem_handler(row, col) solves this with a closure: it returns a fresh handler function that remembers its own row and col from when it was created, one such closure is made per button, in the same loop that creates the button itself.
Handling a tap
def on_gem_tapped(self, row, col):
if self.selected is None:
self.selected = (row, col)
return
first = self.selected
second = (row, col)
self.selected = None
if first == second:
return # tapped the same gem twice, treat as a deselect
if not adjacent(first, second):
self.selected = second # start a fresh selection from here instead
return
self.swap(first, second)
self.render_board()
The logic has three outcomes for a second tap: the same gem (cancel), a non-adjacent gem (start over from that gem instead of swapping), or a valid adjacent gem (swap). Storing self.selected = None at the very top of the "second tap" branch, before any of those checks, means every path correctly clears the selection, there's no way to get stuck with a stale self.selected.
Swapping two positions
def swap(self, pos1, pos2):
r1, c1 = pos1
r2, c2 = pos2
self.board[r1][c1], self.board[r2][c2] = self.board[r2][c2], self.board[r1][c1]
Python's tuple-assignment swap (a, b = b, a) works just as well on two list-of-list cells as it does on two plain variables, no temporary variable needed.
WARNING
This lesson's version swaps unconditionally, even if the swap doesn't create any match. A real Match-3 game only allows swaps that produce at least one match, swapping back otherwise, that's exactly what the next lesson's match-detection makes possible.