Печать

Pyzzle 15, игра известная в нашей стране, как «Пятнашки». «Пятнашки» – это логическая игра-головоломка. Цель игры – упорядочить пронумерованные плитки на игровом поле размером 4Х4 клетки.

Пишем компьютерную версию игры на языке программирования Python с использованием библиотеки Pygame Zero.

import warnings
warnings.filterwarnings("ignore", category=RuntimeWarning, message=".*AVX2.*")
import pgzrun
"""
Pygame Zero (pgzrun) - это обёртка над Pygame,
которая упрощает создание игр,
автоматизируя многие рутинные задачи.
"""

pgzrun.go()      # Запускается главный игровой цикл

Лист. 1. Подключаем библиотеку pgzrun (pygame) и запускаем главный игровой цикл.

В программе листинг 1 метод filterwarnings из библиотеки warnings устраняет сообщение о не оптимально скомпилированной для вашего компьютера библиотеке pgzrun (pygame).  Функция go() из библиотеки pgzrun запускает главный игровой цикл.

Рис. 1. Окно, созданное программой листинг 1.

import warnings
warnings.filterwarnings("ignore", category=RuntimeWarning, message=".*AVX2.*")
import pgzrun
"""
Pygame Zero (pgzrun) - это обёртка над Pygame,
которая упрощает создание игр,
автоматизируя многие рутинные задачи.
"""


def draw():
    """
    draw() особая функция, вызывается автоматически каждый кадр,
    обычно, 60 FPS. Доступны объекты: screen, actors и др.
    Назначение: отрисовка всего, что должно быть на экране.
    """
    pass


def on_mouse_down(pos):
    """
    on_mouse_down(pos) особая функция, вызывается автоматически при клике мыши
    Через параметр pos в функцию передаётся кортеж из пары координат (x, y)
    """
    pass


def on_key_down(key):
    """
    on_key_down(pos) особая функция, вызывается автоматически при нажатии
    клавиши на клавиатуре. Через параметр key в функцию передаётся
    код нажатой клавиши.
    """
    pass


pgzrun.go()      # Запускается главный игровой цикл

Лист. 2. В программу добавлены функции, периодически запускаемые из цикла в функции pgzrun().

Если вы в своей программе создадите функции с именами draw, on_mouse_down, on_key_down и некоторые другие функции, то эти функции будут вызываться (запускаться на выполнение) автоматически, так как их вызовы прописаны в функции pgzrun.

import warnings
warnings.filterwarnings("ignore", category=RuntimeWarning, message=".*AVX2.*")
import pgzrun
"""
Pygame Zero (pgzrun) - это обёртка над Pygame,
которая упрощает создание игр,
автоматизируя многие рутинные задачи.
"""


# Настройки игры, глобальные константы и переменные
TITLE = "Головоломка 15"                        # заголовок окна
WIDTH = HEIGHT = 500                            # ширина и высота окна
BACKGROUND_COLOR = 'khaki'                      # цвет кнопки


def draw():
    """
    draw() особая функция, вызывается автоматически каждый кадр,
    обычно, 60 FPS. Доступны объекты: screen, actors и др.
    Назначение: отрисовка всего, что должно быть на экране.
    """
    screen.fill(BACKGROUND_COLOR)               # очистка экрана


def on_mouse_down(pos):
    """
    on_mouse_down(pos) особая функция, вызывается автоматически при клике мыши
    Через параметр pos в функцию передаётся кортеж из пары координат (x, y)
    """
    pass


def on_key_down(key):
    """
    on_key_down(pos) особая функция, вызывается автоматически при нажатии
    клавиши на клавиатуре. Через параметр key в функцию передаётся
    код нажатой клавиши.
    """
    pass


pgzrun.go()                                     # Запускается главный игровой цикл

Лист. 3. В программу добавлены 3 глобальные константы и добавлена функция очистки экрана в определение функции draw.

Обратите внимание, у функции draw в программе листинг 3 появился функционал. В функцию draw добавлен метод fill объекта screen. Fill (заливка) очищает screen (экран), заливая окно сгенерированное программой определённым цветом.

Рис. 2. Окно, созданное программой листинг 3.

В программе листинг 3 мы создали глобальную константу BACKGROUND_COLOR со значением "khaki". Эту константу, в качестве параметра получает функция fill в определении функции draw. Функция fill интерпретирует значение "khaki" строкового типа, как цвет и использует этот цвет для заливки окна.

В программах с графическим интерфейсом, написанных на  Python, чаще всего, мы можем использовать коды цветов или их строковые значения из таблицы 1.

Образец Код Название CSS Название
          #F0F8FF aliceblue блекло-голубой
         #FAEBD7 antiquewhite античный белый
     #00FFFF aqua синий
          #7FFF00 chartreuse фисташковый
          #F5F5DC azure лазурь
          #F5F5DC beige бежевый
          #FFE4C4 bisque бисквитный
          #FFF8DC cornsilk темно-зеленый
       #FFEBCD blanchedalmond светло-кремовый
       #0000FF blue голубой
       #8A2BE2 blueviolet светло-фиолетовый
       #B8860B darkgoldenrot темный красно-золотой
       #006400 darkgreen темно-зеленый
       #8B008B darkmagenta темный фуксин
       #FF8C00 darkorange темно-оранжевый
    #8B0000 darkred темно-красный
    #8FBC8F darkseagreen темный морской волны
    #2F4F4F darkslategray темный сине-серый
    #9400D3 darkviolet темно-фиолетовый
    #00BFFF deepskyblue темный небесно-голубой
    #1E90FF dodgerblue тускло-васильковый
    #FFFAF0 floralwhite цветочно-белый
    #FF00FF fuchsia фуксии
    #DCDCDC gainsboro гейнсборо
    #DAA520 goldenrod красного золота
    #008000 green зеленый
    #F0FFF0 honeydew свежего меда
    #CD5C5C indianred ярко-красный
    #FFFFF0 ivory слоновой кости
    #E6E6FA lavender бледно-лиловый
    #7CFC00 lawngreen зеленой лужайки
    #ADD8E6 lightblue светло-голубой
    #E0FFFF lightcyan светло-циановый
    #90EE90 lightgreen светло-зеленый
    #FFB6C1 lightpink светло-розовый
    #20B2AA lightseagreen светлый морской волны
    #778899 lightslategray светлый сине-серый
    #FFFFE0 lightyellow светло-желтый
    #32CD32 limegreen зеленовато-известковый
    #FF00FF фуксин blanchedalmond
    #66CDAA mediumaquamarine умеренно-аквамариновый
    #3CB371 mediumseagreen умеренный морской волны
    #BA55D3 mediumorchid умеренно-орхидейный
    #00FA9A mediumspringgreen умеренный сине-серый
    #0C71585 mediumvioletred умеренный красно-фиолетовый
    #0F5FFFA mintcream мятно-кремовый
    #FFE4B5 moccasin болотный
    #000080 navy морской
    #808000 olive оливковый
    #FFA500 orange оранжевый
    #DA70D6 orchid орхидейный
    #98FB98 palegreen бледно-зеленый
    #DB7093 palevioletred бледный красно-фиолетовый
    #FFDAB9 peachpuff персиковый
    #FFC0CB pink розовый
    #B0E0E6 powderblue туманно-голубой
    #FF0000 red красный
    #4169E1 royalblue королевский голубой
    #FA8072 salmon оранжево-розовый
    #2E8B57 seagreen морской зеленый
    #A0522D sienna охра
        #87CEEB skyblue небесно-голубой
        #708090 slategray сине-серый
        #00FF7F springgreen весенне-зеленый
        #D2B48C tan желто-коричневый
        #D8BFD8 thistle чертополоха
        #40E0D0 turquoise бирюзовый
        #F5DEB3 wheat пшеничный
        #F5F5F5 whitesmoke белый дымчатый
        #9ACD32 yellowgreen желто-зеленый
          #A52A2A brown коричневый
         #DEB887 burlywood старого дерева
     #5F9EA0 cadetblue блеклый серо-голубой
          #FF7F50 coral коралловый
          #D2691E chocolate шоколадный
          #6495ED cornflowerblue васильковый
          #000000 black черный
          #DC143C crimson малиновый
       #00FFFF cyan циан
       #00008B darkblue темно-голубой
       #008B8B darkcyan темный циан
    #A9A9A darkgray темно-серый
       #BDB76B darkkhaki темный хаки
       #556B2F darkolivegreen темно-оливковый
    #9932CC darkorchid темно-орхидейный
    #E9967A darksalmon темно-оранжево-розовый
    #483D8B darkslateblue темный сине-серый
    #00CED1 darkturqueise темно-бирюзовый
    #FF1493 deeppink темно-розовый
    #696969 dimgray тускло-серый
    #B22222 firebrick огнеупорного кирпича
    #228B22 forestgreen лесной зеленый
    #F8F8FF ghostwhite туманно-белый
    #FFD700 gold золотой
    #808080 gray серый
    #ADFF2F greenyellow желто-зеленый
    #FF69B4 hotpink ярко-розовый
    #4B0082 indigo индиго
    #F0E68C khaki хаки
    #FFF0F5 lavenderblush бледный розово-лиловый
    #FFFACD lemonchiffon лимонный
    #F08080 lightcoral светло-коралловый
    #FAFAD2 lightgoldenrodyellow светлый золотисто-желтый
    #D3D3D3 lightgrey светло-серый
    #FFA07A lightsalmon светлый оранжево-розовый
    #87CEFA lightskyblue светлый небесно-голубой
    #B0C4DE lightsteelblue светло-стальной
    #00FF00 lime цвета извести
    #FAF0E6 linen льняной
    #800000 maroon оранжево-розовый
    #0000CD mediumblue умеренно-голубой
    #7B68EE mediumslateblue умеренный серо-голубой
    #9370DB mediumpurple умеренно-пурпурный
    #48D1CC mediumturquose умеренно-бирюзовый
    #191970 midnightblue полуночный синий
    #FFE4E1 mistyrose туманно-розовый
    #FFDEAD navajowhite грязно-серый
    #FDF5E6 oldlace старого коньяка
    #6B8E23 olivedrab
    #FF4500 orangered оранжево-красный
    #EEE8AA palegoldenrod бледно-золотистый
    #AFEEEE paleturquoise бледно-бирюзовый
    #FFEFD5 papayawhip дынный
    #CD853F peru коричневый
    #DDA0DD plum сливовый
    #800080 purple пурпурный
    #BC8F8F rosybrown розово-коричневый
    #8B4513 saddlebrown старой кожи
    #F4A460 sandybrown рыже-коричневый
    #FFF5EE seashell морской пены
    #C0C0C0 silver серебристый
        #6A5ACD slateblue серо-голубой
        #FFFAFA snow снежный
        #4682B4 steelblue сине-стальной
        #008080 teal чайный
        #FF6347 tomato томатный
        #EE82EE violet фиолетовый
        #FFFFFF white белый
        #FFFF00 yellow желтый
        #7FFFD4 aquamarine аквамарин

Табл. 1. Таблица цветов с кодами и названиями, которые применимы в Python с библиотекой Pygame, pgzrun. Каждый раз, когда в результате действий игрока или игрового алгоритма ситуация на игровом поле меняется, программа должна перерисовать игровое поле и тем самым привести его в соответствие сложившейся игровой ситуации. Для этой цели в библиотеке pgzrun, в соответствии с определённым алгоритмом, вызывается функция draw. В нашей программе, функция draw должна:

В программе листинг 4 мы предусмотрели весь этот функционал.

import warnings
warnings.filterwarnings("ignore", category=RuntimeWarning, message=".*AVX2.*")
import pgzrun
"""
Pygame Zero (pgzrun) - это обёртка над Pygame,
которая упрощает создание игр,
автоматизируя многие рутинные задачи.
"""


# Настройки игры, глобальные константы и переменные
TITLE = "Головоломка 15"                        # заголовок окна
WIDTH = HEIGHT = 500                            # ширина и высота окна
BACKGROUND_COLOR = 'khaki'                      # цвет кнопки


def draw_grid():
    """
    Рисует игровое поле в клетку
    """
    pass


def draw_numbers():
    """
    Выводит в клетках игрового поля цифры,
    соответствующие игровой ситуации
    """
    pass


def draw_text():
    """
    Выводит на игровое поле текстовую информацию,
    приглашение к началу игры:
    Клавиша пробел - Старт
    """
    pass


def draw():
    """
    draw() особая функция, вызывается автоматически каждый кадр,
    обычно, 60 FPS. Доступны объекты: screen, actors и др.
    Назначение: отрисовка всего, что должно быть на экране.
    """
    screen.fill(BACKGROUND_COLOR)               # очистка экрана
    draw_grid()                                 # нарисовать клетки
    draw_numbers()                              # вывести цифры
    draw_text()                                 # показать текстовую информацию


def on_mouse_down(pos):
    """
    on_mouse_down(pos) особая функция, вызывается автоматически при клике мыши
    Через параметр pos в функцию передаётся кортеж из пары координат (x, y)
    """
    pass


def on_key_down(key):
    """
    on_key_down(pos) особая функция, вызывается автоматически при нажатии
    клавиши на клавиатуре. Через параметр key в функцию передаётся
    код нажатой клавиши.
    """
    pass


pgzrun.go()                                     # Запускается главный игровой цикл

Лист. 4. В программу добавлены определения функций draw_grid(), draw_numbers(), draw_text() и их вызов из функции draw().

В программе листинг 4 созданы прототипы (определения) нужных нам функций, но не прописаны их тела. Так как в Python функция не может быть создана без тела, временно, в тело новых функций запишем оператор pass, который ничего не делает.

Теперь приступим к разработке этих функций и начнём с функции draw_grid.

def draw_grid():
    """
    Рисует игровое поле в клетку
    """
    screen.draw.line((125,0),(125,500), "dimgray")      # вертикальная линия
    screen.draw.line((250,0),(250,500), "dimgray")      # вертикальная линия
    screen.draw.line((375,0),(375,500), "dimgray")      # вертикальная линия
    screen.draw.line((0,125),(500,125), "dimgray")      # горизонтальная линия
    screen.draw.line((0,250),(500,250), "dimgray")      # горизонтальная линия
    screen.draw.line((0,375),(500,375), "dimgray")      # горизонтальная линия

Определение функции draw_grid.

Чтобы код нашей программы легче было разрабатывать, а в дальнейшем поддерживать, желательно параметры, используемые в методе draw.line в функции draw_grid определить как глобальные константы и использовать имена этих констант.

# Настройки игры, глобальные константы и переменные
TITLE = "Головоломка 15"                        # заголовок окна
WIDTH = HEIGHT = 500                            # ширина и высота окна
SIZE = 4                                        # размер игрового поля (4x4)
CELL_SIZE = WIDTH // SIZE                       # размер клетки
BACKGROUND_COLOR = 'khaki'                      # цвет кнопки
GRID_COLOR = 'dimgray'                          # цвет сетки


def draw_grid():
    """
    Рисует игровое поле в клетку
    """
    screen.draw.line((CELL_SIZE,0),(CELL_SIZE,HEIGHT), GRID_COLOR)          # вертикальная линия
    screen.draw.line((CELL_SIZE*2,0),(CELL_SIZE*2,HEIGHT), GRID_COLOR)      # вертикальная линия
    screen.draw.line((CELL_SIZE*3,0),(CELL_SIZE*3,HEIGHT), GRID_COLOR)      # вертикальная линия
    screen.draw.line((0,CELL_SIZE),(WIDTH,CELL_SIZE), GRID_COLOR)           # горизонтальная линия
    screen.draw.line((0,CELL_SIZE*2),(WIDTH,CELL_SIZE*2), GRID_COLOR)       # горизонтальная линия
    screen.draw.line((0,CELL_SIZE*3),(WIDTH,CELL_SIZE*3), GRID_COLOR)       # горизонтальная линия

Определение некоторых глобальных констант и функции draw_grid.

Циклы позволяют существенно сократить количество  строк повторяющегося кода. В выше приведённом варианте функции draw_grid, желательно для этой цели использовать цикл.

В Python мы можем использовать цикл for или цикл while. Цикл for, чаще используется когда известно количество повторений тела цикла, а цикл while используют тогда, когда количество повторений тела цикла заранее не известно.

Функция draw_grid должна нарисовать на игровом поле три горизонтальные и три вертикальные линии. Если тело цикла будет состоять из двух строк одна из которых будет рисовать горизонтальные линии, а вторая - вертикальные, то цикл должен будет повторить эти действия три раза. Следовательно нам подойдёт цикл for.

Цикл for и функция range.

for n in range(0,10,1):
    print(n, end='; ')

Пример использования цикла for и функции range.

0; 1; 2; 3; 4; 5; 6; 7; 8; 9; 

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

for n in range(0,500,125):
    print(n, end='; ')

Пример использования цикла for и функции range применимый в нашей программе.

0; 125; 250; 375; 

Результат работы программы с циклом.  Так как WIDTH=500, а CELL_SIZE=125, заменим числовые значения параметров функции range соответствующими им константами. Эти значения, посредством переменной цикла n, могут быть использованы в качестве параметров в методе screen.draw.line().

for n in range(0, WIDTH, CELL_SIZE):
    print(n)

Этот цикл работает так же, как и предыдущий, но теперь при изменении значения константы WIDTH в начале программы изменится размер окна игрового поля и пропорционально ему изменится и размер сетки на игровом поле. Так же, изменение значения константы SIZE приведёт к перерасчёту константы CELL_SIZE, что приведёт к изменению числа линий сетки на игровом поле.

import warnings
warnings.filterwarnings("ignore", category=RuntimeWarning, message=".*AVX2.*")
import pgzrun
"""
Pygame Zero (pgzrun) - это обёртка над Pygame,
которая упрощает создание игр,
автоматизируя многие рутинные задачи.
"""


# Настройки игры, глобальные константы и переменные
TITLE = "Головоломка 15"                        # заголовок окна
WIDTH = HEIGHT = 500                            # ширина и высота окна
SIZE = 4                                        # размер игрового поля (4x4)
CELL_SIZE = WIDTH // SIZE                       # размер клетки
BACKGROUND_COLOR = 'khaki'                      # цвет кнопки
GRID_COLOR = 'dimgray'                          # цвет сетки


def draw_grid():
    """
    Рисует игровое поле в клетку
    """
    for n in range(0, WIDTH, CELL_SIZE):                        # линии сетки
        screen.draw.line((n, 0), (n, HEIGHT), GRID_COLOR)       # вертикальные линии
        screen.draw.line((0, n), (WIDTH, n), GRID_COLOR)        # горизонтальные линии


def draw_numbers():
    """
    Выводит в клетках игрового поля цифры,
    соответствующие игровой ситуации
    """
    pass


def draw_text():
    """
    Выводит на игровое поле текстовую информацию,
    приглашение к началу игры:
    Клавиша пробел - Старт
    """
    pass


def draw():
    """
    draw() особая функция, вызывается автоматически каждый кадр,
    обычно, 60 FPS. Доступны объекты: screen, actors и др.
    Назначение: отрисовка всего, что должно быть на экране.
    """
    screen.fill(BACKGROUND_COLOR)               # очистка экрана
    draw_grid()                                 # нарисовать клетки
    draw_numbers()                              # вывести цифры
    draw_text()                                 # показать текстовую информацию


def on_mouse_down(pos):
    """
    on_mouse_down(pos) особая функция, вызывается автоматически при клике мыши
    Через параметр pos в функцию передаётся кортеж из пары координат (x, y)
    """
    pass


def on_key_down(key):
    """
    on_key_down(pos) особая функция, вызывается автоматически при нажатии
    клавиши на клавиатуре. Через параметр key в функцию передаётся
    код нажатой клавиши.
    """
    pass


pgzrun.go()                                     # Запускается главный игровой цикл

Лист. 5. В программе в функции draw_grid для создания сетки на игровом поле используется цикл.

Рис. 3. Окно, созданное программой листинг 5.

Приступим к разработке функции draw_numbers.

Функция draw_numbers выводит в клетках игрового поля числа, соответствующие игровой ситуации. В нашей программе состояние игры в любой момент времени может быть определено порядком чисел от 1 до 15 включительно и положением пустой клетки между ними или перед ними или после них. Следовательно, хранить и менять состояние игры мы сможем используя структуру из 16 элементов.

В языке Python определён тип данных список. Список в Python — это последовательность значений любого типа. Создаётся список в квадратных скобках путём перечисления значений элементов списка через запятую.

В нашей игре, для хранения  чисел от 1 до 15 включительно и пустой клетки в виде строки с пробелом будем использовать список. Причём, любой элемент этого списка в процессе игры может перемещаться. Порядок элементов этого списка определяет положение цифр и пробела на игровом поле. Присвоим переменной с именем playground значение этого списка.

playground = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, ' ']

Список playground, порядок элементов в котором соответствует порядку чисел на игровом поле.

Более гибким будет способ создания списка из числовой последовательности с помощью функции range.

Функция range(start, stop, step) создаёт диапазон из целых. Начало диапазона задаётся параметром start включительно, конец диапазона задаётся параметром stop, причём, само значение stop не включается в диапазон. Третий параметр функции range задаёт шаг в последовательности чисел. Первый и третий параметры функции range не обязательны. В случае, если эти параметры не указаны, start = 0, step = 1.

В нашей программе мы можем создать список playground используя функцию range, а также функцию list которая преобразует диапазон range в список.

playground = list(range(1, 16)) + [' ']

Так как в списке playground кроме элементов представляющих числовую последовательность присутствует символ пробела - элемент строкового типа, мы применили оператор +. В случае, когда аргументами операции + являются списки, операция + объединяет эти списки.

Программа будет более универсальной, если мы в функции range в качестве правой границы диапазона будем использовать вместо числа 16 константу SIZE в квадрате.

playground = list(range(1, SIZE**2)) + [' ']

Теперь, если мы изменим константу SIZE, изменится и размер виртуального игрового поля playground.

Определим цвет чисел на игровом поле.

DIGITS_COLOR = 'sienna'                         # цвет цифр

Константа DIGITS_COLOR будет определять цвет цифр.

Можно приступить к созданию тела функции  draw_numbers которая должна выводить в клетках игрового поля цифры из списка playground.

Метод screen.draw.text() в библиотеке pgzrun предназначен для вывода текста в окно программы. При вызове метода screen.draw.text() нужно указать текст и позицию. В параметр text можно передать только строковый объект str.

кроме того, можно указать следующие дополнительные параметры:

В нашей программе в функции draw_numbers для вывода первого числа из списка playground в первую клетку игрового поля применим следующий код:

x = CELL_SIZE // 2
y = CELL_SIZE // 2
text = str(playground[0])
screen.draw.text(str(playground[0]), center=(x, y), color = DIGITS_COLOR, fontsize = DIGITS_SIZE)

Создадим переменную dig_params значением которой будет словарь, содержащий параметры color и fontsize. Это сделает код более удобным для дальнейшей работы.

dig_params = {'color': DIGITS_COLOR,'fontsize': DIGITS_SIZE}    # цвет и размер цифр
x = CELL_SIZE // 2
y = CELL_SIZE // 2
text = str(playground[0])
screen.draw.text(str(playground[0]), center=(x, y), **dig_params)

При использовании переменной dig_params в качестве параметра метода screen.draw.text() мы используем ** для распаковки словаря.

import warnings
warnings.filterwarnings("ignore", category=RuntimeWarning, message=".*AVX2.*")
import pgzrun
"""
Pygame Zero (pgzrun) - это обёртка над Pygame,
которая упрощает создание игр,
автоматизируя многие рутинные задачи.
"""


# Настройки игры, глобальные константы и переменные
TITLE = "Головоломка 15"                        # заголовок окна
WIDTH = HEIGHT = 500                            # ширина и высота окна
SIZE = 4                                        # размер игрового поля (4x4)
CELL_SIZE = WIDTH // SIZE                       # размер клетки
DIGITS_SIZE = int(CELL_SIZE / 2.2)              # размер цифр
BACKGROUND_COLOR = 'khaki'                      # цвет кнопки
GRID_COLOR = 'dimgray'                          # цвет сетки
DIGITS_COLOR = 'sienna'                         # цвет цифр
playground = list(range(1, SIZE**2)) + [' ']    # виртуальное игровое поле


def draw_grid():
    """
    Рисует игровое поле в клетку
    """
    for n in range(0, WIDTH, CELL_SIZE):                        # линии сетки
        screen.draw.line((n, 0), (n, HEIGHT), GRID_COLOR)       # вертикальные линии
        screen.draw.line((0, n), (WIDTH, n), GRID_COLOR)        # горизонтальные линии


def xy_to_n(x, y):
    """
    Конвертирует пару координат x, y в номер клетки
    """
    col = x // (WIDTH // SIZE)
    row = y // (HEIGHT // SIZE)
    return row * SIZE + col


def draw_numbers():
    """
    Выводит в клетках игрового поля цифры,
    соответствующие игровой ситуации
    """
    dig_params = {'color': DIGITS_COLOR,'fontsize': DIGITS_SIZE}    # цвет и размер цифр
    for x in range(CELL_SIZE // 2, WIDTH, CELL_SIZE):
        for y in range(CELL_SIZE // 2, WIDTH, CELL_SIZE):
            n = xy_to_n(x, y)
            screen.draw.text(str(playground[n]), center=(x, y), **dig_params)


def draw_text():
    """
    Выводит на игровое поле текстовую информацию,
    приглашение к началу игры:
    Клавиша пробел - Старт
    """
    pass


def draw():
    """
    draw() особая функция, вызывается автоматически каждый кадр,
    обычно, 60 FPS. Доступны объекты: screen, actors и др.
    Назначение: отрисовка всего, что должно быть на экране.
    """
    screen.fill(BACKGROUND_COLOR)               # очистка экрана
    draw_grid()                                 # нарисовать клетки
    draw_numbers()                              # вывести цифры
    draw_text()                                 # показать текстовую информацию


def on_mouse_down(pos):
    """
    on_mouse_down(pos) особая функция, вызывается автоматически при клике мыши
    Через параметр pos в функцию передаётся кортеж из пары координат (x, y)
    """
    pass


def on_key_down(key):
    """
    on_key_down(pos) особая функция, вызывается автоматически при нажатии
    клавиши на клавиатуре. Через параметр key в функцию передаётся
    код нажатой клавиши.
    """
    pass


pgzrun.go()                                     # Запускается главный игровой цикл

Лист. 6.

Рис. 4.

import warnings
warnings.filterwarnings("ignore", category=RuntimeWarning, message=".*AVX2.*")
import pgzrun
"""
Pygame Zero (pgzrun) - это обёртка над Pygame,
которая упрощает создание игр,
автоматизируя многие рутинные задачи.
"""


# Настройки игры, глобальные константы и переменные
TITLE = "Головоломка 15"                        # заголовок окна
WIDTH = HEIGHT = 500                            # ширина и высота окна
SIZE = 4                                        # размер игрового поля (4x4)
CELL_SIZE = WIDTH // SIZE                       # размер клетки
DIGITS_SIZE = int(CELL_SIZE / 2.2)              # размер цифр
BACKGROUND_COLOR = 'khaki'                      # цвет кнопки
GRID_COLOR = 'dimgray'                          # цвет сетки
DIGITS_COLOR = 'sienna'                         # цвет цифр
playground = list(range(1, SIZE**2)) + [' ']    # виртуальное игровое поле


def draw_grid():
    """
    Рисует игровое поле в клетку
    """
    for n in range(0, WIDTH, CELL_SIZE):                        # линии сетки
        screen.draw.line((n, 0), (n, HEIGHT), GRID_COLOR)       # вертикальные линии
        screen.draw.line((0, n), (WIDTH, n), GRID_COLOR)        # горизонтальные линии


def xy_to_n(x, y):
    """
    Конвертирует пару координат x, y в номер клетки
    """
    col = x // (WIDTH // SIZE)
    row = y // (HEIGHT // SIZE)
    return row * SIZE + col


def draw_numbers():
    """
    Выводит в клетках игрового поля цифры,
    соответствующие игровой ситуации
    """
    dig_params = {'color': DIGITS_COLOR,'fontsize': DIGITS_SIZE}    # цвет и размер цифр
    for x in range(CELL_SIZE // 2, WIDTH, CELL_SIZE):
        for y in range(CELL_SIZE // 2, WIDTH, CELL_SIZE):
            n = xy_to_n(x, y)
            screen.draw.text(str(playground[n]), center=(x, y), **dig_params)


def draw_text():
    """
    Выводит на игровое поле текстовую информацию,
    приглашение к началу игры:
    Клавиша пробел - Старт
    """
    pass


def play(n):
    """
    Делает ход из клетки с номером n
    в пустую клетку, если это возможно!
    """
    m = playground.index(' ')                   # индекс пустой клетки
    playground[n], playground[m] = playground[m], playground[n]


def draw():
    """
    draw() особая функция, вызывается автоматически каждый кадр,
    обычно, 60 FPS. Доступны объекты: screen, actors и др.
    Назначение: отрисовка всего, что должно быть на экране.
    """
    screen.fill(BACKGROUND_COLOR)               # очистка экрана
    draw_grid()                                 # нарисовать клетки
    draw_numbers()                              # вывести цифры
    draw_text()                                 # показать текстовую информацию


def on_mouse_down(pos):
    """
    on_mouse_down(pos) особая функция, вызывается автоматически при клике мыши
    Через параметр pos в функцию передаётся кортеж из пары координат (x, y)
    """
    play(xy_to_n(*pos))                         # сделать ход


def on_key_down(key):
    """
    on_key_down(pos) особая функция, вызывается автоматически при нажатии
    клавиши на клавиатуре. Через параметр key в функцию передаётся
    код нажатой клавиши.
    """
    pass


pgzrun.go()                                     # Запускается главный игровой цикл

Лист. 7.

Рис. 5.

import warnings
warnings.filterwarnings("ignore", category=RuntimeWarning, message=".*AVX2.*")
import pgzrun
"""
Pygame Zero (pgzrun) - это обёртка над Pygame,
которая упрощает создание игр,
автоматизируя многие рутинные задачи.
"""


# Настройки игры, глобальные константы и переменные
TITLE = "Головоломка 15"                        # заголовок окна
WIDTH = HEIGHT = 500                            # ширина и высота окна
SIZE = 4                                        # размер игрового поля (4x4)
CELL_SIZE = WIDTH // SIZE                       # размер клетки
DIGITS_SIZE = int(CELL_SIZE / 2.2)              # размер цифр
BACKGROUND_COLOR = 'khaki'                      # цвет кнопки
GRID_COLOR = 'dimgray'                          # цвет сетки
DIGITS_COLOR = 'sienna'                         # цвет цифр
playground = list(range(1, SIZE**2)) + [' ']    # виртуальное игровое поле


def draw_grid():
    """
    Рисует игровое поле в клетку
    """
    for n in range(0, WIDTH, CELL_SIZE):                        # линии сетки
        screen.draw.line((n, 0), (n, HEIGHT), GRID_COLOR)       # вертикальные линии
        screen.draw.line((0, n), (WIDTH, n), GRID_COLOR)        # горизонтальные линии


def xy_to_n(x, y):
    """
    Конвертирует пару координат x, y в номер клетки
    """
    col = x // (WIDTH // SIZE)
    row = y // (HEIGHT // SIZE)
    return row * SIZE + col


def draw_numbers():
    """
    Выводит в клетках игрового поля цифры,
    соответствующие игровой ситуации
    """
    dig_params = {'color': DIGITS_COLOR,'fontsize': DIGITS_SIZE}    # цвет и размер цифр
    for x in range(CELL_SIZE // 2, WIDTH, CELL_SIZE):
        for y in range(CELL_SIZE // 2, WIDTH, CELL_SIZE):
            n = xy_to_n(x, y)
            screen.draw.text(str(playground[n]), center=(x, y), **dig_params)


def draw_text():
    """
    Выводит на игровое поле текстовую информацию,
    приглашение к началу игры:
    Клавиша пробел - Старт
    """
    pass


def play(n):
    """
    Делает ход из клетки с номером n
    в пустую клетку, если это возможно!
    """
    m = playground.index(' ')                   # индекс пустой клетки
    # если клетки n и m находятся по соседству в одном ряду или колонке
    if n in [m+1, m-1] and m // SIZE == n // SIZE or n in [m-SIZE, m+SIZE]:
        playground[n], playground[m] = playground[m], playground[n]


def draw():
    """
    draw() особая функция, вызывается автоматически каждый кадр,
    обычно, 60 FPS. Доступны объекты: screen, actors и др.
    Назначение: отрисовка всего, что должно быть на экране.
    """
    screen.fill(BACKGROUND_COLOR)               # очистка экрана
    draw_grid()                                 # нарисовать клетки
    draw_numbers()                              # вывести цифры
    draw_text()                                 # показать текстовую информацию


def on_mouse_down(pos):
    """
    on_mouse_down(pos) особая функция, вызывается автоматически при клике мыши
    Через параметр pos в функцию передаётся кортеж из пары координат (x, y)
    """
    play(xy_to_n(*pos))                         # сделать ход


def on_key_down(key):
    """
    on_key_down(pos) особая функция, вызывается автоматически при нажатии
    клавиши на клавиатуре. Через параметр key в функцию передаётся
    код нажатой клавиши.
    """
    pass


pgzrun.go()                                     # Запускается главный игровой цикл

Лист. 8. В функцию play добавлена проверка выбранного хода на соответствие правилам игры.

import warnings
warnings.filterwarnings("ignore", category=RuntimeWarning, message=".*AVX2.*")
from random import choice                       # выбрать случайный элемент списка
import pgzrun
"""
Pygame Zero (pgzrun) - это обёртка над Pygame,
которая упрощает создание игр,
автоматизируя многие рутинные задачи.
"""


# Настройки игры, глобальные константы и переменные
TITLE = "Головоломка 15"                        # заголовок окна
WIDTH = HEIGHT = 500                            # ширина и высота окна
SIZE = 4                                        # размер игрового поля (4x4)
CELL_SIZE = WIDTH // SIZE                       # размер клетки
DIGITS_SIZE = int(CELL_SIZE / 2.2)              # размер цифр
BACKGROUND_COLOR = 'khaki'                      # цвет кнопки
GRID_COLOR = 'dimgray'                          # цвет сетки
DIGITS_COLOR = 'sienna'                         # цвет цифр
playground = list(range(1, SIZE**2)) + [' ']    # виртуальное игровое поле


def draw_grid():
    """
    Рисует игровое поле в клетку
    """
    for n in range(0, WIDTH, CELL_SIZE):                        # линии сетки
        screen.draw.line((n, 0), (n, HEIGHT), GRID_COLOR)       # вертикальные линии
        screen.draw.line((0, n), (WIDTH, n), GRID_COLOR)        # горизонтальные линии


def xy_to_n(x, y):
    """
    Конвертирует пару координат x, y в номер клетки
    """
    col = x // (WIDTH // SIZE)
    row = y // (HEIGHT // SIZE)
    return row * SIZE + col


def draw_numbers():
    """
    Выводит в клетках игрового поля цифры,
    соответствующие игровой ситуации
    """
    dig_params = {'color': DIGITS_COLOR,'fontsize': DIGITS_SIZE}    # цвет и размер цифр
    for x in range(CELL_SIZE // 2, WIDTH, CELL_SIZE):
        for y in range(CELL_SIZE // 2, WIDTH, CELL_SIZE):
            n = xy_to_n(x, y)
            screen.draw.text(str(playground[n]), center=(x, y), **dig_params)


def draw_text():
    """
    Выводит на игровое поле текстовую информацию,
    приглашение к началу игры:
    Клавиша пробел - Старт
    """
    pass


def play(n):
    """
    Делает ход из клетки с номером n
    в пустую клетку, если это возможно!
    """
    m = playground.index(' ')                   # индекс пустой клетки
    # если клетки n и m находятся по соседству в одном ряду или колонке
    if n in [m+1, m-1] and m // SIZE == n // SIZE or n in [m-SIZE, m+SIZE]:
        playground[n], playground[m] = playground[m], playground[n]


def draw():
    """
    draw() особая функция, вызывается автоматически каждый кадр,
    обычно, 60 FPS. Доступны объекты: screen, actors и др.
    Назначение: отрисовка всего, что должно быть на экране.
    """
    screen.fill(BACKGROUND_COLOR)               # очистка экрана
    draw_grid()                                 # нарисовать клетки
    draw_numbers()                              # вывести цифры
    draw_text()                                 # показать текстовую информацию


def on_mouse_down(pos):
    """
    on_mouse_down(pos) особая функция, вызывается автоматически при клике мыши
    Через параметр pos в функцию передаётся кортеж из пары координат (x, y)
    """
    play(xy_to_n(*pos))                         # сделать ход


def on_key_down(key):
    """
    on_key_down(pos) особая функция, вызывается автоматически при нажатии
    клавиши на клавиатуре. Через параметр key в функцию передаётся
    код нажатой клавиши.
    """
    if key == keys.SPACE :                      # выбор кнопки Старт -> "Пробел"
        counter = 1000                          # количество перемешиваний
        while(counter != 0):
            n = choice(range(SIZE**2))          # выбор случайного хода
            play(n)
            counter -= 1


pgzrun.go()                                     # Запускается главный игровой цикл

Лист. 9.

Рис. 6.

import warnings
warnings.filterwarnings("ignore", category=RuntimeWarning, message=".*AVX2.*")
from random import choice                       # выбрать случайный элемент списка
import pgzrun
"""
Pygame Zero (pgzrun) - это обёртка над Pygame,
которая упрощает создание игр,
автоматизируя многие рутинные задачи.
"""


# Настройки игры, глобальные константы и переменные
TITLE = "Головоломка 15"                        # заголовок окна
WIDTH = HEIGHT = 500                            # ширина и высота окна
SIZE = 4                                        # размер игрового поля (4x4)
CELL_SIZE = WIDTH // SIZE                       # размер клетки
DIGITS_SIZE = int(CELL_SIZE / 2.2)              # размер цифр
BACKGROUND_COLOR = 'khaki'                      # цвет кнопки
GRID_COLOR = 'dimgray'                          # цвет сетки
DIGITS_COLOR = 'sienna'                         # цвет цифр
playground = list(range(1, SIZE**2)) + [' ']    # виртуальное игровое поле


def draw_grid():
    """
    Рисует игровое поле в клетку
    """
    for n in range(0, WIDTH, CELL_SIZE):                        # линии сетки
        screen.draw.line((n, 0), (n, HEIGHT), GRID_COLOR)       # вертикальные линии
        screen.draw.line((0, n), (WIDTH, n), GRID_COLOR)        # горизонтальные линии


def xy_to_n(x, y):
    """
    Конвертирует пару координат x, y в номер клетки
    """
    col = x // (WIDTH // SIZE)
    row = y // (HEIGHT // SIZE)
    return row * SIZE + col


def draw_numbers():
    """
    Выводит в клетках игрового поля цифры,
    соответствующие игровой ситуации
    """
    dig_params = {'color': DIGITS_COLOR,'fontsize': DIGITS_SIZE}    # цвет и размер цифр
    for x in range(CELL_SIZE // 2, WIDTH, CELL_SIZE):
        for y in range(CELL_SIZE // 2, WIDTH, CELL_SIZE):
            n = xy_to_n(x, y)
            screen.draw.text(str(playground[n]), center=(x, y), **dig_params)


def draw_text():
    """
    Выводит на игровое поле текстовую информацию,
    приглашение к началу игры:
    Клавиша пробел - Старт
    """
    pass


def play(n):
    """
    Делает ход из клетки с номером n
    в пустую клетку, если это возможно!
    """
    m = playground.index(' ')                   # индекс пустой клетки
    # если клетки n и m находятся по соседству в одном ряду или колонке
    if n in [m+1, m-1] and m // SIZE == n // SIZE or n in [m-SIZE, m+SIZE]:
        playground[n], playground[m] = playground[m], playground[n]
        return True                             # если сделан ход
    return False                                # ходить было невозможно


def draw():
    """
    draw() особая функция, вызывается автоматически каждый кадр,
    обычно, 60 FPS. Доступны объекты: screen, actors и др.
    Назначение: отрисовка всего, что должно быть на экране.
    """
    screen.fill(BACKGROUND_COLOR)               # очистка экрана
    draw_grid()                                 # нарисовать клетки
    draw_numbers()                              # вывести цифры
    draw_text()                                 # показать текстовую информацию


def on_mouse_down(pos):
    """
    on_mouse_down(pos) особая функция, вызывается автоматически при клике мыши
    Через параметр pos в функцию передаётся кортеж из пары координат (x, y)
    """
    play(xy_to_n(*pos))                         # сделать ход


def on_key_down(key):
    """
    on_key_down(pos) особая функция, вызывается автоматически при нажатии
    клавиши на клавиатуре. Через параметр key в функцию передаётся
    код нажатой клавиши.
    """
    if key == keys.SPACE :                      # выбор кнопки Старт -> "Пробел"
        counter = 1000                          # количество перемешиваний
        while(counter != 0):
            n = choice(range(SIZE**2))          # выбор случайного хода
            if play(n):                         # если был сделан ход
                counter -= 1


pgzrun.go()                                     # Запускается главный игровой цикл

Лист. 10. Теперь функция play возвращает значение True или False, и это значение используется в функции on_key_down при перемешивании фишек.

В начале игры или в процессе игры когда фишки с числами упорядочены, необходимо запретить игроку ходить пока он не нажмёт на клавишу "пробел" и не перемешает фишки случайным образом.

import warnings
warnings.filterwarnings("ignore", category=RuntimeWarning, message=".*AVX2.*")
from random import choice                       # выбрать случайный элемент списка
import pgzrun
"""
Pygame Zero (pgzrun) - это обёртка над Pygame,
которая упрощает создание игр,
автоматизируя многие рутинные задачи.
"""


# Настройки игры, глобальные константы и переменные
TITLE = "Головоломка 15"                        # заголовок окна
WIDTH = HEIGHT = 500                            # ширина и высота окна
SIZE = 4                                        # размер игрового поля (4x4)
CELL_SIZE = WIDTH // SIZE                       # размер клетки
DIGITS_SIZE = int(CELL_SIZE / 2.2)              # размер цифр
BACKGROUND_COLOR = 'khaki'                      # цвет кнопки
GRID_COLOR = 'dimgray'                          # цвет сетки
DIGITS_COLOR = 'sienna'                         # цвет цифр
playground = list(range(1, SIZE**2)) + [' ']    # виртуальное игровое поле


def draw_grid():
    """
    Рисует игровое поле в клетку
    """
    for n in range(0, WIDTH, CELL_SIZE):                        # линии сетки
        screen.draw.line((n, 0), (n, HEIGHT), GRID_COLOR)       # вертикальные линии
        screen.draw.line((0, n), (WIDTH, n), GRID_COLOR)        # горизонтальные линии


def xy_to_n(x, y):
    """
    Конвертирует пару координат x, y в номер клетки
    """
    col = x // (WIDTH // SIZE)
    row = y // (HEIGHT // SIZE)
    return row * SIZE + col


def draw_numbers():
    """
    Выводит в клетках игрового поля цифры,
    соответствующие игровой ситуации
    """
    dig_params = {'color': DIGITS_COLOR,'fontsize': DIGITS_SIZE}    # цвет и размер цифр
    for x in range(CELL_SIZE // 2, WIDTH, CELL_SIZE):
        for y in range(CELL_SIZE // 2, WIDTH, CELL_SIZE):
            n = xy_to_n(x, y)
            screen.draw.text(str(playground[n]), center=(x, y), **dig_params)


def draw_text():
    """
    Выводит на игровое поле текстовую информацию,
    приглашение к началу игры:
    Клавиша пробел - Старт
    """
    pass


def in_order():
    """
    Проверка порядка цифр
    """
    if playground == list(range(1, SIZE**2)) + [' ']:
        return True
    return False


def play(n):
    """
    Делает ход из клетки с номером n
    в пустую клетку, если это возможно!
    """
    m = playground.index(' ')                   # индекс пустой клетки
    # если клетки n и m находятся по соседству в одном ряду или колонке
    if n in [m+1, m-1] and m // SIZE == n // SIZE or n in [m-SIZE, m+SIZE]:
        playground[n], playground[m] = playground[m], playground[n]
        return True                             # если сделан ход
    return False                                # ходить было невозможно


def draw():
    """
    draw() особая функция, вызывается автоматически каждый кадр,
    обычно, 60 FPS. Доступны объекты: screen, actors и др.
    Назначение: отрисовка всего, что должно быть на экране.
    """
    screen.fill(BACKGROUND_COLOR)               # очистка экрана
    draw_grid()                                 # нарисовать клетки
    draw_numbers()                              # вывести цифры
    draw_text()                                 # показать текстовую информацию


def on_mouse_down(pos):
    """
    on_mouse_down(pos) особая функция, вызывается автоматически при клике мыши
    Через параметр pos в функцию передаётся кортеж из пары координат (x, y)
    """
    if in_order():              # невозможно сделать ход пока цифры упорядочены
        return
    play(xy_to_n(*pos))                         # сделать ход


def on_key_down(key):
    """
    on_key_down(pos) особая функция, вызывается автоматически при нажатии
    клавиши на клавиатуре. Через параметр key в функцию передаётся
    код нажатой клавиши.
    """
    if key == keys.SPACE :                      # выбор кнопки Старт -> "Пробел"
        counter = 1000                          # количество перемешиваний
        while(counter != 0):
            n = choice(range(SIZE**2))          # выбор случайного хода
            if play(n):                         # если был сделан ход
                counter -= 1


pgzrun.go()                                     # Запускается главный игровой цикл

Лист. 11. Добавлена функция in_order.

Добавим глобальный счётчик ходов. Пусть счётчик обнуляется когда игрок нажимает на клавишу пробел и тем самым перемешивает фишки с числами.

import warnings
warnings.filterwarnings("ignore", category=RuntimeWarning, message=".*AVX2.*")
from random import choice                       # выбрать случайный элемент списка
import pgzrun, pygame
"""
Pygame Zero (pgzrun) - это обёртка над Pygame,
которая упрощает создание игр,
автоматизируя многие рутинные задачи.
"""


# Настройки игры, глобальные константы и переменные
TITLE = "Головоломка 15"                        # заголовок окна
WIDTH = HEIGHT = 500                            # ширина и высота окна
SIZE = 4                                        # размер игрового поля (4x4)
CELL_SIZE = WIDTH // SIZE                       # размер клетки
DIGITS_SIZE = int(CELL_SIZE / 2.2)              # размер цифр
BACKGROUND_COLOR = 'khaki'                      # цвет кнопки
GRID_COLOR = 'dimgray'                          # цвет сетки
DIGITS_COLOR = 'sienna'                         # цвет цифр
playground = list(range(1, SIZE**2)) + [' ']    # виртуальное игровое поле
counter = 0                                     # счётчик ходов


def draw_grid():
    """
    Рисует игровое поле в клетку
    """
    for n in range(0, WIDTH, CELL_SIZE):                        # линии сетки
        screen.draw.line((n, 0), (n, HEIGHT), GRID_COLOR)       # вертикальные линии
        screen.draw.line((0, n), (WIDTH, n), GRID_COLOR)        # горизонтальные линии


def xy_to_n(x, y):
    """
    Конвертирует пару координат x, y в номер клетки
    """
    col = x // (WIDTH // SIZE)
    row = y // (HEIGHT // SIZE)
    return row * SIZE + col


def draw_numbers():
    """
    Выводит в клетках игрового поля цифры,
    соответствующие игровой ситуации
    """
    dig_params = {'color': DIGITS_COLOR,'fontsize': DIGITS_SIZE}    # цвет и размер цифр
    for x in range(CELL_SIZE // 2, WIDTH, CELL_SIZE):
        for y in range(CELL_SIZE // 2, WIDTH, CELL_SIZE):
            n = xy_to_n(x, y)
            screen.draw.text(str(playground[n]), center=(x, y), **dig_params)


def draw_text():
    """
    Выводит на игровое поле текстовую информацию,
    приглашение к началу игры:
    Клавиша пробел - Старт
    """
    pass


def in_order():
    """
    Проверка порядка цифр
    """
    if playground == list(range(1, SIZE**2)) + [' ']:
        return True
    return False


def play(n):
    """
    Делает ход из клетки с номером n
    в пустую клетку, если это возможно!
    """
    m = playground.index(' ')                   # индекс пустой клетки
    # если клетки n и m находятся по соседству в одном ряду или колонке
    if n in [m+1, m-1] and m // SIZE == n // SIZE or n in [m-SIZE, m+SIZE]:
        playground[n], playground[m] = playground[m], playground[n]
        return True                             # если сделан ход
    return False                                # ходить было невозможно


def draw():
    """
    draw() особая функция, вызывается автоматически каждый кадр,
    обычно, 60 FPS. Доступны объекты: screen, actors и др.
    Назначение: отрисовка всего, что должно быть на экране.
    """
    screen.fill(BACKGROUND_COLOR)               # очистка экрана
    draw_grid()                                 # нарисовать клетки
    draw_numbers()                              # вывести цифры
    draw_text()                                 # показать текстовую информацию
    pygame.display.set_caption(TITLE)           # обновление заголовка


def on_mouse_down(pos):
    """
    on_mouse_down(pos) особая функция, вызывается автоматически при клике мыши
    Через параметр pos в функцию передаётся кортеж из пары координат (x, y)
    """
    global counter, TITLE
    if in_order():              # невозможно сделать ход пока цифры упорядочены
        return
    if play(xy_to_n(*pos)):                     # сделать ход
        counter += 1                            # увеличиваем счётчик ходов
        TITLE = "Ход № " + str(counter)         # обновляем заголовок окна


def on_key_down(key):
    """
    on_key_down(pos) особая функция, вызывается автоматически при нажатии
    клавиши на клавиатуре. Через параметр key в функцию передаётся
    код нажатой клавиши.
    """
    global counter, TITLE
    if key == keys.SPACE :                      # выбор кнопки Старт -> "Пробел"
        counter = 1000                          # количество перемешиваний
        while(counter != 0):
            n = choice(range(SIZE**2))          # выбор случайного хода
            if play(n):                         # если был сделан ход
                counter -= 1
        TITLE = "Головоломка 15"                # обновляем заголовок окна


pgzrun.go()                                     # Запускается главный игровой цикл

Лист. 12. Добавлена библиотека pygame и глобальная переменная counter. Изменились функции draw, on_mouse_down и on_key_down.

Рис. 7. Обратите внимание, в заголовке окна выводится значение счётчика ходов.

В начале игры или в процессе игры когда фишки с числами упорядочены, игроку ходить запрещено, пока он не нажмёт на клавишу "пробел" и не перемешает фишки случайным образом. В это время, на игровом поле должна отображаться подсказка "Клавиша пробел - Старт".

import warnings
warnings.filterwarnings("ignore", category=RuntimeWarning, message=".*AVX2.*")
from random import choice                       # выбрать случайный элемент списка
import pgzrun, pygame
"""
Pygame Zero (pgzrun) - это обёртка над Pygame,
которая упрощает создание игр,
автоматизируя многие рутинные задачи.
"""


# Настройки игры, глобальные константы и переменные
TITLE = "Головоломка 15"                        # заголовок окна
WIDTH = HEIGHT = 500                            # ширина и высота окна
SIZE = 4                                        # размер игрового поля (4x4)
CELL_SIZE = WIDTH // SIZE                       # размер клетки
DIGITS_SIZE = int(CELL_SIZE / 2.2)              # размер цифр
BACKGROUND_COLOR = 'khaki'                      # цвет кнопки
GRID_COLOR = 'dimgray'                          # цвет сетки
DIGITS_COLOR = 'sienna'                         # цвет цифр
FONT_COLOR = 'olive'                            # Цвет шрифта сообщений
playground = list(range(1, SIZE**2)) + [' ']    # виртуальное игровое поле
counter = 0                                     # счётчик ходов


def draw_grid():
    """
    Рисует игровое поле в клетку
    """
    for n in range(0, WIDTH, CELL_SIZE):                        # линии сетки
        screen.draw.line((n, 0), (n, HEIGHT), GRID_COLOR)       # вертикальные линии
        screen.draw.line((0, n), (WIDTH, n), GRID_COLOR)        # горизонтальные линии


def xy_to_n(x, y):
    """
    Конвертирует пару координат x, y в номер клетки
    """
    col = x // (WIDTH // SIZE)
    row = y // (HEIGHT // SIZE)
    return row * SIZE + col


def draw_numbers():
    """
    Выводит в клетках игрового поля цифры,
    соответствующие игровой ситуации
    """
    dig_params = {'color': DIGITS_COLOR,'fontsize': DIGITS_SIZE}    # цвет и размер цифр
    for x in range(CELL_SIZE // 2, WIDTH, CELL_SIZE):
        for y in range(CELL_SIZE // 2, WIDTH, CELL_SIZE):
            n = xy_to_n(x, y)
            screen.draw.text(str(playground[n]), center=(x, y), **dig_params)


def draw_text():
    """
    Выводит на игровое поле текстовую информацию,
    приглашение к началу игры:
    Клавиша пробел - Старт
    """
    fnt_params = {'color': FONT_COLOR,'fontsize': DIGITS_SIZE // 2}
    screen.draw.text("Клавиша", (WIDTH - CELL_SIZE + 20, HEIGHT - CELL_SIZE + 30), **fnt_params)
    screen.draw.text("пробел-", (WIDTH - CELL_SIZE + 20, HEIGHT - CELL_SIZE + 50),  **fnt_params)
    screen.draw.text("Старт", (WIDTH - CELL_SIZE + 20, HEIGHT - CELL_SIZE + 70),  **fnt_params)


def in_order():
    """
    Проверка порядка цифр
    """
    if playground == list(range(1, SIZE**2)) + [' ']:
        return True
    return False


def play(n):
    """
    Делает ход из клетки с номером n
    в пустую клетку, если это возможно!
    """
    m = playground.index(' ')                   # индекс пустой клетки
    # если клетки n и m находятся по соседству в одном ряду или колонке
    if n in [m+1, m-1] and m // SIZE == n // SIZE or n in [m-SIZE, m+SIZE]:
        playground[n], playground[m] = playground[m], playground[n]
        return True                             # если сделан ход
    return False                                # ходить было невозможно


def draw():
    """
    draw() особая функция, вызывается автоматически каждый кадр,
    обычно, 60 FPS. Доступны объекты: screen, actors и др.
    Назначение: отрисовка всего, что должно быть на экране.
    """
    screen.fill(BACKGROUND_COLOR)               # очистка экрана
    draw_grid()                                 # нарисовать клетки
    draw_numbers()                              # вывести цифры
    if in_order():                              # если числа упорядочены
        draw_text()                             # показать текстовую информацию
    pygame.display.set_caption(TITLE)           # обновление заголовка


def on_mouse_down(pos):
    """
    on_mouse_down(pos) особая функция, вызывается автоматически при клике мыши
    Через параметр pos в функцию передаётся кортеж из пары координат (x, y)
    """
    global counter, TITLE
    if in_order():              # невозможно сделать ход пока цифры упорядочены
        return
    if play(xy_to_n(*pos)):                     # сделать ход
        counter += 1                            # увеличиваем счётчик ходов
        TITLE = "Ход № " + str(counter)         # обновляем заголовок окна


def on_key_down(key):
    """
    on_key_down(pos) особая функция, вызывается автоматически при нажатии
    клавиши на клавиатуре. Через параметр key в функцию передаётся
    код нажатой клавиши.
    """
    global counter, TITLE
    if key == keys.SPACE :                      # выбор кнопки Старт -> "Пробел"
        counter = 1000                          # количество перемешиваний
        while(counter != 0):
            n = choice(range(SIZE**2))          # выбор случайного хода
            if play(n):                         # если был сделан ход
                counter -= 1
        TITLE = "Головоломка 15"                # обновляем заголовок окна


pgzrun.go()                                     # Запускается главный игровой цикл

Лист. 13.

В программе листинг 13 появилась глобальная переменная FONT_COLOR - цвет шрифта сообщений.

Функция draw_text теперь выводит в 16-ю клетку игрового поля сообщение "Клавиша пробел - Старт".

Функция  функция draw изменилась таким образом, что функция draw_text теперь запускается если функция in_order возвращает значение True.

Рис. 8. Функция draw_text() выводит в 16-ю клетку игрового поля сообщение "Клавиша пробел - Старт".

Теперь нам не хватает сообщения о победе. Создадим функцию draw_win и будем вызывать её из функции draw в случае, если числа на игровом поле упорядочены, а счётчик ходов не равен 0.

import warnings
warnings.filterwarnings("ignore", category=RuntimeWarning, message=".*AVX2.*")
from random import choice                       # выбрать случайный элемент списка
import pgzrun, pygame
"""
Pygame Zero (pgzrun) - это обёртка над Pygame,
которая упрощает создание игр,
автоматизируя многие рутинные задачи.
"""


# Настройки игры, глобальные константы и переменные
TITLE = "Головоломка 15"                        # заголовок окна
WIDTH = HEIGHT = 500                            # ширина и высота окна
SIZE = 4                                        # размер игрового поля (4x4)
CELL_SIZE = WIDTH // SIZE                       # размер клетки
DIGITS_SIZE = int(CELL_SIZE / 2.2)              # размер цифр
BACKGROUND_COLOR = 'khaki'                      # цвет кнопки
GRID_COLOR = 'dimgray'                          # цвет сетки
DIGITS_COLOR = 'sienna'                         # цвет цифр
FONT_COLOR = 'olive'                            # Цвет шрифта сообщений
playground = list(range(1, SIZE**2)) + [' ']    # виртуальное игровое поле
counter = 0                                     # счётчик ходов


def draw_grid():
    """
    Рисует игровое поле в клетку
    """
    for n in range(0, WIDTH, CELL_SIZE):                        # линии сетки
        screen.draw.line((n, 0), (n, HEIGHT), GRID_COLOR)       # вертикальные линии
        screen.draw.line((0, n), (WIDTH, n), GRID_COLOR)        # горизонтальные линии


def xy_to_n(x, y):
    """
    Конвертирует пару координат x, y в номер клетки
    """
    col = x // (WIDTH // SIZE)
    row = y // (HEIGHT // SIZE)
    return row * SIZE + col


def draw_numbers():
    """
    Выводит в клетках игрового поля цифры,
    соответствующие игровой ситуации
    """
    dig_params = {'color': DIGITS_COLOR,'fontsize': DIGITS_SIZE}    # цвет и размер цифр
    for x in range(CELL_SIZE // 2, WIDTH, CELL_SIZE):
        for y in range(CELL_SIZE // 2, WIDTH, CELL_SIZE):
            n = xy_to_n(x, y)
            screen.draw.text(str(playground[n]), center=(x, y), **dig_params)


def draw_text():
    """
    Выводит на игровое поле текстовую информацию,
    приглашение к началу игры:
    Клавиша пробел - Старт
    """
    fnt_params = {'color': FONT_COLOR,'fontsize': DIGITS_SIZE // 2}
    screen.draw.text("Клавиша", (WIDTH - CELL_SIZE + 20, HEIGHT - CELL_SIZE + 30), **fnt_params)
    screen.draw.text("пробел-", (WIDTH - CELL_SIZE + 20, HEIGHT - CELL_SIZE + 50),  **fnt_params)
    screen.draw.text("Старт", (WIDTH - CELL_SIZE + 20, HEIGHT - CELL_SIZE + 70),  **fnt_params)


def draw_win():
    """
    Выводит на игровое поле сообщение:
    ПОБЕДА!
    """
    x = CELL_SIZE + 1                                               # координата x верхнего левого угла
    y0 = 2 * CELL_SIZE                                              # координата y середины флага
    w = 2 * CELL_SIZE - 1                                           # ширина флага
    h = CELL_SIZE * 0.5                                             # высота секции флага
    screen.draw.filled_rect(Rect(x, y0 - h * 1.5, w, h), 'white')
    screen.draw.filled_rect(Rect(x, y0 - h // 2, w, h), 'blue')
    screen.draw.filled_rect(Rect(x, y0 + h // 2, w, h), 'red')
    dig_params = {'color': DIGITS_COLOR,'fontsize': DIGITS_SIZE}    # цвет и размер цифр
    screen.draw.text("Победа!", center=(y0, y0 - h), **dig_params)


def in_order():
    """
    Проверка порядка цифр
    """
    if playground == list(range(1, SIZE**2)) + [' ']:
        return True
    return False


def play(n):
    """
    Делает ход из клетки с номером n
    в пустую клетку, если это возможно!
    """
    m = playground.index(' ')                   # индекс пустой клетки
    # если клетки n и m находятся по соседству в одном ряду или колонке
    if n in [m+1, m-1] and m // SIZE == n // SIZE or n in [m-SIZE, m+SIZE]:
        playground[n], playground[m] = playground[m], playground[n]
        return True                             # если сделан ход
    return False                                # ходить было невозможно


def draw():
    """
    draw() особая функция, вызывается автоматически каждый кадр,
    обычно, 60 FPS. Доступны объекты: screen, actors и др.
    Назначение: отрисовка всего, что должно быть на экране.
    """
    screen.fill(BACKGROUND_COLOR)               # очистка экрана
    draw_grid()                                 # нарисовать клетки
    draw_numbers()                              # вывести цифры
    if in_order():                              # если числа упорядочены
        draw_text()                             # показать текстовую информацию
    pygame.display.set_caption(TITLE)           # обновление заголовка
    if counter > 0 and in_order():              # если победа
        draw_win()                              # вывести сообщение Победа!


def on_mouse_down(pos):
    """
    on_mouse_down(pos) особая функция, вызывается автоматически при клике мыши
    Через параметр pos в функцию передаётся кортеж из пары координат (x, y)
    """
    global counter, TITLE
    if in_order():              # невозможно сделать ход пока цифры упорядочены
        return
    if play(xy_to_n(*pos)):                     # сделать ход
        counter += 1                            # увеличиваем счётчик ходов
        TITLE = "Ход № " + str(counter)         # обновляем заголовок окна


def on_key_down(key):
    """
    on_key_down(pos) особая функция, вызывается автоматически при нажатии
    клавиши на клавиатуре. Через параметр key в функцию передаётся
    код нажатой клавиши.
    """
    global counter, TITLE
    if key == keys.SPACE :                      # выбор кнопки Старт -> "Пробел"
        counter = 1000                          # количество перемешиваний
        while(counter != 0):
            n = choice(range(SIZE**2))          # выбор случайного хода
            if play(n):                         # если был сделан ход
                counter -= 1
        TITLE = "Головоломка 15"                # обновляем заголовок окна


pgzrun.go()                                     # Запускается главный игровой цикл

Лист. 14. Создана функция draw_win

Рис. 9.

import warnings
warnings.filterwarnings("ignore", category=RuntimeWarning, message=".*AVX2.*")
from random import choice                       # выбрать случайный элемент списка
import pgzrun, pygame
"""
Pygame Zero (pgzrun) - это обёртка над Pygame,
которая упрощает создание игр,
автоматизируя многие рутинные задачи.
"""


# Настройки игры, глобальные константы и переменные
TITLE = "Головоломка 15"                        # заголовок окна
WIDTH = HEIGHT = 500                            # ширина и высота окна
SIZE = 4                                        # размер игрового поля (4x4)
CELL_SIZE = WIDTH // SIZE                       # размер клетки
DIGITS_SIZE = int(CELL_SIZE / 2.2)              # размер цифр
BACKGROUND_COLOR = 'khaki'                      # цвет кнопки
GRID_COLOR = 'dimgray'                          # цвет сетки
BORDER_COLOR = 'gold'                           # цвет бордюра
DIGITS_COLOR = 'sienna'                         # цвет цифр
FONT_COLOR = 'olive'                            # Цвет шрифта сообщений
playground = list(range(1, SIZE**2)) + [' ']    # виртуальное игровое поле
counter = 0                                     # счётчик ходов


def draw_grid():
    """
    Рисует игровое поле в клетку
    """
    for n in range(0, WIDTH+1, CELL_SIZE):                              # линии бордюра
        screen.draw.filled_rect(Rect(n-7, 0, 15, HEIGHT), BORDER_COLOR) # вертикальные
        screen.draw.filled_rect(Rect(0, n-7, WIDTH, 15), BORDER_COLOR)  # горизонтальные
    for n in range(0, WIDTH, CELL_SIZE):                        # линии сетки
        screen.draw.line((n, 0), (n, HEIGHT), GRID_COLOR)       # вертикальные линии
        screen.draw.line((0, n), (WIDTH, n), GRID_COLOR)        # горизонтальные линии


def xy_to_n(x, y):
    """
    Конвертирует пару координат x, y в номер клетки
    """
    col = x // (WIDTH // SIZE)
    row = y // (HEIGHT // SIZE)
    return row * SIZE + col


def draw_numbers():
    """
    Выводит в клетках игрового поля цифры,
    соответствующие игровой ситуации
    """
    dig_params = {'color': DIGITS_COLOR,'fontsize': DIGITS_SIZE}    # цвет и размер цифр
    for x in range(CELL_SIZE // 2, WIDTH, CELL_SIZE):
        for y in range(CELL_SIZE // 2, WIDTH, CELL_SIZE):
            n = xy_to_n(x, y)
            screen.draw.text(str(playground[n]), center=(x, y), **dig_params)


def draw_text():
    """
    Выводит на игровое поле текстовую информацию,
    приглашение к началу игры:
    Клавиша пробел - Старт
    """
    fnt_params = {'color': FONT_COLOR,'fontsize': DIGITS_SIZE // 2}
    screen.draw.text("Клавиша", (WIDTH - CELL_SIZE + 20, HEIGHT - CELL_SIZE + 30), **fnt_params)
    screen.draw.text("пробел-", (WIDTH - CELL_SIZE + 20, HEIGHT - CELL_SIZE + 50),  **fnt_params)
    screen.draw.text("Старт", (WIDTH - CELL_SIZE + 20, HEIGHT - CELL_SIZE + 70),  **fnt_params)


def draw_win():
    """
    Выводит на игровое поле сообщение:
    ПОБЕДА!
    """
    x = CELL_SIZE + 1                                               # координата x верхнего левого угла
    y0 = 2 * CELL_SIZE                                              # координата y середины флага
    w = 2 * CELL_SIZE - 1                                           # ширина флага
    h = CELL_SIZE * 0.5                                             # высота секции флага
    screen.draw.filled_rect(Rect(x, y0 - h * 1.5, w, h), 'white')
    screen.draw.filled_rect(Rect(x, y0 - h // 2, w, h), 'blue')
    screen.draw.filled_rect(Rect(x, y0 + h // 2, w, h), 'red')
    dig_params = {'color': DIGITS_COLOR,'fontsize': DIGITS_SIZE}    # цвет и размер цифр
    screen.draw.text("Победа!", center=(y0, y0 - h), **dig_params)


def in_order():
    """
    Проверка порядка цифр
    """
    if playground == list(range(1, SIZE**2)) + [' ']:
        return True
    return False


def play(n):
    """
    Делает ход из клетки с номером n
    в пустую клетку, если это возможно!
    """
    m = playground.index(' ')                   # индекс пустой клетки
    # если клетки n и m находятся по соседству в одном ряду или колонке
    if n in [m+1, m-1] and m // SIZE == n // SIZE or n in [m-SIZE, m+SIZE]:
        playground[n], playground[m] = playground[m], playground[n]
        return True                             # если сделан ход
    return False                                # ходить было невозможно


def draw():
    """
    draw() особая функция, вызывается автоматически каждый кадр,
    обычно, 60 FPS. Доступны объекты: screen, actors и др.
    Назначение: отрисовка всего, что должно быть на экране.
    """
    screen.fill(BACKGROUND_COLOR)               # очистка экрана
    draw_grid()                                 # нарисовать клетки
    draw_numbers()                              # вывести цифры
    if in_order():                              # если числа упорядочены
        draw_text()                             # показать текстовую информацию
    pygame.display.set_caption(TITLE)           # обновление заголовка
    if counter > 0 and in_order():              # если победа
        draw_win()                              # вывести сообщение Победа!


def on_mouse_down(pos):
    """
    on_mouse_down(pos) особая функция, вызывается автоматически при клике мыши
    Через параметр pos в функцию передаётся кортеж из пары координат (x, y)
    """
    global counter, TITLE
    if in_order():              # невозможно сделать ход пока цифры упорядочены
        return
    if play(xy_to_n(*pos)):                     # сделать ход
        counter += 1                            # увеличиваем счётчик ходов
        TITLE = "Ход № " + str(counter)         # обновляем заголовок окна


def on_key_down(key):
    """
    on_key_down(pos) особая функция, вызывается автоматически при нажатии
    клавиши на клавиатуре. Через параметр key в функцию передаётся
    код нажатой клавиши.
    """
    global counter, TITLE
    if key == keys.SPACE :                      # выбор кнопки Старт -> "Пробел"
        counter = 1000                          # количество перемешиваний
        while(counter != 0):
            n = choice(range(SIZE**2))          # выбор случайного хода
            if play(n):                         # если был сделан ход
                counter -= 1
        TITLE = "Головоломка 15"                # обновляем заголовок окна


pgzrun.go()                                     # Запускается главный игровой цикл

Лист. 15. Изменена функция draw_grid.

Рис. 10.

import warnings
warnings.filterwarnings("ignore", category=RuntimeWarning, message=".*AVX2.*")
from random import choice                       # выбрать случайный элемент списка
import pgzrun, pygame
"""
Pygame Zero (pgzrun) - это обёртка над Pygame,
которая упрощает создание игр,
автоматизируя многие рутинные задачи.
"""


# Настройки игры, глобальные константы и переменные
TITLE = "Головоломка 15"                        # заголовок окна
WIDTH = HEIGHT = 500                            # ширина и высота окна
SIZE = 4                                        # размер игрового поля (4x4)
CELL_SIZE = WIDTH // SIZE                       # размер клетки
DIGITS_SIZE = int(CELL_SIZE / 2.2)              # размер цифр
BACKGROUND_COLOR = 'khaki'                      # цвет кнопки
GRID_COLOR = 'dimgray'                          # цвет сетки
BORDER_COLOR = 'gold'                           # цвет бордюра
DIGITS_COLOR = 'sienna'                         # цвет цифр
FONT_COLOR = 'olive'                            # Цвет шрифта сообщений
playground = list(range(1, SIZE**2)) + [' ']    # виртуальное игровое поле
counter = 0                                     # счётчик ходов


def draw_grid():
    """
    Рисует игровое поле в клетку
    """
    for n in range(0, WIDTH+1, CELL_SIZE):                              # линии бордюра
        screen.draw.filled_rect(Rect(n-7, 0, 15, HEIGHT), BORDER_COLOR) # вертикальные
        screen.draw.filled_rect(Rect(0, n-7, WIDTH, 15), BORDER_COLOR)  # горизонтальные
    for n in range(0, WIDTH, CELL_SIZE):                        # линии сетки
        screen.draw.line((n, 0), (n, HEIGHT), GRID_COLOR)       # вертикальные линии
        screen.draw.line((0, n), (WIDTH, n), GRID_COLOR)        # горизонтальные линии


def xy_to_n(x, y):
    """
    Конвертирует пару координат x, y в номер клетки
    """
    col = x // (WIDTH // SIZE)
    row = y // (HEIGHT // SIZE)
    return row * SIZE + col


def draw_numbers():
    """
    Выводит в клетках игрового поля цифры,
    соответствующие игровой ситуации
    """
    dig_params = {'color': DIGITS_COLOR,'fontsize': DIGITS_SIZE}    # цвет и размер цифр
    for x in range(CELL_SIZE // 2, WIDTH, CELL_SIZE):
        for y in range(CELL_SIZE // 2, WIDTH, CELL_SIZE):
            n = xy_to_n(x, y)
            screen.draw.text(str(playground[n]), center=(x, y), **dig_params)


def draw_text():
    """
    Выводит на игровое поле текстовую информацию,
    приглашение к началу игры:
    Клавиша пробел - Старт
    """
    fnt_params = {'color': FONT_COLOR,'fontsize': DIGITS_SIZE // 2}
    screen.draw.text("Клавиша", (WIDTH - CELL_SIZE + 20, HEIGHT - CELL_SIZE + 30), **fnt_params)
    screen.draw.text("пробел-", (WIDTH - CELL_SIZE + 20, HEIGHT - CELL_SIZE + 50),  **fnt_params)
    screen.draw.text("Старт", (WIDTH - CELL_SIZE + 20, HEIGHT - CELL_SIZE + 70),  **fnt_params)


def draw_win():
    """
    Выводит на игровое поле сообщение:
    ПОБЕДА!
    """
    x = CELL_SIZE + 1                                               # координата x верхнего левого угла
    y0 = 2 * CELL_SIZE                                              # координата y середины флага
    w = 2 * CELL_SIZE - 1                                           # ширина флага
    h = CELL_SIZE * 0.5                                             # высота секции флага
    screen.draw.filled_rect(Rect(x, y0 - h * 1.5, w, h), 'white')
    screen.draw.filled_rect(Rect(x, y0 - h // 2, w, h), 'blue')
    screen.draw.filled_rect(Rect(x, y0 + h // 2, w, h), 'red')
    dig_params = {'color': DIGITS_COLOR,'fontsize': DIGITS_SIZE}    # цвет и размер цифр
    screen.draw.text("Победа!", center=(y0, y0 - h), **dig_params)


def in_order():
    """
    Проверка порядка цифр
    """
    if playground == list(range(1, SIZE**2)) + [' ']:
        return True
    return False


def play(n):
    """
    Сдвигает строку или колонку начиная клетки с номером n
    в пустую клетку, если это возможно!
    """
    m = playground.index(' ')                                           # индекс пустой клетки
    step = 0                                                            # Шаг и направление
    if m // SIZE == n // SIZE:                                          # m и n в одной строке
        step = -1                                                       # пустая клетка справа
        if m < n:                                                       # пустая клетка слева
            step = 1
    if m % SIZE == n % SIZE:                                            # m и n в одной колонке
        step = -SIZE                                                    # пустая клетка снизу
        if m < n:                                                       # пустая клетка сверху
            step = SIZE
    if step != 0:
        for i in range(m, n, step):
            playground[i], playground[i+step] = playground[i+step], playground[i]   # ход
        return True
    return False


def draw():
    """
    draw() особая функция, вызывается автоматически каждый кадр,
    обычно, 60 FPS. Доступны объекты: screen, actors и др.
    Назначение: отрисовка всего, что должно быть на экране.
    """
    screen.fill(BACKGROUND_COLOR)               # очистка экрана
    draw_grid()                                 # нарисовать клетки
    draw_numbers()                              # вывести цифры
    if in_order():                              # если числа упорядочены
        draw_text()                             # показать текстовую информацию
    pygame.display.set_caption(TITLE)           # обновление заголовка
    if counter > 0 and in_order():              # если победа
        draw_win()                              # вывести сообщение Победа!


def on_mouse_down(pos):
    """
    on_mouse_down(pos) особая функция, вызывается автоматически при клике мыши
    Через параметр pos в функцию передаётся кортеж из пары координат (x, y)
    """
    global counter, TITLE
    if in_order():              # невозможно сделать ход пока цифры упорядочены
        return
    if play(xy_to_n(*pos)):                     # сделать ход
        counter += 1                            # увеличиваем счётчик ходов
        TITLE = "Ход № " + str(counter)         # обновляем заголовок окна


def on_key_down(key):
    """
    on_key_down(pos) особая функция, вызывается автоматически при нажатии
    клавиши на клавиатуре. Через параметр key в функцию передаётся
    код нажатой клавиши.
    """
    global counter, TITLE
    if key == keys.SPACE :                      # выбор кнопки Старт -> "Пробел"
        counter = 1000                          # количество перемешиваний
        while(counter != 0):
            n = choice(range(SIZE**2))          # выбор случайного хода
            if play(n):                         # если был сделан ход
                counter -= 1
        TITLE = "Головоломка 15"                # обновляем заголовок окна


pgzrun.go()                                     # Запускается главный игровой цикл

Лист. 16. Изменена функция play.