Предыдущие части: Шрифт, Остановите музыку, Включите звук, Жизни и модальные окна, Подсчёт очков, Главное меню, Геймплей, Есть ли у него душа, Организация ввода, View, Визуализация поля, Загрузка уровня, INI-файл, Пишем Питона на Питоне!
Код для этой части находится в ветке scoreboard на github. Вы можете смотреть там все файлы онлайн и также скачать зип-архив всей ветки.
Особенность таблицы рекордов заключается в том, что она должна сохраняться между играми. Допустим, она будет храниться в файле на том компьютере, где работает игра.
Но кроме вас на вашем компьютере вряд ли будет играть кто-то ещё. Получается, в таблице рекордов будете только вы сами.
Старые игры записывали таблицу рекордов прямо в себя. И когда они копировались с дискеты на дискету, распространяясь между пользователями, вы, получив копию игры, могли увидеть рекорды тех, кто играл в неё до вас.
Сейчас игры так уже не распространяются. Чтобы это была действительно общая таблица рекордов, игра должна скачивать её с сервера. И отправлять новые рекорды тоже на сервер.
Но так как работа с вебом это тема, которую мы ещё не трогали (плюс надо ещё написать сам сервер), то мы сделаем просто модель для работы с таблицей рекордов. Модель будет уметь читать и писать данные, а откуда и куда – для нас будет абстракцией. Откуда-то читает и куда-то пишет. В нашем случае это будет всё-таки локальный файл, но в дальнейшем можно изменить модель, чтобы она работала с вебом. Сама игра этого никак не заметит.
Для начала сделаем класс-модель UserData для хранения одной записи. Сразу с запасом выделим свойства, которые могут пригодиться:
- name: имя пользователя
- score: набранные очки
- level: до какого уровня дошёл (да, в игре будет много уровней)
- apples: сколько яблок всего съел
- length: питона какой длины вырастил
- flags: специальные отметки
У класса также есть два метода: from_string() умеет вытаскивать данные из строки, а to_string() умеет наоборот слеплять из данных строку. Формат строки простой – просто перечисляем значения всех свойств через запятую. Эти строки будут читаться из файла или записываться в него.
Затем сделаем класс-модель UserTable, который является собственно таблицей рекордов и содержит в себе список из 10 объектов класса UserData (свойство table). Класс конструируется с необязательным параметром – именем файла, и содержит два метода: read() и write(). Они внешне максимально абстрактны, чтобы мы потом могли заменить эти методы на какие-то другие. Но пока что они читают и пишут файл.
Также потребуется метод sort(), чтобы располагать записи по убыванию. Для его реализации воспользуемся стандартной функцией сортировки списка по полю score:
self.table.sort(key = lambda x: x.score, reverse = True)
При любом изменении таблицы она будет сортироваться, поэтому мы можем считать, что она всегда отсортирована и самый низкий результат находится на последнем месте. Чтобы узнать, попадает результат игрока в таблицу или нет, нужно просто сравнить его с последним результатом. Если игрок набрал больше, то его результат записывается вместо последнего результата, и таблица опять сортируется.
Для записи имени игрока его нужно ввести. Это будет происходить на отдельном экране, и мы сделаем для этого свой набор из контроллера, модели и представления, и режим игры MODE_ENTER_NAME.
Представление EnterNameView будет выглядеть так:
Оно будет рисовать подсказку, прямоугольник для ввода имени, текущее имя, курсор и меню из единственного пункта "Продолжить".
Курсор это обычный прямоугольник, но он будет мигающим. Делается это так: представление в методе render() накапливает счётчик тиков (свойство ticks). Так как игра работает с частотой 30 кадров в секунду, а render() вызывается в каждом кадре, то 15 тиков это полсекунды. Поэтому курсор рисуется только пока счетчик меньше 15. А счетчик считает до 30 и после этого сбрасывается.
Имя, которое выводится, является свойством представления (user_name). Оно предварительно копируется из Game.user_name, когда представление создаётся.
Сам же ввод имени будет происходить с помощью обработки нажатий клавиш в контроллере EnterNameController. Он будет проверять, какая клавиша нажата. Если это BackSpace, то он удалит последний символ из имени в представлении. Если это одна из печатных клавиш, и длина имени ещё не максимальна, он добавит букву в конец имени. А представление всё отрисует.
Получив нажатие Enter, контроллер сделает следующие действия:
- Если имя пустое, то заменить его на "NO NAME"
- Сохранить имя в свойстве Game.user_name, чтобы оно появилось при повторном вводе
- Обновить последнюю запись в таблице рекордов, используя введённое имя и достижения игрока
- Отсортировать и сохранить таблицу рекордов.
- Переключить игру в режим MODE_HALL_OF_FAME
Для режима MODE_HALL_OF_FAME у нас, само собой, будет своё представление UserTableView:
В коде нет ничего сложного, просто берутся данные из таблицы и выводятся текстом. Основная работа это вычисление правильных координат и выравнивание чисел, что довольно нудно. Чтобы немного разгрузить код (и для других представлений тоже), я добавил в базовый класс View методы для центровки координат относительно прямоугольника: center_axis() и center_box(). Для вычисления ширины и высоты текстовых строк я добавил методы get_str_width(), get_len_width(), get_lines_height() в класс FontParams.
Теперь можно настроить в контроллере MainMenuController обработку пункта главного меню HALL OF FAME так, чтобы при его выборе тоже включался режим игры MODE_HALL_OF_FAME.
Чтобы оживить геймплей, в следующем выпуске нарастим питону глаза и сделаем пульсирующие яблоки.
Читайте дальше: Строим глазки
Читайте также:
- ООП в Python: особенности реализации