Лабораторная работа №3 Игра "Lines"

Цель:

  1. Закрепить навыки разработки программ с простым графическим интерфейсом пользователя на зыке Python при помощи библиотеки Tkinter;
  2. Получить представление о разработке простых игровых приложений.

Постановка задачи

  1. Используя стандартный модуль для разработки программ с графическим интерфейсом Tkinter реализуйте игру Lines на зыке Python.
  2. В качестве образца графического интерфейса используйте данную игру.

Подробности указаны далее.

Ход работы

Видимое поведение игры должно соответствовать алгоритму указанному ниже, при этом исходный код программы может работать по другому алгоритму.
Примечание: Переход в состояние - изменяет значение состояние игры на указанное, никаких других действия не происходит; Выполнение действия - выполняет указанное действие, после чего алгоритм продолжает основное действие; Переход к действию - выполняет указанное действие, при этом основное действие прерывается.

  1. Действие Инициализация игры:

    1. Если на экране была надпись "Игра окончена", она убирается.
    2. Игровое поле очищается от шариков;
    3. Все плитки игрового поля становятся вида: плитка не выбрана (стандартный);
    4. Выбранного шарика нет;
    5. Счёт равен нулю;
    6. Выполнение действия Добавление шариков на игровое поле.
    7. Игра переходит в состояние Шарик не выбран;
  2. Действие Добавление шариков на игровое поле:

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

    2. Из буфера подсказки достаются три шарика и добавляются на игровое поле в случайные места;

    3. Если в процессе добавления очередного шарика на игровое поле для него НЕ оказывается свободного места:

      1. Переход к действию Завершение игры.
    4. После добавления всех шариков на игровое поле генерируется 3 новых шарика случайного цвета и помещаются в буфер подсказки.

  3. Действие Проверка линий:
    В результате хода пользователя может быть создано не более одной независимой линии. В результате добавления 3х рандомных шариков - не более 3х. Будем называть линии зависимыми, если при удалении одной из них остальные разрушаются. Данный этап сильно зависит от алгоритма поиска линий.

    1. Выполняется проверка игрового поля на присутствие линии из шариков одного цвета. Линия из шариков - это ровно 5 шариков расположенных подряд:

      • по горизонтали;
      • по вертикали;
      • по любой диагонали.
    2. Если найдены линии:

      1. Шарики входящие в состав линии удаляются с игрового поля:

        1. Если удаляется выбранный шарик:

          1. Плитка под шариком становится стандартного цвета;
          2. Выбранного шарика нет;
          3. Игра переходит в состояние Шарик не выбран;
      2. Количество очков увеличивается на 2 за каждый удалённый шарик;

      3. На данном этапе все независимые линий должны быть удалены.

    3. Если линии НЕ найдены:

      1. Если игра находится в состоянии Шарик перемещён:

        1. Игра переходит в состояние Шарик не выбран;
        2. Выполнение действия Добавление шариков на игровое поле;
        3. Переход к действию Проверка линий.
      2. Если игра НЕ находится в состоянии Шарик перемещён, то выполняется проверка наличия хотя бы одной пустой плитки на игровом поле:

        1. Свободной плитки нет, то переход к действию Завершение игры.
  4. Если пользователь щёлкает ЛКМ в состоянии Шарик НЕ выбран по:

    • Пустой плитке. Состояние игры не изменяется;

    • Если пользователь щёлкает по шарику:

      1. Шарик считается выбранным;
      2. Плитка меняет цвет;
      3. Игра переходит в состояние Шарик выбран.
  5. Если пользователь щёлкает ЛКМ в состоянии Шарик выбран по:

    • Пустой плитке:

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

      2. Если плитка НЕ доступна. Состояние игры не меняется;

      3. Если плитка доступна:

        1. Плитка под шариком становится стандартного цвета;
        2. Шарик перемещается на выбранную плитку;
        3. Выбранного шарика нет;
        4. Игра переходит в состояние Шарик перемещён;
        5. Выполнение действия Проверка линий.
        6. Игра переходит в состояние Шарик не выбран;
    • Если пользователь щёлкает по шарику:

      1. Если шарик тот же. Состояние игры не меняется;

      2. Если шарик другой:

        1. Плитка под текущим шариком становится стандартного цвета;
        2. Новый шарик считается выбранным;
        3. Плитка под новым шариком меняет цвет;
        4. Состояние игры не меняется
  6. Действие Завершение игры:

    1. На экране появляется надпись: "Игра окончена";
    2. Игра переходит в состояние Игра окончена.
  7. При старте приложения выполняется переход к действию Инициализация игры.

  8. Если на игровом поле нажата кнопка "Новая игра", то выполняется переход к действию Инициализация игры.

  9. Если на игровом поле нажата кнопка "Сделать ход", то, если игра НЕ находится в состоянии Игра окончена, то:

    1. Выполнение действия Добавление шариков на игровое поле;
    2. Выполнение действия Проверка линий.

 

0. Изучение образца

Дизайн игры должны соответствовать игре представленной по ссылке. Дизайн игра изменять можно, но только в сторону эстетического улучшения (лучше получилось или хуже решает преподаватель :)). При разработке игры можно воспользоваться данными картинками.
Для упрочения задания анимацию появления новых шариков, перемещения шарика и увеличения количества очков реализовывать не обязательно.

 

I. Подготовка

Для работы с изображениями понадобится библиотека Pillow. Т.к. она не входит в состав стандартных, то её необходимо установить. Если у вас уже установлена библиотека PIL, используйте её или удалите перед установкой Pillow. Чтобы установить/обновить Pillow наберите в командной строке:

 

II. Теория

Для размещения элементов в окне удобно использовать упаковщик grid(видео, текст). Изучите образец интерфейса игры и подумайте как можно уложить представленные элементы в сетку (кнопки, подсказку, счёт тоже).

Код демонстрирующий работу упаковщика grid

Для размещения элемента в произвольное место окна удобно использовать упаковщик place(текст).

Код демонстрирующий работу упаковщика place

Для загрузки изображения в виджет используйте параметр image. В примере ниже картинка ball-green.png должна лежать в папке со скриптом.
Внимание: загруженная картинка обязательно должна быть привязана к какой-нибудь переменной (в примере это img) иначе она может быть удалена сборщиком мусора и перестанет отображаться или вызовет ошибку.

Для реакции на щелчок мыши используем метод bind. В данном примере, по щелчку ЛКМ по лейблу у него должна поменяться картинка на img2.png. В примере привязываем одну и туже функцию ко всем лейблам, а для того, чтобы знать по какому из них щелкнули используем параметр event. Данный параметр содержит поле widget - это и есть лейбл по которому был щелчок. Далее при помощи метода config меняем его картинку.

В предыдущих примерах можно было бы использовать виджет (кнопка) Button вместо (метка) Label, но т.к. Button имеет анимацию нажатия, для игрового поля он не очень подходит. Но для кнопок "Новая игра" и "Сделать ход" можно использовать его.

В случае если изображения даны не в виде отдельных картинок, а в виде нескольких картинок склеенных вместе (tileset) нужно иметь возможность вырезать нужный фрагмент изображения. В данном примере исходный файл cell-bgr.png содержит изображение выбранной и НЕ выбранной плитки. После загрузки нарезаем картинку при помощи метода crop. Метод принимает 4 параметра: x и y координату левого верхнего угла нужной части изображения и, x и y координату правого нижнего угла нужной части изображения. Координаты указываются в пикселях и легко определяются в любом графическом редакторе.

В нашем случае в игре 7 цветов шариков и 2 цвета плитки, итого получается 14 вариантов картинок, но если ещё делать анимацию, то количество вариантов существенно возрастёт. Чтобы решить данную проблему будем накладывать картинку шарика поверх картинки плитки прямо в программе. Это можно сделать двумя способами:

  1. При помощи метода paste. Плюсом данного метода является возможность наложить друг на друга картинки разных размеров. Для того, чтобы учесть прозрачность накладываемой картинки есть возможность задать маску. В этом как раз и заключается основной минус метода paste. Маска имеет только 2 уровня: пиксел прозрачный и пиксел НЕ прозрачный, поэтому наложенная картинка шарика будет выглядеть грубовато, т.к. у неё больше уровней прозрачности:

  2. При помощи метода alpha_composite. Плюсом данного метода является возможность наложить друг на друга картинки с учётом альфа-канала (прозрачность). Т.е. полупрозрачные участки изображения (например тени) получатся лучшего качества. Минус данного метода в том, что изображения должны быть одинакового размера, иначе программа падает с ошибкой.
    Код приведённый ниже позволяет использовать alpha_composite с изображениями разного размера.

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

Рассмотрим типичную ситуацию происходящую во время игры, а именно: пользователь щелкнул по лейблу (одна из плиток игрового поля). Как узнать координаты плитки, есть ли на ней шарик и т.д. Вариантов как обычно очень много. Несколько вариантов:

  1. Завести словарь, в котором в качестве ключа использовать конкретный объект класса Label, а в качестве значения другой словарь/класс со всеми необходимым данными;

  2. Воспользоваться ООП и создать класс-наследник от Label добавив в него все необходимые поля;

  3. Добавить к конкретному объекту класса Label пользовательские атрибуты. С учётом пройдённого на сегодняшний день материала, рекомендуется выбрать этот вариант. Например:

В процессе разработки игры придется решить 4 основные задачи:

Для решения этих задач может быть удобно структурировать элементы по разному, например для поиска пути и определения линий удобно работать с плитками как с двумерным массивом, а для выбора рандомной пустой плитки удобно собрать все пустые плитки в отдельный одномерный список.
Т.к. в Python переменные, в том числе и элементы списков, являются ссылками, то можно легко поместить объект в несколько разных списков, при этом объект не копируется, а является одним и тем же.
Ниже представлен код, в котором демонстрируется работа с одними и теми же виджетами Label через двумерный список bord и одномерный список line. Так же, при щелчке по плитке, формируется временный одномерный список cand состоящий только из тех плиток, которые подходят под условие соседства с выбранной.

 

Что должно быть в отчёте

  1. Отчёт по лабораторной работе оформляется в соответствии с указанными в разделе Правила оценивания требованиями.

  2. В отчёте создайте раздел (заголовок второго уровня) Постановка задачи и продублируйте туда соответствующий блок из этого документа.

  3. Создайте раздел (заголовок второго уровня) Выполнение работы и текстом подробно опишите всё, что делали в процессе выполнения. В описании обязательно должны присутствовать:

    1. Скриншот демонстрирующий интерфейс приложения в процессе игры;
    2. Ссылка на исходный код приложения;
  4. В папке с лабораторной работой должно быть:

    1. Отчёт в файле ReadMe.md;
    2. Каталог с кодом игры на языке Python.