От простого к сложному, пишем на 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()
Рис. Получилось! И в первой же игре совсем неожиданный результат - побеждает Эппл.