Чайный сервиз — логическая игра-головоломка на поле по 3 клетки в две строки. На поле выставлены чайник, сахарница и три чашки. Цель игры: переставить чайник и сахарницу местами. Напишем программу игры чайный сервиз на Python с библиотекой tkinter.

Создадим поле из 6 кнопок для игры чайный сервиз.

from tkinter import *

for i in range(6):
    Button().pack()

mainloop()

Лист. 1.

В программе листинг 1, в первой строке мы импортируем все функции из библиотеки tkinter. Графическая библиотека tkinter входит в стандартный дистрибутив Python версии 3 и старше и служит для создания графического интерфейса программ на Pyton. В версии 2.7 имеется библиотека Tkinter с тем же функционалом.

Ключевое слово for создаёт цикл, в котором переменная i последовательно принимает значения из диапазона range(6).

Функция range() создаёт диапазон значений. Параметрами функции range() могут быть границы диапазона и приращение (правая граница не включается в диапазон). 

Цикл for создаётся с помощью ключевых слов for и in, определение цикла заканчивается двоеточием :. После двоеточия начинается тело цикла. Каждый оператор тела  цикла, если он пишется с новой строки, должен иметь отступ слева.

Для выделения блока операторов в python, (например, тела цикла) рекомендуется добавлять перед каждым оператором блока 4 пробела. 

Цикл for i in range(6): выполняется 6 раз, при этом переменная i принимает значения от 0 до 6 с шагом 1. 0 включительно, 6 исключительно. Так как мы указали только один параметр диапазона range(), диапазон создан с левой границей 0 и шагом 1 по умолчанию. 6— правая граница диапазона. 

В цикле for выполняется конструктор Button() с методом pack(). 

Конструктор Button() создаёт объект кнопка. Метод pack() отображает объект кнопку в окне программы. Окно программы, создаётся библиотекой tkinter по умолчанию, в тот момент, когда мы создаём в программе визуальный объект (кнопку, например). Конструктор Button() выполняется в цикле 6 раз и наша программа, листинг 1, создаёт 6 кнопок в окне нашего приложения, см. рис. 1.

Рис. 1.

from tkinter import *

for i in range(6):
    Button().pack(expand=YES, fill=BOTH)

mainloop()

Лист. 2

В программе листинг 2 в методе pack() мы добавили два параметра.

Параметр expand со значением YES применяется для того, чтобы визуальный объект мог занять всё свободное пространство в контейнере. В нашем случае, кнопка (визуальный объект) размещается в окне программы (контейнер).

Свойство fill со значением BOTH позволяет визуальному объекту растягиваться в обоих направлениях, по X и по Y до размера контейнера.

Результат работы программы лист 2 смотрите на рисунке 2.

Рис. 2.

from tkinter import *

for i in range(6):
    Button().pack(expand=YES, fill=BOTH, side=LEFT)

mainloop()

Лист. 3.

В программе листинг 3 параметр side со значением LEFT прижимает визуальный объект, при размещении в контейнере, влево. Смотрите рисунок 3.

Рис. 3.

from tkinter import *

frm = [Frame(), Frame()]
frm[0].pack(expand=YES, fill=BOTH)
frm[1].pack(expand=YES, fill=BOTH)

for i in range(6):
    Button(frm[i//3]).pack(expand=YES, fill=BOTH, side=LEFT)

mainloop()

Лист. 4.

В программе листинг 5 мы создали пустой список с именем frm. В цикле из трёх повторений в список frm методом append() добавляется 3 фрейма. Фрейм — это контейнер для других визуальных объектов. По умолчанию фрейм невидим и прозрачен. Создаёт фреймы конструктор Frame(). В нашей программе мы размещаем невидимые фреймы на экране растянутыми по горизонтали методом pack() с соответствующими параметрами.

В итоге, мы имеем список frm состоящий из трёх фреймов. К верхнему фрейму можно обратиться по имени frm[0], к среднему — по имени frm[1], к нижнему — frm[3].

Во внутреннем цикле for методом Button() с параметром frm[i] создаются кнопки. Первый параметр метода Button() указывает на родительский контейнер в котором должна быть размещены кнопка. Так как переменная i первые три повторения внутреннего цикла имеет значение 0, то первые три кнопки размещаются в верхнем фрейме. Ещё три кнопки размещаются в среднем фрейме и ещё три — в нижнем.

Рис. 4

В дальнейшем  у нас возникнет необходимость обращаться к кнопкам, как к объектам, по имени. Поэтому, в программе листинг 6 мы создали пустой список btn в который в цикле будет добавлено 9 кнопок. 

from tkinter import *

btn = []

frm = [Frame(), Frame()]
for i in frm: i.pack(expand=YES, fill=BOTH)

for i in range(6):
    btn.append(Button(frm[i//3]))
    btn[i].pack(expand=YES, fill=BOTH, side=LEFT)

mainloop()

Лист. 6

В программе листинг 6 в цикле из 9 повторений в список btn методом append() добавляется 9 объектов класса Button (кнопоки). В списке btn все кнопки имеют индекс (номер) от 0 до 8 включительно. Чтобы разместить эти кнопки на экране методом pack(), к каждой кнопке мы должны обратиться по индексу. Индекс очередной кнопки мы вычисляем, используя параметры внешнего и внутреннего цикла, переменные i и j.

В остальном, программа листинг 6 не отличается от программы листинг 5.

Рис. 6

Рис. 6

from tkinter import *

btn = []; img = []
playArea = [2, 0, 3, 1, 1, 1]

tk = Tk()
tk.title('Переставьте местами чайник и сахарницу')
img.append(PhotoImage(file='blank.png'))
img.append(PhotoImage(file='cups.png'))
img.append(PhotoImage(file='tea.png'))
img.append(PhotoImage(file='sugar.png'))

frm = [Frame(), Frame()]
for i in frm: i.pack(expand=YES, fill=BOTH)

for i in range(6):
    btn.append(Button(frm[i//3]))
    btn[i].pack(expand=YES, fill=BOTH, side=LEFT)
    btn[i].config(image=img[playArea[i]])

mainloop()

Лист. 7.

from tkinter import *

btn = []; img = []
file = ['blank.png', 'cups.png', 'tea.png', 'sugar.png']
playArea = [2, 0, 3, 1, 1, 1]

tk = Tk()
tk.title('Переставьте местами чайник и сахарницу')
for i in file: img.append(PhotoImage(file=i))

frm = [Frame(), Frame()]
for i in frm: i.pack(expand=YES, fill=BOTH)

for i in range(6):
    btn.append(Button(frm[i//3]))
    btn[i].pack(expand=YES, fill=BOTH, side=LEFT)
    btn[i].config(image=img[playArea[i]])

mainloop()

Лист. 7a.

Рис. 6

from tkinter import *

btn = []; img = []
file = ['blank.png', 'cups.png', 'tea.png', 'sugar.png']
playArea = [2, 0, 3, 1, 1, 1]

def play(n):
    m = playArea.index(0)
    playArea[m], playArea[n] = playArea[n], playArea[m]
    btn[m].config(image=img[playArea[m]])
    btn[n].config(image=img[playArea[n]])

tk = Tk()
tk.title('Переставьте местами чайник и сахарницу')
for i in file: img.append(PhotoImage(file=i))

frm = [Frame(), Frame()]
for i in frm: i.pack(expand=YES, fill=BOTH)

for i in range(6):
    btn.append(Button(frm[i//3]))
    btn[i].pack(expand=YES, fill=BOTH, side=LEFT)
    btn[i].config(image=img[playArea[i]])
    btn[i].config(command=lambda n=i:play(n))

mainloop()

Лист. 

from tkinter import *

btn = []; img = []
file = ['blank.png', 'cups.png', 'tea.png', 'sugar.png']
playArea = [2, 0, 3, 1, 1, 1]

def play(n):
    m = playArea.index(0)
    if (abs(m - n) + abs(n//3 - m//3)) == 1 or abs(m - n) == 3:
        playArea[m], playArea[n] = playArea[n], playArea[m]
        btn[m].config(image=img[playArea[m]])
        btn[n].config(image=img[playArea[n]])

tk = Tk()
tk.title('Переставьте местами чайник и сахарницу')
for i in file: img.append(PhotoImage(file=i))

frm = [Frame(), Frame()]
for i in frm: i.pack(expand=YES, fill=BOTH)

for i in range(6):
    btn.append(Button(frm[i//3]))
    btn[i].pack(expand=YES, fill=BOTH, side=LEFT)
    btn[i].config(image=img[playArea[i]])
    btn[i].config(command=lambda n=i:play(n))

mainloop()

Лист. .

В программе листинг 7 с помощью ключевого слова  def мы создали функцию play(). Новые функции, созданные программистом в программе называют функциями пользователя. Новые функции, обычно, создают в начале программы после определения глобальных переменных, до их первого упоминания в коде программы.

В теле функции play() мы прописали метод config() применяемый к кнопке по которой кликнул игрок. Параметр text метода config() выводит на кнопку надпись. Функция str() превращает число в строку. Параметр n, передаваемый в функцию play() это номер кнопки по которой кликнул игрок.

Функция play() будет зарегистрирована как обработчик события "клик мышью по кнопке". Обработчик события по умолчанию для кнопки можно зарегистрировать методом config(). Параметр command метода config() регистрирует обработчик события. Так как значением параметра command должно быть имя функции без скобок, мы для передачи параметра в функцию play в случае возникновения события, воспользовались lambda функцией. 

Первый параметр lambda функции n вычисляется в процессе создания объекта кнопка, а второй параметр, указанный в lambda функции после двоеточия, передаётся для регистрации в качестве обработчика события. А в lambda функции мы указываем функцию play() с круглыми скобками и передаваемым параметром n. Причём, параметр n вычисляется на этапе создания кнопки и сохраняется в свойстве command как значение. То есть, функция play, как обработчик события будет вызвана с параметром номер кнопки, который был вычислен на этапе создания кнопки. 

Рис. .

from tkinter import *

btn = []; img = []
file = ['blank.png', 'cups.png', 'tea.png', 'sugar.png']
playArea = [2, 0, 3, 1, 1, 1]
num = 0

def play(n):
    global num
    m = playArea.index(0)
    if (abs(m - n) + abs(n//3 - m//3)) == 1 or abs(m - n) == 3:
        playArea[m], playArea[n] = playArea[n], playArea[m]
        btn[m].config(image=img[playArea[m]])
        btn[n].config(image=img[playArea[n]])
        num += 1
        if (playArea[0] == 3 and playArea[2] == 2):
            tk.title('Вы победили за '+str(num)+' ходов')

tk = Tk()
tk.title('Переставьте местами чайник и сахарницу')
for i in file: img.append(PhotoImage(file=i))

frm = [Frame(), Frame()]
for i in frm: i.pack(expand=YES, fill=BOTH)

for i in range(6):
    btn.append(Button(frm[i//3]))
    btn[i].pack(expand=YES, fill=BOTH, side=LEFT)
    btn[i].config(image=img[playArea[i]])
    btn[i].config(command=lambda n=i:play(n))

mainloop()

Лист.

Рис.

from tkinter import *
from  time import time

btn = []; img = []
file = ['blank.png', 'cups.png', 'tea.png', 'sugar.png']
playArea = [2, 0, 3, 1, 1, 1]
num = 0; t = 0

def play(n):
    global num, t
    m = playArea.index(0)
    if (abs(m - n) + abs(n//3 - m//3)) == 1 or abs(m - n) == 3:
        playArea[m], playArea[n] = playArea[n], playArea[m]
        btn[m].config(image=img[playArea[m]])
        btn[n].config(image=img[playArea[n]])
        num += 1
        if t == 0: t = time()
        if (playArea[0] == 3 and playArea[2] == 2):
            tk.title('Вы победили за '+str(num)+' ходов и '+str(int(time()-t))+' сек.')

tk = Tk()
tk.title('Переставьте местами чайник и сахарницу')
for i in file: img.append(PhotoImage(file=i))

frm = [Frame(), Frame()]
for i in frm: i.pack(expand=YES, fill=BOTH)

for i in range(6):
    btn.append(Button(frm[i//3]))
    btn[i].pack(expand=YES, fill=BOTH, side=LEFT)
    btn[i].config(image=img[playArea[i]])
    btn[i].config(command=lambda n=i:play(n))

mainloop()

Лист.

Рис.

from tkinter import *
from  time import time

btn = []; img = []
file = ['blank.png', 'cups.png', 'tea.png', 'sugar.png']
playArea = [2, 0, 3, 1, 1, 1]
num = 0; t = 0
game = True

def play(n):
    global num, t, game
    m = playArea.index(0)
    if (abs(m - n) + abs(n//3 - m//3)) == 1 or abs(m - n) == 3:
        playArea[m], playArea[n] = playArea[n], playArea[m]
        btn[m].config(image=img[playArea[m]])
        btn[n].config(image=img[playArea[n]])
        num += 1
        if t == 0: t = time()
        if (playArea[0] == 3 and playArea[2] == 2 and game) or (playArea[0] == 2 and playArea[2] == 3 and not(game)):
            tk.title('Вы победили за '+str(num)+' ходов и '+str(int(time()-t))+' сек.')
            num = 0; t = 0; game = not(game)

tk = Tk()
tk.title('Переставьте местами чайник и сахарницу')
for i in file: img.append(PhotoImage(file=i))

frm = [Frame(), Frame()]
for i in frm: i.pack(expand=YES, fill=BOTH)

for i in range(6):
    btn.append(Button(frm[i//3]))
    btn[i].pack(expand=YES, fill=BOTH, side=LEFT)
    btn[i].config(image=img[playArea[i]])
    btn[i].config(command=lambda n=i:play(n))

mainloop()

Лист.

#!/usr/bin/env python
# -*- Mode: Python; coding: utf-8; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*- 
#
# puzzle.py
# Copyright (C) 2021 Aleksandr Diorditsa, see <https://adior.ru>
# I thank Sergey Polozkov for checking the code for hidden errors.
#
# puzzle.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.
# 
# puzzle.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  time import time

btn = []; img = []
file = ['blank.png', 'cups.png', 'tea.png', 'sugar.png']
playArea = [2, 0, 3, 1, 1, 1]
num = 0; t = 0
game = True
recordN = 100
recordT = 100

def play(n):
    global num, t, game, recordN, recordT
    m = playArea.index(0)
    if (abs(m - n) + abs(n//3 - m//3)) == 1 or abs(m - n) == 3:
        playArea[m], playArea[n] = playArea[n], playArea[m]
        btn[m].config(image=img[playArea[m]])
        btn[n].config(image=img[playArea[n]])
        num += 1
        if t == 0: t = time()
        if (playArea[0] == 3 and playArea[2] == 2 and game) or (playArea[0] == 2 and playArea[2] == 3 and not(game)):
            if (recordN > num) or (recordN == num and recordT > time()-t):
                recordN = num
                recordT = int(time()-t)
            tk.title('Вы победили за '+str(num)+' ходов и '+str(int(time()-t))+' сек. Рекорд '+ str(recordN)+ ' и '+str(recordT))
            num = 0; t = 0; game = not(game)

tk = Tk()
tk.title('Переставьте местами чайник и сахарницу')
for i in file: img.append(PhotoImage(file=i))

frm = [Frame(), Frame()]
for i in frm: i.pack(expand=YES, fill=BOTH)

for i in range(6):
    btn.append(Button(frm[i//3]))
    btn[i].pack(expand=YES, fill=BOTH, side=LEFT)
    btn[i].config(image=img[playArea[i]])
    btn[i].config(command=lambda n=i:play(n))

mainloop()

 

Лист.

Рис.

Рис.

#!/usr/bin/env python
# -*- Mode: Python; coding: utf-8; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*- 
#
# puzzle.py
# Copyright (C) 2021 Aleksandr Diorditsa, see <https://adior.ru>
# I want to thank Sergey Polozkov for checking the code for hidden errors.
# I want to thank Sergey Polozkov for the idea of creating this puzzle.
# I want to thank all my students for the inspiration they give me.
#
# puzzle.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.
# 
# puzzle.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  time import time

btn = []; img = []
file = ['blank.png', 'cups.png', 'cup.png', 'tea.png', 'sugar.png']
playArea = [3, 0, 4, 1, 2, 1]
gameTrue = [4, 0, 3, 1, 2, 1]
gameFalse = [3, 0, 4, 1, 2, 1]
num = 0; t = 0
game = True
recordN = 100
recordT = 100

def play(n):
    global num, t, game, recordN, recordT
    m = playArea.index(0)
    if (abs(m - n) + abs(n//3 - m//3)) == 1 or abs(m - n) == 3:
        playArea[m], playArea[n] = playArea[n], playArea[m]
        btn[m].config(image=img[playArea[m]])
        btn[n].config(image=img[playArea[n]])
        num += 1
        if t == 0: t = time()
        if (playArea == gameTrue and game) or (playArea[0] == 3 and playArea == gameFalse and not(game)):
            if (recordN > num) or (recordN == num and recordT > time()-t):
                recordN = num
                recordT = int(time()-t)
            tk.title('Вы победили за '+str(num)+' ходов и '+str(int(time()-t))+' сек. Рекорд '+ str(recordN)+ ' и '+str(recordT))
            num = 0; t = 0; game = not(game)

tk = Tk()
tk.title('Переставьте местами только чайник и сахарницу')
for i in file: img.append(PhotoImage(file=i))

frm = [Frame(), Frame()]
for i in frm: i.pack(expand=YES, fill=BOTH)

for i in range(6):
    btn.append(Button(frm[i//3]))
    btn[i].pack(expand=YES, fill=BOTH, side=LEFT)
    btn[i].config(image=img[playArea[i]])
    btn[i].config(command=lambda n=i:play(n))

mainloop()

Лист.

Рис.