От простого к сложному, пишем на Python оригинальную игру-головоломку в стиле Flip-Flop. Программируем графический интерфейс (GUI) с применением библиотеки Tkinter. Автором идеи поля 2х2х5 является Сергей Полозков.

Игра в стиле Flip-Flop с полем 2х2 клетки. В каждой клетке-кнопке может сменяться одна из пяти картинок. В начале игры, выбор картинки для каждой кнопки  происходит случайным образом. Когда игрок кликает по кнопке меняются картинки в трёх соседних клетках. Картинки меняются в строго определённом порядке (по очереди). Цель игры - вывести на все кнопки одинаковые картинки.

В качестве картинок мы выбрали логотипы олицетворяющие операционные системы:

    

Рис. 1. Linux, FreeBSD, Android, MacOS, Windows.

Создаём 4 кнопки для игрового поля:

from tkinter import *   # Импортируем все функции из библиотеки Tkinter.
 
Button().pack()         # С помощью конструктора Button()
Button().pack()         # создаём кнопки и
Button().pack()         # размещаем их в главном окне программы
Button().pack()         # методом pack().
 
mainloop()              # Главный цикл программы

Рис. Кнопки расположились с параметрами pack() по умолчанию (прижаты вверх).

Добавим несколько параметров в метод pack():

from tkinter import *
 
Button().pack(expand=YES, fill=BOTH)    # expand растянуть
Button().pack(expand=YES, fill=BOTH)    # fill растягивать, заполняя
Button().pack(expand=YES, fill=BOTH)    # предоставленную площадь
Button().pack(expand=YES, fill=BOTH)    # BOTH по горизонтали и вертикали
 
mainloop()

Рис. Кнопки растягиваются во всех направлениях.

Прижмём кнопки к левому краю окна:

from tkinter import *
 
Button().pack(side=LEFT, expand=YES, fill=BOTH)    # side прижимает кнопку
Button().pack(side=LEFT, expand=YES, fill=BOTH)    # LEFT к объекту слева.
Button().pack(side=LEFT, expand=YES, fill=BOTH)    # Первая кнопка прижата к левому краю
Button().pack(side=LEFT, expand=YES, fill=BOTH)    # главного окна программы (к контейнеру).

mainloop()

 

Рис. Кнопки прижаты влево.

Для того чтобы расположить кнопки в 2 ряда, добавим 2 горизонтальных фрейма и разместим в них по паре наших кнопок. При размещении фреймов будем учитывать полученный опыт с размещением кнопок (используем pack(expand=YES, fill=BOTH)):

from tkinter import *

frm1 = Frame()                                          # С помощью конструктора Frame()
frm1.pack(expand=YES, fill=BOTH)                        # создаём объекты класса Frame
frm2 = Frame()                                          # и даём им имена frm1 и frm2.
frm2.pack(expand=YES, fill=BOTH)                        # Frame невидимый объект (контейнер).
Button(frm1).pack(side=LEFT, expand=YES, fill=BOTH)     # Параметр в конструкторе Button()
Button(frm1).pack(side=LEFT, expand=YES, fill=BOTH)     # указывает на объект (контейнер)
Button(frm2).pack(side=LEFT, expand=YES, fill=BOTH)     # в котором необходимо
Button(frm2).pack(side=LEFT, expand=YES, fill=BOTH)     # разместить кнопку.

mainloop()

Рис. Кнопки расположились в 2-х фреймах попарно.

Было бы логичнее написать эту программу с использованием цикла:

from tkinter import *

frm1 = Frame()
frm1.pack(expand=YES, fill=BOTH)
frm2 = Frame()
frm2.pack(expand=YES, fill=BOTH)
for i in 'Ёж':                                          # Цикл For в котором переменная i
    Button(frm1).pack(side=LEFT, expand=YES, fill=BOTH) # последовательно принимает значения
    Button(frm2).pack(side=LEFT, expand=YES, fill=BOTH) # 'Ё' и 'ж' из строки. Всего 2 итерации.
    
mainloop()

А может и не логичнее.

Рис. Результат тот же.

Посмотрим в каком порядке создаются кнопки, иногда это бывает важно:

from tkinter import *

frm1 = Frame()
frm1.pack(expand=YES, fill=BOTH)
frm2 = Frame()
frm2.pack(expand=YES, fill=BOTH)
for i in [1, 3]:                    # i принимает значения 1 и 3 из списка [1, 3]
    Button(frm1, text=i).pack(side=LEFT, expand=YES, fill=BOTH)
    Button(frm2, text=i+1).pack(side=LEFT, expand=YES, fill=BOTH)
    
mainloop()

Рис. Порядок - сначала заполняются колонки колонки.

Попробуем переписать программу так, чтобы конструктор Button запускался 1 раз в каждой итерации цикла for и кнопки расположим построчно. Рекомендую для этого создать список с нашими фреймами так, чтобы оба наших фрейма (объекты класса Frame) имели имя с индексом. Ещё, вероятно, нам понадобится операция целочисленного деления. Получить целую часть частного (деления) можно с помощью функции int(x/y) или непосредственно использовать операцию целочисленного деления //:

from tkinter import *

frm = [Frame(), Frame()]            # Создаём именованный список с двумя фреймами.
frm[0].pack(expand=YES, fill=BOTH)  # К элементам списка можно обращаться
frm[1].pack(expand=YES, fill=BOTH)  # по индексу.
for i in range(0,4):                # i принимает значения 0,1,2,3 из диапазона range(0,4).
    Button(frm[i//2], text=i).pack(side=LEFT, expand=YES, fill=BOTH)
    
mainloop()

Рис. Порядок заполнения - построчно.

В этой игре все кнопки будут с картинками. Причём, картинок 5 и появляются они на кнопках в начале игры в случайном порядке.

Логично, если мы создадим пять объектов PhotoImage с картинками и также поместим их в список. Также нам понадобится функция случайного выбора числа. Воспользуемся функцией choice() из библиотеки random. Choice() случайным образом выбирает один из элементов списка или диапазона.

from tkinter import *
from  random import choice

frm = [Frame(), Frame()]
frm[0].pack(expand=YES, fill=BOTH)
frm[1].pack(expand=YES, fill=BOTH)
img = [PhotoImage(file='PenguinLinux.png'),   # pictures 100х100 px
       PhotoImage(file='FreeBSD.png'),
       PhotoImage(file='android.png'),
       PhotoImage(file='apple.png'),
       PhotoImage(file='windows.png'),]
for i in range(0,4):
    Button(frm[i//2], image=img[choice(range(0,5))]).pack(side=LEFT, expand=YES, fill=BOTH)
    
mainloop()

Рис. Кнопки с картинками.

По правилам игры, нажатие на кнопку меняет картинку на следующую на всех кнопках, кроме этой самой нажатой кнопки.

Значит нам необходимо написать функцию обработчик события - нажатие кнопки.

Прежде чем писать свою функцию необходимо разобраться что на что мы будем менять и какими средствами?

Как установить на кнопку новую картинку мы знаем, для этого необходимо воспользоваться методом config() объектов класса Button.
Но мы должны указать функцию config() с параметром: config(image=img[N]), где N равно 0, 1, 2, 3 или 4. Так что же поставить в качестве индекса N?
Мы сможем ответить на предыдущий вопрос, если будем знать какая картинка в данный момент размещена на кнопке.
Разбираться какая картинка на кнопке установлена мы не будем (но это возможно). Мы создадим список из 4 элементов (по количеству кнопок). Непосредственно этот список будет указывать какая картинка в данный момент находится на кнопке:

from tkinter import *
from  random import choice

frm = [Frame(), Frame()]
frm[0].pack(expand=YES, fill=BOTH)
frm[1].pack(expand=YES, fill=BOTH)
img = [PhotoImage(file='PenguinLinux.png'),
       PhotoImage(file='FreeBSD.png'),
       PhotoImage(file='android.png'),
       PhotoImage(file='apple.png'),
       PhotoImage(file='windows.png'),]
playArea = []                               # Создаём пустой список
for i in range(0,4):
    playArea.append(choice(range(0,5)))     # наполняем список случайными числами 
    Button(frm[i//2], image=img[playArea[i]]).pack(side=LEFT, expand=YES, fill=BOTH)
    
mainloop()

Рис. Результат тот же. Хорошо что ничего не испортилось.

Далее нам нужен будет доступ к свойствам каждой кнопки. Значит Все кнопки (объекты класса Button) должны быть именованными, и желательно проиндексированы. Создадим список из кнопок:

from tkinter import *
from  random import choice

frm = [Frame(), Frame()]
frm[0].pack(expand=YES, fill=BOTH)
frm[1].pack(expand=YES, fill=BOTH)
img = [PhotoImage(file='PenguinLinux.png'),
       PhotoImage(file='FreeBSD.png'),
       PhotoImage(file='android.png'),
       PhotoImage(file='apple.png'),
       PhotoImage(file='windows.png'),]
playArea = []; btn = []                               # Создаём пустые списки
for i in range(0,4):
    playArea.append(choice(range(0,5)))
    btn.append(Button(frm[i//2], image=img[playArea[i]]))
    btn[i].pack(side=LEFT, expand=YES, fill=BOTH)
    
mainloop()

 

Рис. Результат тот же, а программа всё сложнее.

Определяем функцию обработчик нажатия кнопки и привязываем эту функцию к кнопкам в конструкторе Button():

#!/usr/bin/env python
# -*- Mode: Python; coding: utf-8; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*- 
#
# OS.py
# Copyleft ???? 2021 Aleksandr Diorditsa
# The author of the idea of the 2x2x5 field is Sergey Polozkov
#
# OS.py is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the
# Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# 
# OS.py is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public License along
# with this program.  If not, see http://www.gnu.org/licenses/.

from tkinter import *
from  random import choice

def play(n):
    for i in range(0, 4):
        if i==n: continue
        playArea[i] = playArea[i]+1
        if (playArea[i]==5): playArea[i]=0
        btn[i].config(image=img[playArea[i]])

frm = [Frame(), Frame()]
frm[0].pack(expand=YES, fill=BOTH)
frm[1].pack(expand=YES, fill=BOTH)
img = [PhotoImage(file='PenguinLinux.png'),
       PhotoImage(file='FreeBSD.png'),
       PhotoImage(file='android.png'),
       PhotoImage(file='apple.png'),
       PhotoImage(file='windows.png'),]
playArea = []; btn = []
for i in range(0,4):
    playArea.append(choice(range(0,5)))
    btn.append(Button(frm[i//2], image=img[playArea[i]],
                      command=lambda n=i: play(n)))
    btn[i].pack(side=LEFT, expand=YES, fill=BOTH)
    
mainloop()

Рис. Получилось! И в первой же игре совсем неожиданный результат - побеждает Эппл.