Добавить в корзинуПозвонить
Найти в Дзене
ZDG

Пишем Питона на Питоне #15: Таблица рекордов

Старые игры записывали таблицу рекордов прямо в себя. И когда они копировались с дискеты на дискету, распространяясь между пользователями, вы, получив копию игры, могли увидеть рекорды тех, кто играл в неё до вас.

Предыдущие части: Шрифт, Остановите музыку, Включите звук, Жизни и модальные окна, Подсчёт очков, Главное меню, Геймплей, Есть ли у него душа, Организация ввода, 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, контроллер сделает следующие действия:

  1. Если имя пустое, то заменить его на "NO NAME"
  2. Сохранить имя в свойстве Game.user_name, чтобы оно появилось при повторном вводе
  3. Обновить последнюю запись в таблице рекордов, используя введённое имя и достижения игрока
  4. Отсортировать и сохранить таблицу рекордов.
  5. Переключить игру в режим MODE_HALL_OF_FAME

Для режима MODE_HALL_OF_FAME у нас, само собой, будет своё представление UserTableView:

-2

В коде нет ничего сложного, просто берутся данные из таблицы и выводятся текстом. Основная работа это вычисление правильных координат и выравнивание чисел, что довольно нудно. Чтобы немного разгрузить код (и для других представлений тоже), я добавил в базовый класс View методы для центровки координат относительно прямоугольника: center_axis() и center_box(). Для вычисления ширины и высоты текстовых строк я добавил методы get_str_width(), get_len_width(), get_lines_height() в класс FontParams.

Теперь можно настроить в контроллере MainMenuController обработку пункта главного меню HALL OF FAME так, чтобы при его выборе тоже включался режим игры MODE_HALL_OF_FAME.

Чтобы оживить геймплей, в следующем выпуске нарастим питону глаза и сделаем пульсирующие яблоки.

Читайте дальше: Строим глазки

Читайте также: