Текст
                    МИССИЯ:

PYTHON
СОЗДАЕМ И ГРЬI
ВМЕСТЕ С ДЕТЬМИ


г;-;) ПРОГРАММ ИРОВАНИ.§ '§1 ДЛЯ ДЕТЕИ
SEAN MCMANUS MISSION PYTHON CODE А SPACE ADVENTURE GAME •
ШОН МАКМАНУС миссия PVTHON СОЗДАЕМ ИГРЫ ВМЕСТЕ С ДЕТЬМИ Москва 2022
УДК ББК 004.43-053.2 32.973.26-018.1 М15 MISSION РУТНОN: CODE А SPACE ADVENTURE GAME! 1ST EDIТION Sеап McMaпus Copyrigl1t © 2018 Ьу Sean McManlls. Title of Englisl1-la11gL1age origiпal: Missioп Python: Code а Space Adveпtшe Game!, ISBN 97~-1-59327-~57-1 , pt1Ыished Ьу No Starcl1 Press lnc. 245 ~th Street, San Francisco, Califorпia Uпited States 94103. Т\1е Rt1ssian Laпgtiage 1st editioп Copyright © 2022 Ьу PL1Ьlisl1iпg HoL1se Eksmo ш1dег liceпse Ьу No Starcl1 Press lпс. All rigllts reserved. МакМанус, Шон. Ml5 Миссия: Pytlюn. Создаем игры вместе с детьми [перевод с английского С. В. Черникова]. 384 с.: ил. - - / Москва Шон МакМанус : Эксмо , ; 2022. - (Программирование для детей). ISBN 978-5-04-159530-2 Добро пожаловать в увлекательное космическое путешествие по просторам бескрайней Вселенной! У каждого космонавта есть своя миссия, а у юного чита­ теля этой книги миссия особенная - создать свою первую игру на языке Pytlюn. В этом ему помогут подробные инструкuии от автора , пошаговые иллюстраuии и пул полезных советов. В конuе каждой главы вас ждут практические упражнения для закрепления материала, а в конuе книги образuовые фрагменты кода и ал­ - горитмы удаления самых распространенных багов. И все это в формате больших космических приключений , где главный герой - это вы! УДК ББК © ISBN 978-5-04-159530-2 004.43-053.2 32.973.26-018.1 Райтман М.. А., перевод на русский юык, 2022 2022 ©Оформление. ООО «Издательство «Эксмо»,
Посвящается моей супруге Карен с благодарностью за ее поддержку на протяжении всей этой миссии, а также нашему замечательному сыну Лео, который захватил нас с собой в это невероятно увлекательное путешествие
оrnАВЛЕНИЕ 06 авторе 12 О техническом рецензенте 14 &nаrодарности 15 Введение 17 Как правильно пользоваться книгой .... Что представляет собой эта книга? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ... Установка программного обеспечения . . . . . ...... Установка программного обеспечения на RаsрЬеггу Pi . . . . . . . . .. . . . . . ..... Установка Python на Windows . . . . . . . . . . . . . . . . . . . . . . ....... Установка Pygame Zего в Windows . . . . . . . . . .. . . . . . .. . . . . .. ...... По.цдержка русского языка ......................... ................................. ...... Установка ПО на другие устройства . . . . . . . . . . . . . ........................... Загрузка игровых файлов . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .......................... Загрузка и распаковка файлов на RаsрЬеггу Pi . . Распаковка файлов на ПК на Windows . Содержимое ZIР-архива .. . ..... Запуск игры . . . . . .. . . . . . . . . . .. . . . . .. .. . . . .. . . . . . .. . . . .. . . . . . . .. . . . . ............................... Запуск программ Pygame Zего на RаsрЬеггу Pi ................................... Запуск программ Pygame Zего на Windows . . . . . . . . . . . . . . . . . . . . . . . . . .. Прохождение игры ...... ...... rnaвa 1. Твой первый выход в открытый космос Запуск редактора Python . Запуск IDLE в Windows 1 О ... . Запуск IDLE в Windows 8 ... . Запуск IDLE в RаsрЬеггу Pi .. . 6 ОFЛАВЛЕНИЕ 18 19 21 22 22 23 25 26 26 27 28 29 29 30 31 32 35 . ....... 37 ··· · ········ ········ ·· ······ 37 . 37 . ..... ........ 38
Знакомство с оболочкой Pythoп .... .... .. ... . 38 39 Отображение текста ........... . Вывод и использование чисел 41 . 42 Знакомство с окном ввода сценариев Создание игрового поля ..................... . 43 47 Подробное объяснение программ .. 50 51 Завершение работы программы Pygame Zего . Создаем планету и космический корабль . Меняем перспективу: полетаем за планетой Выход в открытый космос! . 52 . ...... .. .......... ....... . Разбираемся в коде выхода в открытый космос ... ........ . Готав ли ты к полету? rnaвa 2. ............................ . Списки спасут тебе жи3нь 63 Составим твой первый список : перечень требований к запуску .... 64 Просматриваем наш список .. . 65 66 67 . 67 ."" ... "" ... ". 67 " .. "" ."". 68 Добавляем и удаляем элементы Используем индексы ... Вставляем элемент ... Доступ к отдельному элементу . . . . . "" "" ". "" Замена элемента ................................................. . Удаление элемента ............... . 69 69 Создаем контрольный список для выхода в открытый космос Список списков: руководство к полету ..... 70 " " .. "" ... " 70 72 73 Создаем список списков "" .. " ... "" ... "" .. Поиск элемента в руководстве к полету ............ . Объединение списков " ..... " .... " ..... " .... 74 . 75 . 76 Создание карт из списков: отделение неотложной помощи Составляем карту Поиск предметов первой необходимости . Меняем местами предметы, расположенные в помещении Готав ли ты к полету? .. " ..... "............. rnaвa 3. 54 56 60 78 80 . .. "" ... " ........... . Повторяй 3а мной 83 Отображаем карты с помощью циклов " "" . """."" Создаем карту помещения " " """"". Цикл в цикле " ..... " .... " .... " " ......... "" ... " .... " ... " ..... " ... " ..... "...... 84 84 87 Использование вложенных циклов для получения координат помещения 88 Наводим порядок на карте """ """"" Рисуем трехмерную комнату . . . . . . . . . . . . . . . . . . . . . Создание трехмерного помещения """." ". """" """"" . 90 . ....... ...... 92 "" . "" "" . ". 94 . ............ 96 Готав ли ты к полету? ................................................. . ........... .......................... 99 "" " """ . " Узнаем, где необходимо нарисовать каждый предмет . . . . rnaвa 4. Со3даем космическую станцию 101 Автоматизация процесса создания карт ............... . Как работает автоматический картограф .. 102 102 Создание картографических данных Содержимое списка GAME_MAP """""" " .. 103 105 Тестирование и отладка кода . "" ... "" 110 оrЛАВЛЕНИЕ 7
112 114 116 118 120 121 125 126 Создание помещений на основе данных . " .......... . Как работает код, создающий комнату .... . Создание помещения простой формы Создание выходов ........... . Тестируем программу ........... . Исследование космической станции в 3D Создание собственных карт . Готов ли ты к полету? .............. . .. ........ . .. .... . rnaвa 5. Подrотовка оборудования 129 дnя космической станции Создание простого словаря планет .................. .......... . Разница между списком и словарем ............. . . .... . .... Составление словаря-шпаргалки по астрономии Проверка словаря на ошибки ............. . Размещение списков в словарях Извлечение информации из списка, помещенного в словарь . Словарь объектов космической станции .... .................................... . . Создание первых объектов для игры «Побег» Просмотр объектов с помощью кода в разделе ОБЗОР Проектирование помещения .... ... . Добавляем остальные объекты . Готов ли ты к полету? ...... . rnaвa 6. Обустройство космической станции 155 Изучение словаря данных для декораций Добавление декораций ............................ . Добавляем ограждение по периметру поверхности планеты .......... . ...... . ........... . Загрузка декораций для каждого помещения ...... . Необходимые приготовления для экскурсии по космической станции ... Готов ли ты к полету? ... rnaвa 7. Путешествие на космическую станцию Прибытие на космическую станцию . . . . . . . . . . . . . . . . . . . . . . .................. . Отключение навигации по помещениям в разделе ОБЗОР . Добавление новых переменных Телепортируемся на космическую станцию Добавление кода для перемещения . . . ............. . Разбираемся в коде передвижения ..... ................. . Передвижение между помещениями Готов ли ты к полету? rnaвa 8. ......................... . . Уnучwаем космическую станцию Передаем в функцию данные ..................................... . Создание функции, принимающей данные ... . Как это работает .............. . 130 130 130 133 135 136 138 140 143 144 147 152 156 159 162 166 170 173 175 .. 176 176 177 180 182 185 191 196 197 198 198 199 Добавление переменных для работы с тенями, невидимыми частями стен и цветом ........... . Удаляем раздел ОБЗОР а оrЛАВЛЕНИЕ ...... 201 .. .. ....... 204
Добавляем раздел ЭКРАН .. " ... . " ... 205 . 206 " " ... """. 209 "."".". 211 "."" .... 215 " 218 " 221 ".". 223 . 225 Добавление функций для рисования объектов Рисуем помещение ..................................... . Разбираемся, как работает новая функция dгaw() . Расположение помещения на экране ..... . Делаем так, чтобы передняя стена постепенно появлялась и исчезала Отображение подсказок, советов и предупреждений .. . Отображение названия помещения при входе в него ........ . " .. . Готов ли ты к полету? ......................................................... . rnaвa 9. 227 Распаковка nичных вещей Добавление кода для реквизита ............... . "" .. "."". 227 " ... " " ". 232 "". 236 ····· 239 .. 239 " . 241 . 242 . 243 . 246 .. 248 "." " .... 249 . 252 . 255 Добавляем реквизит на карту помещения . " Поиск объекта на карте помещения .. . Подбираем предметы .. " ...... " ... " ........... ....... " .. . Подбираем реквизит Добавляем управление с клавиатуры Настраиваем инвентарь ... ". "" ""." ""." " .. " ". Отображение инвентаря ... Настраиваем клавишу ТаЬ для управления инвентарем Тестируем инвентарь Выкладывание предметов .... Изучаем объекты "" "" Готов ли ты к полету? . " " . rnaвa 1 о. 257 Действия с предметами Действия с предметами с помощью клавиатуры ". "" .. """"" "" " .. .. " Добавляем стандартные сообщения при использовании объектов ....... . Добавляем переменные, отвечающие за прогресс игры .................. . Задаем действия различным объектам .................. . Комбинирование объектов Задаем финальный эпизод игры Исследуем объекты ..... Готов ли ты к полету? . """. ""." rnaвa 11. &nокировка дверей в помещения " .. ". 258 " .. "" ... 259 " .... 261 " .. """. 262 " ... 265 ... 269 . 270 . 270 271 Выбираем места размещения защитных дверей .. "" .. "".". 272 Размещаем двери ............................................. . "" .. " 273 Настраиваем доступ в помещения . ". " . .. " ".". 274 Открываем и закрываем двери ". "". " ... " "" " ... " " ... "" .. "". .". 277 Анимация дверей . """ " .... " " 280 Закрываем дверь, на которой установлен таймер .. " ... """. 282 . .. " 283 Создание телепорта . """.". ".". " .. ". "."" ... ". Включение защитной двери шлюзового отсека """ .""". 285 Удаляем выходы для создания собственных игровых проектов .... ...... . . 289 Миссия завершена? . " ... "" .. "". " 289 Готов ли ты к полету? .. " ... " .... "" ... " ... 290 оrЛАВЛЕНИЕ 9
rnaвa 12. Внимание! Опасность! &удь осторожен! 291 Вводим систему контроля уровня воздуха ..... Отображение индикаторов уровня воздуха и энергии ...... ... . .. .. .......... Добавляем функции уменьшения уровня воздуха .............................. Уменьшаем уровень воздуха и добавляем сигнал тревоги ................... Добавление подвижных опасностей .. . . . . . .. . . . . . . ... Добавляем в игру опасные объекты ..... Уменьшение энергии (здоровья) игрока . .. . . . . . . . .. .. .... .. . ... . .. . ... Запускаем и останавливаем опасности . . .... Создание карты опасностей . . . .. .. Заставляем опасности двигаться ......... ........... . .. .......... . ...... . ............ Отображаем опасности в помещении . . .. . . . .. .. . . . .. . ..... Запрещаем игроку проходить сквозь опасности . . . .... Создаем лужи ядовитой жижи . . . . . . . . . . .... . ..... Вносим последние штрихи . . . . . ..................... . ................................... Отключаем телепорт ............. ........ . .. .. .......... . .. ......... . .... . .............. Приводим в порядок данные . . . . .... Твое приключение начинается .... ...... Твоя следующая миссия: настройка игры под себя ....... . .................. . .. Готов ли ты к полету? ........ .... Приnожение А. Поnный nистинr иrры «Побеr» Приnожение 292 293 294 297 299 300 302 303 306 307 31 О 3 11 3 12 3 13 3 13 314 314 315 317 319 &. Табnица с переменными, списками и сnоварями 349 Приnожение В. Отnадка кода 353 Расстановка отступов ...................... . .................................... . ....................... 354 Чувствительность к регистру ..... . .... Двоеточия . . . . . . . .... Запятые .................................................................................................... Изображения и звуки ..................................................................................... Орфография . . . .. . . . .. . . . .. .. . . . . .................. . .................. .... Круглые, квадратные и фигурные скобки . Приnожение r. Настройка карты космической станции Создание крутой планировки . . . .. . . . . . .. . . . .. . .... ......... .. .. .. Изменение кода карты . . . . . . . . Работа с поверхностью планеты Удаление дверей . . . . . . . . .. . . . .. ... . .. . . . . . . . . . . . . 355 356 356 357 357 357 359 . ....... . . .. ............ ............................ .. . . . . . .. ... . . . ...... ...... 361 362 362 364 Приnожение Д. Заrрузка карты космической станции 365 Приnожение Е. Добавnение реаnьных звуков Марса в иrру «Побеr» 367 Приnожение :Ж. Добавnение скрытых объектов в качестве пасхаnьных яиц 371 Прячем реквизит за декорацией ...................................................................... 372 1о оrЛАВЛЕНИЕ
Прячем реквизит внутри декорации .. Прячем реквизит внутри случайной декорации .. Добавление новых объектов в игру ...... . Сокрытие объектов в игре .. Код игры «Побег», в которой есть пасхальные яйца Предметный указатеnь .. .. . ........ . ....... ··········· ······ 373 374 374 377 378 379
О& АВТОРЕ Шон Макманус - эксперт в области технологий и бизнеса. Он автор таких книг, как: Cool Scratch Pгojects in Easy 1 2 Steps , Scratch Ргоgга1ш11 ing in Easy Steps , « П рограммист. 4 3 Детская академия » и «Raspberry Pi Fог Dш11mies » (в соав ­ торстве с Майком Куком). В качестве внештатного копирай­ тера Ш он пишет тексты для многих крупнейших мировых технологических компаний. Он также является автором 5 ром ана для взрослых Еагwогш , посвященного музыкальной индустрии и разоблачению ее тайного плана по замене живых музыкантов компьютерной музыкой. Кроме того, одно время Шон Макманус был волонтером в местном « Кр ут ы е про е кты 11а я:и,1к е S c г a t c l1 : л е гк о 11 пр о сг о» . « Про с тое р у ко1юд ст110 по г1 р о гра м ~111ро11а111110 11а н :з ыкс ~ И ~щ ана н а р ус скu ~ 1 н :Jык с: i\1\ акм а нус М.: /·jо м () о ра . « l{a s pl1 e п y 2()1 8. Pi /1, лн ч а ii1111ков ». « На в н:.1•1иван м е лод 11н ». 12 О&АВТОРЕ IJJ. Sc 1·a tc l1». Гlро1·р а м м 11ст . Д е тс ка я ака д е ~ 111н.
компьютерном клубе, где помогал детям изучать искусство программирования. У Шона есть свой веб-сайт, он нахо­ дится по адресу: www.sean.co.uk. Там ты найдешь некоторые главы из его книг и различные бонусы, прилагаемые к ним.
О ТЕХНИЧЕСКОМ РЕЦЕНЗЕНТЕ Дэниел Олдред фанат программирования и опытный - преподаватель компьютерных наук. Он возглавляет вычис­ лительный отдел CAS (протокол управления доступом, обеспечивающий технологию единого входа в веб-серви­ сах) н школе , благодаря которому поддерживаются и разви­ ваются другие учебные и иные организации в регионе. Он периодически выпускает пособия для пользователей и раз­ работчиков системы Linux, а также принимает участие в создании различных ресурсов и проектов для Raspbeгry Pimoгoni, тiсго: bit и Cambгidge lnteгnational Pi, Assessment. В свободное время Дэниел ведет два веб-сайта: www.canyoucoшpute.co.uk , содержащий курс для подго­ товки к британским экзаменам по работе с компьютерами, и www.tecoed.co.uk, где он демонстрирует свои навыки хакинга. Кроме того, под руководством Олдреда команда из восьми студентов выиграла конкурс Astгo Pi, и в качестве приза астронавт майор Тим Пик запустил их программу прямо на борту МКС! 14 О ТЕХНИЧЕСКОМ РЕЦЕНЗЕНТЕ
&ЛАfОДАРНОСТИ Хочу поблагодарить всех сотрудников издательства No Staгch Pгess, усердно трудившихся над созданием этой книги, а особенно ответственного редактора Лиз Чедвик, техниче­ ского редактора Райли Хоффмана, редактора-копирайтера Энн Мари Уокер, корректоров Эмили Бернетт и Мег Сни­ рингер, а также менеджера Серену Янг. Спасибо Та1':'1леру Ортману, который подарил мне идею написать эту книгу, и Биллу Поллоку за его поддержку в этом проекте. Джош ЭJ1лингсон нарисовал потрясающую обложку. Спасибо Аманде Харири, Анне Морроу и Рэ1':"'1чел Барри за их помощь в реализации книги. Рафаэль Пимента создал потрясающую графику для на­ шей игры. Дэниел Олдред отлично вписался в роль техни­ ческого редактора, протестировав весь код, приведенный в этой книге, и сообщив нам обо всех найденных ошибках. Спасибо вам обоим. Эта книга не смогла бы появиться на свет без добросо­ вестной и безвозмездной работы многих программистов. &ЛАrОДАРНОСТИ 15
Дэниел Поуп создал Pygame Zero и помогал нам с различ­ ными исследовательскими вопросами. На сJЙте http://pygame-zero.readthedocs.io/en/latest/ ты можешь узнать о некоторых весы.ы интересных возможностях PygJme Zero, которые не вошли в тот JрсенJл, что мы использовали в на­ шей миссии. Pygame Zero - это расширение для Pygame. Поэтому я отдельно хочу поблагодарить КОJ\tанду разработ­ чиков Руgагпе и все большое сообщество Python, которые вносят свой вклад в его успех. Мы также благодарны NASA за то, что они разрешили наi\1 добавить свои снимки в эту книгу. Ваша работJ неверо­ ятно вдохновляет. Спасибо Расселу Барнсу, Сэму Олдеру, Эбену Аптону и Кэрри Энн Филбин из компании Raspberry Pi Fol!ndation, которые помогли осуществить этот проект. Наконец, спасибо тем, кто прочитал эту книгу! Если она тебе понравилась, пожалуйста, поделись своим отзывом, твитом или сообщением в блоге. Так ты поможешь другим людям узнать о ней. В любом случае, я надеюсь, что тебе она понравится.
ВВЕДЕНИЕ Кислорода почти не осталось. На космической станции произо­ шла утечка этого жизненно важного газа, и теперь действовать надо быстро. Сможешь ли ты найти безопасное место? Тебе надо сориентироваться на космической станции, найти карты доступа для разбло­ кировки дверей и починить поврежденный скафандр. Приключение началось! История начинается здесь, на Земле, в центре управле­ ния, также известном как твой коl\-шьютер. Эта книга на­ учит тебя, как с помощью Python построить коо.-1ическую станцию на Марсе, исследовать ее и выжить в приключенче­ ской графической игре. Итак, космонавт, сумеешь выбрать­ ся из этой передряги? ВВЕДЕНИЕ 17
КАК ПРАВИЛЬНО ПОЛЬЗОВАТЬСЯ книrой Следуя инструкциям в этой книге, ты сможешь создать игру под названием «Побег», в которой у тебя будет карта для исследования и множество головоломок для решения. Игра написана на Python, популярном языке программиро­ вания, код на котором читается легко и просто. В ней также используется расширение Руgагпе Zero, которое, помимо про­ чего, добавляет некоторые специальные инструкции по управ­ лению изображениями и звуками. Шаг за шаго;-.1 я покажу тебе, как можно создать игру самостоятельно и расскажу, как работают основные части кода. Прочитав эту книгу, ты СJ\Ю­ жешь настраивать и создавать собственные игры, основываясь на моем проекте. Нет необходимости набирать вручную весь код игры, его можно просто загрузить с моего сайта. Если ты на чем-то застрял(а) или просто хочешь поскорее перейти к игре и увидеть, как она работает, никаких проблем. Все необ­ ходимое программное обеспечение бесплатно. Я же в свою очередь предоставил инструкции для работы на компьютере с Windows и для Raspberry Pi. Рекомендую тебе воспользо­ Raspberry Pi 3 или Raspberry Pi 2. На Pi Zего, ориги­ ваться нальной модели В+ и на более старых моделях игра может подвисать, поэтому насладиться игрой в полной мере, скорее всего, не получится. Вот несколько различных вариантов использования кни­ ги и игры. • Для начала просто СI{ачай игру и поиграй в нее, а затем, чтобы понять, как она работает, вернись к книге. Иначе ты рискуешь забежать вперед и «Заспойлерить» игру! Конечно, я постарался, чтобы таr{ого было как можно 11лснь­ ше, но ты все равно увидишь несколько подсказок в коде при прочтении книги. Если же ты крепко застрял(а) на какоl\1-то :моменте, попробуй прочитать код. Скорее всего, ты наi'r­ дешь решение своей проблемы. В любом случае я рекомен­ дую тебе сначала разочек запустить игру. Тогда ты поймешь, что ты будешь создавать, и узнаешь, как будут запускаться твои программы. 18 ВВЕДЕНИЕ
• Создай игру, а затем сыграй в нее. Благодаря этой книге ты сможешь создать свою игру от начала и до конца. По мере того как ты будешь продвигаться от главы к главе, ты сможешь добавлять в игру новые разделы, и смотреть, как там все устроено. Если вдруг на каком-то этапе твой код перестанет работать, ты сможешь просто скопировать мой код и продолжить программировать с этого места. Если ты решил воспользоваться этим способом, не вноси собствен­ ные изменения в игру до тех пор, пока не напишешь к ней код , не сыграешь в нее и в целом не завершишь над ней работу. Иначе может случиться так , что тебе просто не удастся закончить игру. (Но это не касается тех измене­ ний, которые я предлагаю вносить в своих заданиях.) • Настрой игру. Когда ты разберешься, как работает про­ гра1'1ма, ты сможешь добавлять в нее собственные карты, изображения, предметы и головоломки. Действие нашей игры « Побег » разворачивается на космической станции, но твой игровой процесс может происходить в джунглях , в морских глубинах, да где угодно. Ты можешь воспользо­ ваться книго~':i, чтобы попробовать самостоятельно создать свою версию « Побега » , или же просто возьми мою версию игры и настрой ее так, как тебе хочется. Кстати, мне было бы интересно посмотреть, что у тебя получится! Меня можно найти в «Твиттере » по имени @шusicandwoгds или же ты J\южешь посетить мой веб-сайт, перейдя по ссылке www.sean.co.uk. что ПРЕДСТАВЛЯЕТ СО&ОЙ ЭТА книrА? Вот краткое описание того, что тебя ждет с самого начала миссии. • Глава 1 расскажет, как выйти в открытый космос. Ты научишься работать с графикой с помощью Pygame Zero, а также познакомишься с некоторыми азами программиро­ вания на Python. • Глава 2 рассматривает списки, в которых хранится большая часть игровых данных « Побега». Ты узнаешь, как применять списки для создания карт. ВВЕДЕНИЕ 19
• Глава 3 продемонстрирует, как r..южно повторять различные программные инструкции и как применять эти знания для отображения карты. Кроме того, ты сможешь разработать планировку космической станции, строя стены и укладывая на пол плитку. • В главе 4 ты приступишь к созданию игры «Побег» и пер­ вым делом нарисуешь чертеж станции. Ты увидишь, что программе под силу распознать схему планировки и на ее основе декорировать помещение, а также создать стены и пол. • В главе 5 ты научишься пользоваться словаря.ми, необходи­ мыми для хранения информации. Ты добавишь в програм­ му данные о каждом существующем в игре объекте и узнаешь, как можно в целом посмотреть на помещение, чтобы оценить придуманный тобой дизайн. • После усовершенствования программы в главе 6 все декора­ ции окажутся на своих местах, и ты наконец-то сможешь полюбоваться всеми помещениями. • После окончания строительства космической станции ты сможешь попасть в нее. В главе 7 ты создашь своего персо­ нажа-космонавта и узнаешь, как передвигаться внутри стан­ ции и анимировать движение героя игры. • В главе 8 ты узнаешь, как улучшить графику, используя тени и делая стены обветшальJI1ли, а также воспользуешься новой функцией, которая исправляет графические огрехи, что всегда остаются после разработки. • Когда космическая станция заработает, ты сможешь доба­ вить в игру собственные эффекты. В главе 9 ты узнаешь, как размещать предметы, чтобы в дальнейшем игрок мог их рас­ сматривать, поднимать и выкладывать. • В главе 10 ты научишься использованию и комбинированию предметов. Это необходимо для решения головоломок в игре. • Работа с космической станцией почти завершена. В главе 11 ты установишь защитные двери, которые ограничивают доступ в те или иные зоны станции. • И как раз в тот моr.1ент, когда ты уже расслабишься и приго­ товишься отпраздновать окончание работы, поблизости 20 ВВЕДЕНИЕ
за углшл обнаружится одна подвижная опасность, о кото­ рой и пойдет речь в главе 12. Читая книгу, ты будешь обучаться, выполняя различные миссии, с помощью которых сможешь проверить, верно ли работают созданные тобой программы, а также оценить уровень своих навыков программирования. Ответы, если понадобятся, будут ждать тебя в конце каждой главы. Кро:ме того, обрати вни1\,1ание на приложения в конце книги. В Приложении А находится код всей игры. Если на каком-либо этапе разработки у тебя появится вопрос, куда следует добавлять тот или иной фрагмент кода, загля­ ни сюда. Приложение Б содержит таблицу наиболее важ­ ных переменных, списков и словарей. Если ты никак немо­ жешь вспомнить, что где находится, обратись к этому разделу. Приложение В содержит некоторые советы по от­ ладке программы, если она почему-то не работает. Дополнительную информацию и вспомогательные ре­ сурсы к книге ты можешь получить, посетив веб-сайт по ад­ ресу: www.sean.co.uk/books/шission-python/. Кроме того, найти ресурсы ты можешь, перейдя по ссылке http://addons. eksшo.ru/it/escape.zip. УСТАНОВКА ПPOf PAMMHOro О&ЕСПЕЧЕНИll Для создания игры используется язык программирования Python, а также программное обеспечение под названием Руgагпе Zero, которое упрощает работу с граф1rко1'.'1 и зву­ ками. Перед тем как приступить к разработке игры, тебе нужно установить обе эти программы. nРимЕчдниЕ Для получения самых свежих инструкt{иЙ по установке данных программ обратись к веб-спzрt~нице по ссылке Ьttps://nostarch.com/missionpytlю11/. ВВЕДЕНИЕ 21
УСТАНОВКА ПPOrPAMMHOro О&ЕСПЕЧЕНИll НА RASPBERRY PI Если ты уже работал с Raspberry Pi, Pytlюn и Руgа1ле Zero, и все эти программы у тебя уже установлены, то можешь сразу переходить к разделу «Загрузка игровых файлов». УСТАНОВКА PYTHON НА WINDOWS Если на твоем компьютере стоит операционная система Windows, выполни следующие действия. 1. Открой веб-браузер и пере1':'~ди по ссылке https://www. python.org/downloads/windows/. Python Releases for Windows Рис. 1. Страница загрузки Python 2. На момент написания этой главы номер последней версии Python - 3.9, но, к сожалению, эта версия конфликтует с установI<оЙ Pyga111e. Я рекомендую вместо версии 3.9 использовать версию Python 3.6 (3.6.8 на момент написания книги). Ты можешь найти предыдущие версии Python, про­ кручивая страницу вниз (см. рис. 1). Сохрани файл на рабо­ чем столе или там, где тебе будет легI<о его найти. (Pyga111e Zero работает только с Python 3, поэтому, если ранее тебе 22 ВВЕДЕНИЕ
чаще приходилось иметь дело с Руtlюп 2, для работы с э той книгой пр и дется переключиться на Pytlюn 3. 3.) Когда файл скачается, дважды щелкни по нему мышью, чтобы запустить программ у установки. 4. В открывшемся окне установи флажок Add Python 3.6 to РАТН (Добавить Python 3.6 к переменной РАТН) (см. рис. 2). 5. Нажми кнопку lnstall Now (Установить сейчас). !;.. Python 3.б.8 х (32·bit) Setup lnstall Python 3.6.8 (32-Ьit) Select lnstall Now to install Python \•1ith default settings. or choose Customize to еnаЫе or disaЫe features. ~ !r1stall No\\' C:\U,ers\r19l1t .AppD• t •\ Loc•Г\Progr om~\Pythcn\P) thon3б· 32 lnclude~ IDLE. р1р ond document•t1on Creotes •honcuts 1nd f1le assoc1at1cns ~ Cцstorнize installation Choose locat1on and fe• lures python windnws (, 121 lnstall Jauncher for all users (recommended) 121 Add fython 3.6 to РАТН Рис. 2. Программа установки Python 6. Если программа спросит, разрешаешь ли ты этому приложе­ нию вносить изменения на твое устройство, нажми кнопку Yes (Да). 7. Установка Python займет несколько минут. После ее окон­ чания нажми кнопку Close (Закрыть), тем самым завершая установку. УСТАНОВКА PYGAME ZERO В WINDOWS Теперь, когда на твоем компьютере « поселился » можешь установить Руgагпе Zero. Python, Для этого следуй указан­ ным ниже де1':'1ствиям. 1. Нажми сочетание клавиш Win+R. 3). Должно открыться окно выполнения команд (см. рис. 2. Введи сшd (см. рис. 3). Нажми клавишу Enter или кнопку ОК. ВВЕДЕНИЕ 23
х Выпо11н\ln 81tД"Т~ "",. nроrр1мм~. n•n•o•. доlС)'",f!!нт• ".1ш ркурс• Инт ернеТi, коrор ••е т ребуете• ono:p"n. Qrкp"r.: 1 c_m_c4 _ _ _ _ _ _ _ _ _ _ _ _~ ~ "1 ок Отм ен • Рис. 3 Диалоговое окно выполнения команд в Windows 3. Перед тобой должно открыться окно командной строки, как показано на рис. 4. Здесь ты можешь ввести инструкции по управлению файлами или запуску программ. Введи install pgzero Рис. 4. pip и нажми клавишу Enteг. 4. Окно командной строки Начнется установка Pygame Zего. Процесс займет несколько минут, после чего снова появится приглашение командной строки, это означает, что все прошло успешно. 5. Если у тебя высвечивается сообщение об ошибке, в котором говорится , что команда вить Python pip не распознана, попробуй устано­ еще раз. Сначала можно удалить Python, а затем снова запустить программу установки. Убедись, что ты устано­ вил(а) флажок Add Python 3.6 to РАТН (Добавить Pytlюn 3.6 Python (см. рис. 2). После переустановки Pytlюn снова попробуй установить Pyga111e Zero. в переменную РАТН) при установке 6. Когда загрузка Руgаше Zего завершится, и ко 1,ландная строка вновь будет доступна для ввода инструкций, введи следующее: echo 7. рrint("Привет!") Эта команда создаст новый файл с имене~м содержит инструкцию 24 > test.py ВВЕДЕНИЕ print ("Привет!"). test.py, который В главе 1
я подробнее расскажу про функцию print (),а пока знай, что это просто быстрый способ создать тестовый файл. Будь осторожнее при вводе круглых скобок и кавычек: если ты пропустишь хотя бы одну из них, файл не будет работать должным образом. 8. Чтобы запустить на выполнение созданный файл test.py, введи следующую команду: pgzrun test.py 9. После небольшой задержки откроется пустое окно с заго­ ловком Руgаше Zero Саше. Щелкни мышью по окну командной строки, тем самым выведя его на передний план: на экране должен появиться текст Привет !. В окне команд­ ной строки нажl\rи сочетание клавиш Ctrl+C. Таким обра­ зом можно прервать выполнение программы. 1О. Если захочешь удалить свою тестовую программу, введи: del test.py ПОДДЕРЖКА PYCCKOro 113ЫКА Обрати внимание на важный момент: чтобы твоя игра под­ держивала не только английский язык, а, например, и рус­ ский, в Pyga111e Zero нужно включить поддержку символов Юникода. Иначе игра попросту не запустится, а в окне ты увидишь сообщения об ошибках. Чтобы решить проблему, найди файл находится в папке, куда установлен runner.py, который Pyga111e Zero. По умол­ чанию путь выглядит примерно так (i\южет отличаться в за­ висимости от операционной системы и пути установки, а также версии Python): c:\users\ твое_ и.мя \appdata\local\programs\pytlюn \pythonЗ б\ lib \site-packt:iges \pgzao \ Вместо твое_имя будет имя твоей учетной записи на компьютере. Перейди в указанную папку и открой файл runner.py в любом или Notepad++. текстовом редакторе, например Блокнот ВВЕДЕНИЕ 25
Найди строку под нш.·1ером 76 (в твое1':'1 версии програм­ мы номер строки может быть другим): with open(path) as f : и измени ее, добавив следующий код: with open(path, encoding="utf-8") as f : Сохрани файл. Теперь твоя игра будет поддерживать и русский, и многие другие языки! УСТАНОВКА ПО НА ДРУfИЕ УСТРОЙСТВА Python и Pygame Zero доступны и для других компьютер­ ных систем. В частности, Руgаше Zero создан еще и с той целью, чтобы игры могли запускаться на разных платфор­ мах, поэтому код игры «Побег» должен работать на всех устройствах, которые поддерживают Pyga111e Zero. Эта книга содержит рекомендации только для пользователей устройств под управлением операционных систем Windows и Raspbeпy Pi. Если у тебя другой компьютер, ты можешь Python, перейдя по ссылке https://www.python.oгg/ downloads/, а также найти совет по установке Pyga111e Zего скачать на сайте http://pygame-zeгo.i-eadthedocs.io/en/latest/ installation.html. 3Af PY3KA Иf РОВЫХ ФАЙЯОВ Я подготовил все программные файлы, звуки и изображе­ ния, необходимые для игры «Побег». Все листинги, что приводятся в этой книге, доступны для скачивания, поэтому, если у тебя что-то н е будет работать, просто вос­ пользуйся моими файлаr..1и. Все, что тебе может понадо­ биться в процессе создания игры, можно скачать в виде еди­ ного ZIР-файла с именем 26 ВВЕДЕНИЕ esce1pe.zip.
3ArPY3KA И РАСПАКОВКА ФАЙЛОВ НА RASPBERRY PI Чтобы загрузить игровые файлы на следующие шаги и обратись к рис. Raspberry Pi, выполни 5. Цифры на рисунке 5 обозначают последовательность действий. О Открой веб-браузер и перейди по ссылке https://nosta1·ch.coш/шissionpython/. Для загрузки фай­ лов щелкни по ссылке Download the full code, iшages, and sound effects for tl1e gаше he1·e. 6 На рабочем столе нажми на кнопку File Manageг (Файло­ вый менеджер), которая находится на панели задач в верхней части экрана. @ Дважды щетши мышью по папке чтобы от­ Downloads, крыть ее. О Дважды щелкни мышью по файлу escape.zip. 0 Нажми кнопку Extract Files (Извлечь файлы), чтобы от­ крыть одноименное диалоговое окно. Ф Укажи следующую папку для извлечения: / home/pi/escape. б Убедись, что установлен флажок Extract files with full path (Извлекать файлы с полными путями). 0 Нажми кнопку Extract (Извлечь). * "" i п:i ~ ~ 11 != CJ; [!j (? G oo09 ~ '1.f 1haм@'p1IOQ\•n'Oi_d_s_- - - - - - - - - - - - - · [ J brn · D ooot · О о.. osap@:rp·Xarct>r,...05~ ·О ~с · CJnom@ L" Р• l.J tl E .Фactto • ~ D~top о] Doeutne!'ts 01nv9ts .rюsyЬfold~> ' ~MYSIC Ol1s~n9s D sounds • ioi1PrC1urts • 7jPUOl•C " es • Allflts Q 11st1n9s ~1ma9ts 1 кaipt ру -rv1-a·· • ··Н Optrons L.J 0._."ff'oVП tf' f'X1~t1n9 .., o •tract f,lesмthfuil f 'М pa:h D PJ'1h0"-9am@s L:J ·!mpla:~s :::: ladюs .n ,...,.п l Freshen ~"sм9 f, lts О Upda t~ a.., ,~,,n,..., f, 1~ .• 'tseape z,p· (17 О'-' 51Z.p ardlav~ •ree spa~ 9 i G S (Total 1~ 5 Gr!!) Рис. 5. Шаги, благодаря которым твои файлы будут распакованы ВВЕДЕНИЕ 27
РАСПАКОВКА ФАЙЛОВ НА ПК НА WINDOWS Чтобы распаковать файлы на компьютере под управлением операционной системы Windows, сделай следующее. Открой веб-браузер и перейди по ссылке http://addons. eksшo.ru / it/escape.zip. Все необходимые ресурсы скачаются на твой компьютер авто1\1атически 13 формате архива. Сохра­ ни ZIР-файл на рабочем столе в папке « Документы }} или в любом другом удобном для тебя каталоге. В зависимости от браузера, которым ты пользуешься, ZIР-файл может открываться а13томатически, или же ты мо­ жешь открыть его с ПОJ\•ющью кнопки 13 на. Удерживая нажатой клавишу (с логотипом Win нижнеИ части экра­ Windows) на клавиатуре, нажми клавишу Е. Должно от­ крыться окно программы Про13одник Windows. Перейди 13 папку, в которой сохранен ZIР-файл. Дважды щелкни мы­ шью по ZIР-файлу. Нажми кнопку Извлечь все (Extract а!!), расположенную в верхней части окна. х Выберите конечную папку и извлеките файлы ~'c_,,_us_er_s\_ri9_h_1ф_o_c_ um_e_п1_s\_Es_c•_Pr_ _ _ _ _ _ _ _ _ _ _~j [ 063~ l2J По••3•n. ю впеч енные Ф • iiл ы Рис. 6. Выбор папки для распаковки игровых файлов Я рекомендую создать папку escape в папке « Документы>} и извлечь туда файлы. Моя папка с документами расположе­ 28 на по адресу C:\Users \ Sean\Documents, ввел конец строки пути. Таким образом, я создал ВВЕДЕНИЕ \escape 13 поэтому я просто
новую директорию в имеющейся папке (см. рис. 6). обходимости ты можешь воспользоваться кнопкой При не­ Browse (Обзор), чтобы перейти к папке « Документы}>. Нажми кнопку Ext1·act (Извлечь). СОДЕРЖИМОЕ ZIP-APXИBA ZIР-файл, которы1"::'1 ты только что загрузил(а), содержит три папки и программу на языке программирования escape.py (см. рис. Pytl1011 - 7). Программа на Pythoп является оконча­ тельной версией игры « Побег }} ' поэтому ты можешь сыграть в нее прямо сейчас. Папка !mt:1ges содержит все изображе­ ния, необходимые нам для игры и других проектов, кото­ рые описаны в этой книге. Папка Sounds содержит звуковые эффекты. В папке listings ты найдешь все пронумерованные листин­ ги, перечисленные в этой книге. Если у тебя не получается запустить программу, то попробуй скопировать мой код из этой папки. Тебе б удет нужно скопировать нужный файл из папки listings и вставить в папку escape, где сейчас нахо­ escape.py. Причина, по которой: тебе необ­ дится программа ходиr-ло выполнить эти действия, з аключается в том, что для правильной работы програмl\IЫ она должна находиться ря­ дом с папками irnages и sounds. ~ ~ im19м Рис. 7. listings sounds мс1ре re1dme Содержимое Z I Р-файла в том виде, в каком оно может отображаться в Windows ЗАПУСКИfРЫ После установки Python на твоем компьютере появится еще одна программа под названием IDLE. IDLE - это инте­ грированная среда разработки, которая представляет собой: специальную программу, с помощью которой можно созда­ вать собственные программы на Python. Ты можешь В ВЕД Е Н И Е 29
запустить некоторые из листингов, приведенных в этой книге, из редактора IDLE Python, следуя приведенным инструкциям. Однако большинство программ работают на Руgаше Zero, и в этом случае тебе придется запускать эти программы из командной строки. Далее приведены инструкции, которые помогут тебе запустить игру « Побег » и любые другие программы Руgаше ЗАПУСК ПРОrРАММ НА Zero. PYGAME ZERO RASPBERRY PI Если ты работаешь на Raspbeгry Pi, выполни следующие действия, чтобы запустить игру « Побег >> . 1. В файловом менеджере перейди в папку положена внутри папки escape, которая рас­ pi. 2. Выбери пункт Tools (Инструменты) в J\tеню и щелкни по строке Open Current Folder in Teгшinal (Открыть теку­ щую папку в тер~минале), или же нажми клавишу F4. Должно открыться окно командной строки (также извест­ ное как оболочка), как показано на рис. 8. Здесь ты сможешь ввести команды для управления файлами или запуска программ. pi@raspЬerryiз1: ~ 1l e Ed1t Tabs Help Рис. 8. Окно командной строки на Raspberry Pi 3. Введи приведенную ниже команду и подтверди ее. Игра начинается! pgzrun escape.py Это способ запуска программы на Pyga111e Zero Raspberry Pi. Чтобы снова запустить ту же программу, повтори последний шаг. Чтобы запустить другую програм­ му, сохраненную в той же папке, повтори последний шаг, но измени имя файла после слова программу Руgаше 30 ВВЕДЕНИЕ Zero pgzrun. Чтобы запустить из другой папки, выполни
действия, начиная с первого шага, но открой командную строку уже из папки с програмr..юй, которую ты хочешь за­ пустить. ЗАПУСК ПРОrРАММ PYGAME ZERO HAWINDOWS Если ты работаешь в Windows, проделай следующее, чтобы запустить программу. 1. Перейди в папку escape. (Нажми сочетание клавиш Win+E, чтобы снова открыть окно программы Проводник Windows.) 2. Щелкни по длинной полосе, расположенной над файлами, как показано на рис. вишу 9. Введи сшd в эту строку и нажми кла­ Enter. о 7' ,.,_"..,., ." ..... -.. о ... ~"."•lkt•- t!J..,,_,....... ~=:.-;.:;:;::. 'Г х . n.. t NКТWl•'-'81110111• Cow•••,...,_, . •n.orтoA~ · . 1,»...,,. гa..-1•fll• . ~ L~'----------------~ ,,,. -.с,.-~· l\c.ii.c··-.- """"'°"""- ~~~~~~~ ;- ~~ tj ---r"" ~ ~ i' __. ...." ·- · ~· Рис. 9. Ввод команды cmd в папке с файлами игры 3. Откроется окно командной строки. Путь к папке escape будет указан в последней строке, непосредственно перед значком> (рис. 1 8 10). C:\WondCМ'l\5ytt•m32\cmcl."t о х Рис. 10. Окно командной строки в Windows 4. Введи кш.~анду pgпun Нажми клавишу Enter, escape.py в окне командной строки. чтобы запустить игру « Побег >>. ВВЕДЕНИЕ 31
Таким способом запускается программа Pygame Zero на компьютере под управлением операционной системы Windo:vvs. Ты можешь запустить программу снова, повторив последний шаг. Чтобы запустить другую программу, сохра­ ненную в той же папке, повтори последний шаг, но измени имя файла в строке после коr,,1анды стить программу Pygame Zero pgzrun. Чтобы запу­ из другой папки, выполни действия, начиная с первого шага, но открой командную строку уже из папки с программой, которую ты хочешь за­ пустить. ПРОХОЖДЕНИЕ Иf РЫ Ты работаешь в одиночку на космической станции на Марсе, за много J~лиллионов километров от дома. Осталь­ ная часть экипажа находится в миссии далеко от тебя. Они исследуют каньон в поисках признаков жизни и вернутся только через несколько дней. Вокруг тебя непрерывно жуж­ жат системы жизнеобеспечения. Сигнал тревоги заставляет тебя вздрогнуть. В стене кос­ мической станции образовалась брешь, и теперь через нее медленно утекает воздух. Ты быстро, но осторожно надева­ ешь скафандр, но тут компьютер сообщает, что твой костюм для выхода в космос поврежден. Твоя жизнь в опасности. Итак, прежде всего тебе нужно починить скафандр и обеспечить надежную подачу воздуха. Вторая задача - вызвать по радио помощь, однако радиосистемы космиче­ ской станции тоже неисправны. Прошлой ночью посадоч­ ныi;'r модуль, посланный с Земли, совершил аварийную посадку на поверхности Марса. Если тебе удастся отыскать его, возможно, ты сможешь воспользоваться его радиопере­ датчиком , чтобы подать сигнал бедствия. Используй клавиши+---, j, l и~ для перемещения по кос­ мической станции. Чтобы осмотреть предмет, встань на него и нажми клавишу П робел. Если же на пред~мет не­ волложно встать, подойди к нему как можно ближе, а затем нажми Пр обел и клавишу со стрелкой, направленной в сто­ рону предмета. 32 ВВЕДЕНИЕ
Чтобы поднять предмет, подойди к нему и нажми клави­ шу G (чтобы забрать его). Чтобы выбрать предмет из инвентаря, который отобра­ жается в верхней части экрана (см. рис. 11), нажимай клави­ шу ТаЬ, чтобы переходить от одного предмета к другому. Чтобы удалить выбранный предмет, нажми клавишу D. Чтобы использовать объект, либо выбери его в инвента­ ре, либо подойди к нему или войди в него, а затеJ\1 нажми клавишу U. Предметы можно комбинировать или использо­ вать их вместе, нажимая клавишу U в тот момент, когда у тебя в руках находится один предмет и ты стоишь на дру­ гом или когда ты несешь один предмет и входишь в друго1~1. Чтобы преодолеть все препятствия и добраться до без­ опасного места, с умом используй свои ресурсы, их не так !'vIНОГО. Удачи! Рис. 11. Приключение началось!

ТВОЙ ПЕРВЫЙ ВЫХОД В ОТКРЫТЫЙ КОСМОС Добро пожаловать на М арс. Твоя миссия заключается в том, чтобы создать п ервый человеческий фор п ост на Марсе. В те ч ение м н огих лет величай­ ш ие ученые мира посылали р обо т ов для изучен и я этой планет ы вблизи. Совсем скоро и ты сможе ш ь ступить на его пыльную п ове р хнос т ь. П утешествие на Марс занимает от шести до восьми меся ­ цев , в зависимости от того, как в данный момент взаимно расположены Марс и Земля. Во вреJ\1я путешествия косми­ ческий корабль рискует столкнуться с метеоритами и дру­ гим космическим мусором. Если корабль будет поврежден, тебе придется надеть скафандр, пройти в шлюз, а затем ш аг- ТВОЙ ПЕРВЫЙ ВЫХОД В ОТКРЫТЫЙ КОСМОС 35
нуть в космическую неизвестность, чтобы устранить полом­ ку, подобно космонавту, изображенному на рис. 1.1. В этой главе ты отправишься в открытый космос, исполь­ зуя язык программирования Python, чтобы зас т авить твоего персонажа перемещаться по экрану компьютера. Чуть позже ты запустишь свою первую программу на Pyt lюn и изучишь некоторые основные команды на которые тебе по­ Python, надобятся д ля построения космической станции. Ты также узн а ешь, как получить эффект объема, накладывая друг на друга изображения. Это знание пригодится тебе, когда мы будем создавать игру « Побег >> в режиме 3D (начиная с первого помещения в третьей главе). '{ r Рис. 1. 1. В 201 О году астронавт NASA Рик Маст раккио вышел в откры т ый космос на 26 минут, а сфотографировал коллегу астронавт по имени Клейтон Андерсон. Выход в открытый космос за пределы Международной космической станции был одним из серии мероприятий, проведенных с целью замены баков с охлаждающей жидкостью Если ты еще нс установил(а) пользователей Windo\vs), Python и Pygamc Zcro (для см. раздел «Установка програJ1.1м­ ного обеспечения » на стр. 22. Тебе также понадобятся фай­ лы для игры « Побег», упомянутые в этой главе. В разделе « Загрузка игровых файлов » на странице 26 загрузить и затем распаковать эти файлы. 36 rnAB A 1 объясняется, как
ЗАПУСК РЕДАКТОРА PYTHON Как я уже упоминал во введении, в этой книге мы будем использовать язык программирования Python. Язык про­ граммирования представляет собой способ общения с ком­ пьютером посредством инструкций. Эти инструкции объ­ ясняют компьютеру, как реагировать на нажатие той или иной клавиши или, например, как показывать изображения. Мы также будем использовать Pygame Zего, что даст Python некоторые дополнительные инструкции по обработке звука и изображений. Python устанавливается совместно с редактором IDLE, поэтому именно в неJ1.1 мы будем создавать наши программы на Python. Поскольку ты уже установил Python, IDLE авто­ матически должен оказаться на твоем компьютере. В сле­ дующих разделах объясняется, как запустить IDLE в зави­ симости от используемых тобой операционной системы и компьютера. ЗАПУСК IDLE В WINDOWS 10 Чтобы запустить IDLE в Windows 10, проделай следующие шаги. 1. Нажми кнопку поиска в виде лупы, которая расположена на панели в левом нижнем углу экрана, и введи в поле Python. 2. 3. Выбери пункт IDLE, После того как IDLE чтобы запустить IDLE. запустится , щелкни правой кнопкой мыши по его значку, расположенному на панели задач в нижней части экрана, и закрепи его. Теперь ты всегда смо­ жешь запускать отсюда IDLE, щет<нув по иконке приложе­ ния один раз кнопкой мыши. ЗАПУСК IDLE В Чтобы запустить 1. WINDOWS 8 IDLE в Windows 8, проделай следующие шаги. Перемести мышь в правую верхнюю часть экрана, чтобы отобразить дополнительную панель. ТВОЙ ПЕРВЫЙ ВЫХОД В ОТКРЫТЫЙ КОСМОС 37
2. Щелкни по значку поиска и введи в поле Руt!юп. 3. 4. Выбери пункт IDLE, После того как IDLE чтобы запустить IDLE. запустится, щелкни правой кнопкой мыши по его значку, расположенному на панели задач в нижней части экрана, и закрепи его. Теперь ты всегда смо­ жешь запускать отсюда IDLE, щелкнув по иконке приложе­ ния один раз кнопкой мыши. ЗАПУСК IDLE В Чтобы запустить RASPBERRY PI IDLE на Raspberry Pi, проделай следую­ щие шаги. 1. Открой список программ с помощью кнопки, расположен­ ной в левом верхнем углу э1<рана. 2. Откро1"::i категорию P1·ogra111111ing (Программирование). 3. Выбери пункт Python 3 (IDLE). На Raspberry Pi установ­ лены как Python 2, так и Python 3, но большинство про­ грамм, описанных в этой книге, будут работать только на Python 3. ЗНАКОМСТВО С О&ОЯОЧКОЙ PYTHON При запуске IDLE перед тобой должна открыться оболочка Python, как показано на р и с. 1.2. В этом окне ты сможешь Python и сразу же видеть реакцию компьютера. Три стрелки (>>>) называются подсказкой. вписывать инструкции Они говорят тебе о том, что Pytlюn готов к работе. lf. Python 3.6.8 Sht11 filt Ed•I DtЬug Sht11 о Op!;on• w;ndow F.-~hon 3.6.! (~aqs/v3.6.S:3cibH6aS~, ~с 23 20l! ;- 23:3l:l7) (Inиl)] Т:,-ре х Htlp [HSC v :l ~:6 32 Ьн on v1n32 •help"', •c;op:,·r1.9hc•, "'cre'11.c.t"' о: •1J.ce:1sie1)• to:: zo: e J.::to:J:CAC.lon~ »~ 1 Ln: 3 Рис. 1.2. Оболочка Со~4 Python Давай-ка попросим Руt!юп выполнить какую-нибудь команду! 38 rЛАВА 1
ОТО&РАЖ:ЕНИЕ ТЕКСТА В качестве нашей первой инструкции предлагаю задать Pytlюn команду отобразить текст на экране. Введи следую­ щий код и нажми клавишу >>> print ( " K старту Enter: готов !" ) П о мере ввода цвет текста станет меняться. Сначала он будет черный, но как только пример print, На рис. 1.3 Python распознает команду, на­ цвет текста ИЗ1\1енится. показаны названия различны х частей только что введенной инструкции. Фиолетовое слово это print - имя встроенной функции, которая является одной из мно­ жества инструкций, доступных в Pytlюn. Функция print () отображает на экране информацию, которую ты помещаешь в круглые скобки. Информация, помещенная в скобки, - это аргумент функции. В нашей первой инструкции аргументом функции print () является строка - так программисты называют фрагменты текста. (Строка может содержать числа, но они рассматриваются просто как символы, поэтому ты не мо­ жешь выполнять вычисления с числами в строке.) Двойные кавычки (" ") обозначают начало и конец строки. Все, что ты вводишь между двойными кавычками, будет выделено зеле­ ным цветом, как и сами кавычки. имя функции print ( "К L аргумент старту готов!" ) круглые скобки___j Ри с. 1.3. Различные части твоей первой инструкции Разные цвета - это не просто украшение: они выделяют различные части инструкции, чтобы помочь тебе найти ошибки. Например, если скобка), которая закрывает часть кода, отображается зеленым цветом, это значит, что ты за­ был(а) поставить двойную кавычку и строка осталась неза­ крытой. ТВОЙ ПЕРВЫЙ ВЫХОД В ОТКРЫТЫЙ КОСМОС 39
Если инструкция введена правильно, твой компьютер отобразит следующий текст: К старту готов ! Строка, которая была выделена зеленым цветоl\1, выво­ дится на экране синим цветом. Весь вывод (информация, которую тебе выдает компьютер) отображается синим цве­ том. Если твоя команда не сработала, убедись, что ты проде­ лал(а) следующие шаги. 1. Слово print напечатано без ошибок. Если это так, оно будет выделено фиолетовым (см. рис. 2. 1.3). В тексте должны быть две круглые скобки. Другие скобки здесь не подходят. 3. Использованы две двойные кавычки. Нельзя вместо двой­ ной кавычки (") вводить подряд две одинарные кавычки ('').Хотя двойная кавычка тоже включает в себя две чер­ точки, у этого символа есть своя кнопка на клавиатуре. В английской раскладке на клавиатуре двойная кавычка находится в среднем буквенном ряду справа и должна нажи­ маться вместе с клавишей Sh ift. На клавиатуре в русской рас­ кладке двойная кавычка находится на клавише с цифрой 2. Если при вводе текста между двойными кавычками будет допущена ошибка, инструкция все равно сработает, но ко:м­ пьютер отобразит именно то, что ты там напечатал(а). По­ пробуй ввести, например, это: >>> print ( "K тарту готов !" ) Ничего страшного, если сейчас при вводе текста в строку ты допустишь ошибку, но когда будешь печатать строки или инструкции при дальне1':'1шем прочтении книги, будь как .r-ложно внимательнее. Ошибки часто мешают программе ра­ ботать корректно, и бывает непросто отследить ошибку в длинном коде програ!\н.1ы, даже если части кода подсвечи­ ваются разными цветами. 40 rЛАВА 1
YЧE&HAll МИССИЯ № 1 Можешь ли ты составить новую инструкцию для вывода своего имени? (Ответы на учебные задания ты найдешь в разделе «Обсуждение миссии» в конце каждой главы.) ВЫВОД И ИСПОЛЬЗОВАНИЕ ЧИСЕЛ До этого момента ты использовал(а) функцию print () для вывода строки, но эта инструкция также умеет делать вычисления и выводить их результат. Введи следующий код: >>> print (4 + 1) Коl\шьютер должен вывести число ния чисел 4 и 5 как результат сложе­ 1. В отличие от вывода строки, в начале и в конце чисел и вычислений кавычки не нужны. Но при этом все равно нужно использовать круглые скобки, чтобы ОТ1\1сппь начало и конец информации, которую ты хочешь передать функции print (). Что произойдет, если ты поставишь кавычки вокруг вы ­ ражения 4 + 1? Попробуй! Ком п ьютер выведет тому что он не будет рассматривать 4 и 4 + 1, п о­ 1 как числа. Вместо этого он обр;с1батывает аргумент как строку. Получается, что ты просишь вывести 4 + 1, и именно это Pythoп и де- лает! >>> print (4 + 1) 5 >>> print ( "4 + 1 " ) 4 + 1 На языке програмJ\111рования Pytl1on вычисление выпол­ няется только тогда, когда ты не ставишь кавычки. Тебе ча­ стенько придется использовать функцию print () в своих программах. ТВОЙ ПЕРВЫЙ ВЫХОД В ОТКРЫТЫЙ КОСМОС 41
ЗНАКОМСТВО С ОКНОМ ВВОДА СЦЕНАРИЕВ Оболочка отлично подходит для быстрых вычислений и коротких инструкциi::С Но для написания более длинных фрагментов кода, без которых не обойтись при создании игр, гораздо проще создавать целые программы. Программы - это большие наборы инструкций, которые можно сохранять, чтобы в дальнейшем запускать их всякий раз, когда они нам понадобятся, и изменять их всякий раз, когда нам это нужно. Перепечатывать их уже не придется. Мы будем создавать программы, используя окно ввода сценариев IDLE. Когда мы вводим инструкцию в окне ввода сценариев, она запускается не сразу, как это происходило в оболочке. Используя меню, расположенное в верхней части окна, выбери пункт Fi le (Файл), а затем New File (Новый файл), чтобы открыть новое пустое окно, как показано на рис. 1.4. Строка заголовка, расположенная в верхней части окна, отображается без названия до тех пор, пока ты не сохра­ нишь файл, присвоив ему имя. Как только ты сохранишь программу, в строке заголовка сразу же отобразится указан­ ное тобой имя файла. Отныне при создании программ на Python мы почти все время будем использовать окно вво­ да сценариев. L.J, Untitled File -i - Edot о Form•I Run Options Window х Help ln: 1 Col: О Р ис. 1. 4 . Окно ввода сценариев Python При вводе кода в окне ввода сценариев ты 1\южешь изме­ нять, добавлять и удалять инструкции с помощью мыши или клавиатуры, поэтому здесь гораздо проще исправлять ошибки и создавать собственные программы. Начиная с четвертой главы, мы создадим игру «Побег», вводя в нее 42 rЛАВА 1
один за другим различные дополнения в окне ввода сцена­ риев и тестируя внесенные п р и прочтении каждо г о раздела изменения. СОВЕТ Если ты не знаешь, в каком режиме сейчас работает IDLE в режиме ввода команд или ввода сценариев, посмотри на строку заголовка, расположенную вверху. Если сейчас ак­ тивен режим оболочки, там будет написано Python Shell. В случае режима ввода сценариев название либо совсем от­ сутствует (Untitled), либо в строке заголовка отображается имя твоей программы. СОЗДАНИЕ иrРовоrо ПОЛJI П ервая програмl\~а, которую мы с тобой напишем, будет выводить на экран изображение звездного неба, его мы будем использовать в качестве фона п р и создании нашей программы про выход в открытый космос. Это изображе­ ние находится в каталоге iтages папки окно пРимЕчдниЕ IDLE введи код из листинга escape. В новое пустое 1-1. В этой книге я буду использовать числа в круJ1сочках (например, такие: О), ч. тобы ссылаться на различные фрагменпzы кода во время объяснений., чrпобы тебе было легче пони.мать, что .мы делаем. Не вводи эти числа в свою програ.м.му. Если в тексте ты видииtь число в кружке, обра­ тись к 11олно.му коду програ.м"мы, чтобы понять, о какой части кода я говорю. Листинг 1-1 - это короткая программа. Однако здесь есть несколько деталей, на которые стоит обратить внима­ ние при наборе кода: после ввода оператора def О в конце строки должно стоять двоеточие, а следующая строка 0 должна начинаться с четырех п робелов. После того как ты поставишь двоеточие в конец строки, начинающейся с и нажмешь клавишу Enter, IDLE def, автоматически поставит четыре пробела в начале следующей с т роки. ТВОЙ ПЕРВЫЙ ВЫХОД В ОТКРЫТЫЙ КОСМОС 43
О # Космическая прогулка # Шана Макмануса # www.sean.co.uk / www .n ostarch . com б WIDTH = 800 HEIGHT = 600 €) player_ x 600 player_ y = 350 О def draw (): screen.Ыit 0 (images.backdrop, (0, 0)) Листинг 1-1. Просмотр звездного неба в Pygame Zего Открой меню File (Файл), расположенное в верхней ча­ сти экрана, а затем выбери команду Save (Сохранить) (от­ ныне мы будем использовать сокращенное название для вы­ бор а команды меню, которое выглядит следующим образом: File ~ Save (Файл ~ Сохранить) ). В открьшше1\1ся диалого­ вом окне Save (Сохранить) у кажи дл я своей программы на­ звание listirzgl-1.py. Тебе необходи мо сохранить фай л в пап­ ке escape, которую ты создал(а) еще во введении. Таким образом, он будет находиться в той же папке, что и папка images, у помянутая ранее, и Pyga rпe Ze1·0 сможет найти изо ­ бражения при запуске программы. После сохранения файла в папке escape в ней должны быть файл fz:stingJ-1.py и папка irnages, как показано на рис. 1.5 (вместе с п а пками listings и sounds). 1 J3 • C:\Ut:trs\nght\OмDr111t\Ooc1.1m•nЬ.\.s.c1p• С:1 Пa,.\tA\tt..U D ~ :::::UT• "·~· ,t о &..,, 3•~=~~~Tr•o".:o';;t~t;'* ~ on.roo1.1t• 8с1 11tщ1 В kтlll•1" J1pnt1111:: См •т11 lllU.tl1f"~1t' Пtptt111111oa.11r• ~ Q6p8tмtt l&f.1t,'lt1'1Mt 5',0roo611t~ ~r.11n. Пс1Ч1С" t~Clpt Pa~•.•rp im1g6 ЭтoY"OtJn.taYtp > lilB•..ato ) ~ > .f. 3J111Y1"• 1' 2109-201117,)Q П1nr1<фa.t"11.111 П.n••с ф•и.., ..... " list1ng1 ZЗ.09.202117,JO юunch 2309l0211S,JS П.1n•1<ф.111;,,а1111 22.09.I0.!111'3S P1th.cnF1le (fo lostong1·1 1КЕ 4o")'ut""" r.1y1"•• Рис. 1.5. Твоя новая программа на Pythoп и папка images должны храниться в одном и том же месте 44 rЛАВА 1 х
Чуть позже я объясню, как работает программа Hstingl-1.py, но сначала давай запустим ее, чтобы мы мо гли полюбовать­ ся звездным небом. Чтобы работать с изображениями, на­ шей п рограмме нужные некоторые дополнительные воз­ можности, которые содержатся в Pygame Zero. Для их использования наJ1.1 необходимо запустить программу с по­ мощью команды pgzrun. Вообще говоря, всякий раз, когда мы используем какие-либо инструкции из Руgаше Zего в Pytl1on, нам необходимо запускать их с помощью команды pgzrun. Набираем это слово в командной строке компьютера точ­ но так же, как мы это делали во введении для запуска игры «Побег » . Вернись к разделу «Запуск игры» во введении и выполни все инструкции, которые требуются для запуска командной строки из папки escape. Затем выполни следую­ щую команду из командной строки: pgzrun listingl-1 .py llJИ·Ji·jj Не вводи эту инструкцию в из командной сrпроки IDLE: ее следует запускапzь Windows или Raspberry Pi. Во введении показано, как это л.10J1сно сделать. Если все идет по плану, то ты сможешь полюбоваться красотой космоса, как показано на рис. 1.6. ТВОЙ ПЕРВЫЙ ВЫХОД В ОТКРЫТЫЙ КОСМОС 45
х=О х= 799 у=О у=59 9 Рис. 1.6. Звездное небо. Изображение звездного неба любезно предоставлено NASA/JPL-Caltech/UCLA и демонстрирует звездное скопление NGC 2259 ИСПОЛЬЗОВАНИЕ МОИХ ПРИМЕРОВ С КОДОМ Если программа, которую ты пишешь, опираясь на эту книгу, не работает, просто воспользуйся моим файлом программы. Например, ты можешь использовать код из моего файла listing 1-1.ру ственный и изменить его так, чтобы получился твой соб­ listingl-2.py. Это займет не так много времени, бла- годаря чему ты сможешь двигаться дальше. Мои программы находятся в каталоге listings, ходится в папке escape. Просто открой папку который на­ listings в файло­ вом менеджере операционной системы Windows или устройства RаsрЬеггу Pi, найди нужный файл с кодом, скопи­ руй его и вставь в папку escape. Затем открой в IDLE скопиро­ ванный листинг и выполни шаги, описываемые в книге. Не забывай, что файл листинга и каталог images должны ходиться в одном и том же месте (см. рис. 1.5). 46 rЛАВА 1 на­
ПОДРОliНОЕ OliЪRCHEHИE ПРОrРАММ Большинство инструкций, которые описаны в этой книге, будут работать в любой программе на функция print () Python. Н апри.мер, доступна везде. Кроме того, для создания програN1м в этой книге 1\1ы используем Руgаше Zero. Эта программа предоставляет некоторые дополнительные воз ­ можности для создания игр. Большинство функций Руgаше Zcro касаются отображения графики и проигрывания звука. В листинге 1-1 представлены наши первые инструкции из Руgаше Zero, используемые для настройки окна игры и отрисовки звездного неба. Давай подробнее рассмотрим, как работает программа listingl-1.py. Первые несколько строк программы - это коммента­ рии О. Когда ты набираешь символ #, Python игнорирует все, что записано после него в строке, а сама она отобража ­ ется красным цветом. Комl\·1ентарии помогают тебе и дру­ гим пользователям, читающиr-.1 твой код , понять, в чем за­ ключается смысл программы и как она работает. Далее нашей программе потребуется сохранять различ­ ные данные. Как правило, программы должны сохранять какую-либо информацию, в том числе ту, к которой необхо­ димо обратиться позже. Например, во многих играх ком ­ пьютер самостоятельно отслеживает счет и положение иг­ рока на экране. Поскольку эти сведения могут меняться (то есть с ними происходят какие-то перемены) по мере ра­ боты программы, они хранятся в TOJ\1, что называется пере­ менной. Переменная - это имя, которое ты задаешь фраг­ менту информации, будь то число или текст. Чтобы создать переменную, воспользуйся инструкцией следующего вида: имя_переменной nР и мЕч д ниЕ = значение Строки кода, выделенные курсивом, - это ~uаблоны, которые меняются на нужные. Так, вмесrпо слов имя_пере­ менной будет указано имя твоей 11еременной. ТВОЙ ПЕРВЫЙ ВЫХОД В ОТКРЫТЫЙ КОСМОС 47
Наприл1ер, следующая инструкция присваивает перемен­ ной score score значение, равное 500: 500 Переменным можно давать любые имена. Однако чтобы код было легче писать и впоследствии читать, лучше выби­ рать имена для переменных таким образом, чтобы они опи­ сывали информацию, которую содержит переменная. Обра­ ти внимание, что в качестве имени переменной ты не можешь использовать ключевые слова в Pytl1on, например print. А также нелатинские сим13олы, например переменная. ij>Jфlf.jj Руtlюп чувспzвителен к регисrпру. Это означает, чrпо ему важно, прописные или спzрочные буквы использовались при вводе имен переменных. То есть для и Score Python score, SCORE, эпzо ;при соверzи енно разные пере.менные. Убедись, что ты верно скопировал(а) код моих примеров программ, иначе они .могут работать неверно. Листинг ных. 1-1 начин ается с создания нескольких перемен­ Pygame Zero использует переменные WIDTH и HEIGHT б для настройки размера окна игры на экране. В ширину окно больше, чем в высоту, поскольку значение WIDTH (800) больше, чем значение HEIGHT (600). Обрати внимание, что мы записали эти переменные про­ писными буквами . Прописные буквы в именах переменных говорят нам о том, что такие переменные являются констан­ тами. Констаmпа - это особый вид переменной, один раз присвоив ей значение, ты больше не сможешь его из1\1енить. Прописны е буквы помогают друпн.·1 программистам, чи­ тающим твой код, понять, что значения этих переменных не меняются. Переменные player _ Т13ое положение на экране х и 130 player _ у ~ хранят время выхода 13 13 себе открытый кос­ мос. Позже в этой главе мы воспользуемся этими перемен­ ными, чтобы разместить нашего героя на экране. Затем с помощью оператора Функция - def О мы создаем функцию. это несколько объединенных в группу ин­ струкций, которые ты сможешь при необходимости 48 rЛАВА 1
за п ускать (да, все разом). С одной функцией мы уже даже вст р ечались - это print ().В на ш у собственную функцию этой программе мы создадим: - draw (). Руgаше Zе го будет использовать ее для вывода картинки на экран при каждом ее изменении . Мы определяем функцию с помощью ключевого слова def О , за которым следует выбранное нами имя функции, пустые скобки и двоеточие. Иногда тебе будет необходимо использовать круглые скобки функции для того, чтобы ука­ зать некую информацию для этой функции. Как это работа­ ет, я покажу чуть дальше. Теперь нам нужно научить функцию делать то, для чего мы ее, собственно, и создали. Чтобы Pytl1on понимал, ч т о нужный нам набор инструкций относится именно к функ­ ции, все инструкции, которые идут после строки с жны иметь отсту п в четыре пробела. Инструкция Ыit def, дол ­ screen. () 0 в Руgап1 е Zero выводит на экран изображение. В скобках мы указываем, какое изображение вывести и в каком именно месте, вот так: sсrееп.Ыit(imаgеs.имя_графического_файла, (х, у)) Воспользуемся файлом backdтop.jpg, находя щ емся в папке images. Это звездное небо. В нашей программе listingl-1.py мы будем ссылаться на картинку с помощью функции images. backdrop. Нам не нужно использовать расши р е­ ние файла.jрg, поскольку для обработки изображений мы работаем с Руgаше Zero, а Руgаше Zero не требует указывать расширение файла. К роме того, программа сама знает, где находится изображение. Именно поэтому все изображения должны храниться в папке images, чтобы програмУiа Руgаше Zего могла легко их найти. Помещаем изображение на экран в координаты (О, О) @, что означает верхний левы1':'1 угол экрана. Первое число, из­ вестное как позиция п о оси х, сообщает инструкции screen.Ыit (),как далеко от левого края экрана мы хотим расположить наше изображение. Второе число, известное как позиция по оси у, показывает, насколько низко мы хо­ тим опустить картинку. Значение позиции по оси х r-.южет ТВОЙ ПЕРВЫЙ ВЫХОД В ОТКРЫТЫЙ КОСМОС 49
быть любым в диапазоне от О, означающего левый край окна, до 799, означающий правый край, поскольку окно имеет ширину 800 пикселей. Аналогично с позицией по оси у, где значение О означает верхнюю часть окна, а зна­ чение 599 - нижнюю (см . рис. 1.6). Положение объекта на экране мы задаем с поr-.ющью кор­ тежа, который представляет собой группу чисел или строк, заключенных в круглые скобки, например 0). Для (0, удобства чтения чисел в кортеже их разделяют запятой и пробелом после нее. Главное , что необходимо знать о кортежах, - это то, что здесь важна правильная пунктуация. Поскольк у кортеж сам использует круглые скобки, а затем еще и целиком помеща­ ется внутрь круглых скобок функции screen.Ыit (),у нас получатся два набора круглых скобок. Поэтому важно нс за­ бывать, что не только сам кортеж должен находиться в скоб­ ках, но еще и круглые скобки функции screen.Ыit (),ко­ торой мы «сообщаем» кортеж, тоже должны быть закрыты. ЗАВЕРШЕНИЕ РА&ОТЫ ПРОfРАММЫ PYGAMEZERO Подобно космосу, твоя программа Руgа1:пе Zero может выполняться бесконечно. Чтобы завершит ь ее, нажми кнопку закрытия окна игры, расположенную в правом верх­ нем углу (см. рис. 1.6). Программу можно закрыть и в окне командно1':1 строки (там, где мы вводили инструкцию pgzrun), нажав сочетание клавиш Ctrl+C. iiJjфlf.jj Не закрывай само окно ко"мандной строки. Иначе тебе придется открыть его снова, чтобы вновь запустить програ"м"му Pygame Zero. Если ты все-таки закрыл(а) его по оиtибке, обратись к разделу «Запуск игры» во введении, чпzобы вспо"мниrпь, как зтzустить 50 rЛАВА 1 Pygarne Zao.
СОЗДАЕМ ПЛАНЕТУ И КОСМИЧЕСКИЙ КОРА&nь Давай отобразим на экране Марс и наш космический корабль. В IDLE добавь к твоей программе listiпg1-l.py две 1-2. последние строки из листинга пРимЕчАниЕ Я буду использовапzь сrпроку --пропуск-- в листингах, пzем самы"м показывая, что я пропустил часть продемонстри­ рованного ранее кода. Кроме того, я буду выделять уже имею~цийся код серым цветом, чтобы новый код, который необходимо добавить, сразу бросался тебе в глаза. Не вводи код, оформленный серым цветом, несколько раз! В следующе!\1 листинге я не стал вписывать комментарии и настраивать переменные, чтобы сэкономить место и об­ легчить тебе чтение нового кода. Но в твоей программе эти инструкции должны остаться. Просто добавь две новые строки в конец имеющегося кода. --пг::1пуск-­ dе f draw(): screen.Ьlit(images.backdrop, screen . Ыit screen .Ы it (images.mars, (images.ship, (0, 0)) (50, 50)) (130, 150)) Листинг 1-2. Добавляем изображение Марса и космического корабля Сохрани обновленную программу в файл listiпgl-2.py, выбрав команду меню File ~ Save as (Фа1'::'1л ~ Сохранить как). Затем переключись обратно на окно командной строки и запусти программу, введя команду ру. На рис. ран - 1.7 pgzrun l i stingl-2. показано, как теперь должен выглядеть эк­ на нем должна появиться красная планета, а над ней космический корабль. пРимЕчАниЕ Если твоя програ"м.ма не рабопzает, убедись, что перед все.ин инсrпрукциями screen.Ыit () стоят четыре пробе­ ла, а сами инструкции стоят ровно друг над другом. П ервая из новых инструкций помещает изображение тars.jpg в позицию (50, 50), которая находится в левом ТВОЙ ПЕРВЫЙ ВЫХОД В ОТКРЫТЫЙ КОСМОС 51
верхнем углу экрана. Вторая новая инструкция помещает корабль в позицию с координатами (130, 150). В обоих слу­ чаях координаты используются для того, чтобы ПО1\·1естить изображение в верхний левый угол. Рис. 1.7. Марс и космический корабль. Снимок Марса сделан космическим телескопом «Хаббm> в 1991 году MEHREM ПЕРСПЕКТИВУ: ПОЛЕТАЕМ ЗА ПЛАНЕТОЙ А теперь давай посмотрим, как сделать так, чтобы корабль летел за планетой. Измени порядок следования последних двух инструкций в IDLE, как показано в листинге 1-3. Для этого выдели одну из строк, нажми сочетание клавиш Ctrl+X, тем самым вырезав строку, затем щелкни по новой строке и нажми сочетание клавиш Cti-l+V для вставки ранее вырезанной строки. Тот же результат можно получить, вос­ пользовавшись пунктами в меню Cut (Вырезать) и Paste (Вставить) Edit ( Правка), расположенном в верхней части экрана. --гrг >r,yc..к-­ dE>f draw (): screen.Ыit 52 rЛАВА 1 (images.back.drop, (0, 0))
screen.Ыit screen.Ыit (images.ship, (images .mars, (130, 150)) (50, 50)) Листинг 1-3. Меняем местами инструкции для планеты и корабля Если предыдущая версия твоей программы все еще рабо­ тает, закрой ее. Сохрани нов ую программу в файл с именем lz:stz:ngl-3.py ду и за пусти ее из командной строки, введя коман­ pgzrun listingl-3.py. Теперь перед тобой должен по­ явиться космический корабль, который находится позади планеты, как показано на рис. 1.8. Если же перед тобой какая-то другая картинка, убедись, что ты открыл(а) нуж­ ный файл (listingl-3.py), а затем проверь правильность напи­ сания инструкций для программы. Корабль находится позади планеты потому, что изобра­ жения добавляются на экран в том порядке, в котором они указаны в програ.мr·.~е. В новой версии нашей программы сначала добавляется звездное небо, потом корабль, а затем Марс. К аждое следующее изображение накладывается по­ верх предыдущего. Если картинки перекрываются, то изо­ бражение, которое было добавлено последним, появляется н ад тем изображением, что было добавлено раньше. Рис. 1.8. Космический корабль теперь находится позади планеты ТВОЙ ПЕРВЫЙ ВЫХОД В ОТКРЫТЫЙ КОСМОС 53
YЧE&HASI МИССИЯ No 2 Сможешь передвинуть одну из инструкций рисования карти­ нок так, чтобы планета и космический корабль исчезли? Если затрудняешься , то просто поэкспериментируй, меняя местами инструкции. После каждой перестановки сохраняй програм­ му и запускай ее заново, чтобы понаблюдать за тем, как меня­ ется результат выполнения кода. Убедись, что инструкции для создания рисунков в функции draw () выровнены и имеют отступы в четыре пробела. Когда закончишь с экспериментами, снова следуй инструкциям в ли­ стинге 1-3, чтобы вернуть корабль и Марс в прежнее положение. ВЫХОД В ОТКРЫТЫЙ КОСМОС! Пришло время выбраться из нижней части космического корабля и выйти в открытый космос. От р едакти р уй свою программу так, чтобы она соответствовала листингу 1- 4. Только не забудь оставить инструкции, которые здесь не о т об р ажены, так и ми же, какими они были раньше. Сохрани обновленную программу в файл - listingl-4.py. проГiуск def draw(): screen.Ыit (images.backdrop, (0, 0)) screen. Ыit ( images. mars, ( 50, 50) ) screen. Ыit ( images . astronaut, (player_x, player_y) ) screen.Ыit(images . ship, (550 , 300)) о б def game_loop (): global player_x, player_y 0 if keyboard.right : @ player_ x += 5 б elif keyboard.left: player_x - = 5 elif keyboard.up: player_y - = 5 elif keyboard.down : player_y += 5 €) о 0 clock.schedule_interval(game_loop, 0.03) Ли с тин г 1-4. Добавляе м ко м анды для выхода в отк р ыты й к ос мос 54 rЛАВА 1
В этот листинг мы добавим новую инструкцию О для того, чтобы нарисовать астронавта, расположив его в коорди­ натах player _ х и player чале программы в листинге _у, которые мы указали еще в на­ 1-1. Как видишь, вместо конкрет­ ных чисел мы 1\ЮЖем использовать имена пере1\1енных. Программа будет использовать хранящуюся в этих перемен­ ных информацию, чтобы понимать, куда следует помещать астронавта всякий раз, когда ты решишь вывести его на экран. Обрати внимание, что порядок вывода изображений в прогр~1мме изменился, и теперь сначала идет фон, затем Марс, потом астронавт и последним - корабль. Убедись, что ты изменил(а) порядок инструкщ11':'1 screen.Ыit () в соответствии с этой последовательностью. Астронавт начинает «Перекрывать» корабль. И поскольку астронавт на­ рисован перед кораблем, все будет выглядеть так, будто аст­ ронавт появляется из-под космического транспортного сред­ ства. Кроме того, мы переместили корабль 6 на правую нижнюю часть экрана. Так мы создаем свободное простран­ ство, пересекая которое космонавт может долететь до планеты. Рис. 1.9. Ты покидаешь корабль, чтобы выйти в открытый космос Запусти программу, введя команду listingl-4 .ру. pgzrun Воспользуйся клавишами+-----, j, l и-----+, ТВОЙ ПЕРВЫЙ ВЫХОД В ОТКРЫТЫЙ КОСМОС 55
чтобы свободно перемещаться в пространстве. Как видно из рис. 1.9, твой персонаж защищен скафандром. Из рисунка также видно, что наш человечек находится за космическим ко­ раблем, но поверх Марса и звездного неба. Такой порядок по­ явления изображений создает простую иллюзию глубины. Ко­ гда мы будем рисовать космическую станцию (в третьей главе), мы будем пользоваться именно этиJ\1 приемом. Благодаря это­ му нам удастся создать 3D-перспективу помещения. Мы будем «Конструировать » комнаты, начиная с заднего плана , тем са­ мым придавая пространству ощущение глубины. YЧE&HASI МИССИЯ No 3 Можешь ли ты отредактировать код так, чтобы переместить в правый верхний угол экрана космический корабль и астро­ навта? Для этого тебе понадобится изменить начальные зна­ чения переменных player _ х и player _у, а также позицию, в которой появляется космический корабль. Убедись, что в начале программы игрок находится «внутри» ко­ рабля (фактически за ним). Поэкспериментируй с другими переменными. Это отличный способ ознакомиться с возмож­ ными расположениями объектов на экране. При необходимо­ сти обратись к рис. 1.6 РАЗ&ИРАЕМСR В КОДЕ ВЫХОДА В ОТКРЫТЫЙ КОСМОС Листинг выхода в открытый космос, 1-4, интересен тем, что он позволяет тебе управлять персонажем с поJ\ющью клавиатуры, что очень важно для нашей игры «Побег». Давай посмотрим, как работает итоговая версия программы выхода в открытый космос. В ней мы берем все наработки из наших предыдущих ли­ стингов и добавляем в них новую функцию, game _ loop () @. Эта функция позволяет изменять значения переменных player _ х и player _у при нажатии клавиш+---, j, l и----+. Таким образом можно пере1\-1ещать нашего персонажа - аст ­ ронавта, поскольку эти переменные определяют позицию, на которой должен находиться наш герой. 56 rЛАВА 1
Прежде чем мы продолжим, нам необходимо изучить два разных типа переменных. Переменные, которые изменяют­ ся внутри функции, обычно принадлежат этой функции и не могут быть использованы другими функциями. Они называются локальными и исключают случайное взаимо­ действие одних кусочков программы с другими кусочками, в результате которого могут возникнуть ошибки. Но в коде выхода в открытый космос нам нужны функ­ ции draw () и game _ loop (),которые и те же переменные player _ х и используют одни player _ у. Поэтому эти переменные должны быть глобальными, то есть их может использовать любая часть программы. Мы настраиваем гло­ бальные переменные в начале программы, отдельно от каких -либо функций. Чтобы сообщить Python, что функция game _ loop () должна обращаться именно к глобальным переменным, которые мы настроили отдельно от этой функции, мы ис­ пользуем ключевое слово global О . Мы помещаем его в начало функции и перечисляеl\1 переменные, которые будут использоваться в качестве глобальных. Таким обра­ зом мы как бы модифицируем некую систему безопасно­ сти, которая не позволяла нам изменять значения пере­ менных, определенных вне функции. Н ам не нужно использовать глобальные переменные в функции draw (), ведь здесь нет необходимости изменять значения этих пе­ ременных. Ей просто нужно знать, чпzо содержат эти пе­ ременные. С помощью команды i f мы задаем в программе исполь­ зование управления с клавиатуры. Эта инструкция опреде­ ляет некоторые условия, при соблюдении которых Python будет что-то делать. Мы по-прежнему используем четыре пробет1 для отступа перед инструкциями, принадлежащи­ ми команде if. Это означает, что в листинге 1- 4 эти ин­ струкции отделены от начала строки в общей сумме восе­ мью пробелами, поскольку они также находятся внутри функции game _ loop (). Эти инструкции выполняются только в том случае, если после команды тора окажется true. if значение опера­ Иначе программа должна пропустить все инструкции, принадлежащие команде if. ТВОЙ ПЕРВЫЙ ВЫХОД В ОТКРЫТЫЙ КОСМОС 57
Может покz~заться странным (особенно если у тебя есть опыт программирования HJ д ругих языках) использование такого количества пробелов, чтобы показать, какие ин­ струкции относятся друг к другу, но на самом деле это об­ легчает чтение программ. При работе с другими языками програмl\шрования наборы инструкций зачастую необхо­ димо заключать в скобки, как, например, в данном случае. Но в случае с Python все проще. Мы используем команду клавиша ной - 0. p l ayer _ if, чтобы п роверить, нажата ли Если это так, мы м е няем значение перемен­ х, прибавив к нему 5 0, тем самым переме­ щая персонажа - астронавта вправо. Символы +=означают « увеличить на сколько-то», поэтоJ1.1у следующая строка уве­ личивае т значение переменной player _ хна 5: playe r _ x += 5 Аналогично символы -=означают « уменьшить на сколь­ ко-то», поэтому следующая инструкция уменьшает значе­ ние переменной player _ х на 5: player_x - = 5 Если клавиша - не нажата , мы проверяем, нажата ли кла­ виша+--. Если да, то программа вычитает 5 из значения пе­ ременной player _ х, перемещая героя-астронавта влево. Для этого мы используем команду от eli f б . Это сокращение else if. Представь, что слово else здесь о з начает « иначе » . Таким образом, эту часть нашей программы можно « переве­ сти» так: « Если нажата клавиша-, прибавь 5 к значению пе­ ременной х. В противном случае, если нажата клавиша+--, вычти 5 из значения переменной Х». Далее мы используем elif, ч т обы по той же схеме проверить, нажаты ли клавиши j и l, и затем меняем положение объекта относительно оси у, чтобы переместить астронавта выше или ниже. Функция draw() работает со значениями переменных player _ х и player _ у для изменения положения астронавта, поэто­ му изменение этих чисел заставляет астронавта двигаться по экрану. 58 rЛАВА 1
СОВЕТ Если ты изменишь команду elif (i) на команду if, программа позволит тебе перемещаться вверх или вниз одновременно с передвижением влево или вправо, то есть ты будешь двигать­ ся по диагонали . В программе про выход в открытый космос это выглядит забавно, но чуть позже мы попробуем использо­ вать подобный код для перемещения по космической станции, и вот там это будет выглядеть очень ненатурально. Последняя инструкция loop () 0 запускаться каждые в Руgагпе Zero. заставляет функцию 0,03 game _ секунды благодаря таймеру Таким образом программа постоянно прове­ ряет нажатие клавиш и своевре1'.Iенно перемещает объект. Обрати внимание, что ставить скобки после имени loop game _ не нужно. В этой инструкции нет отступа, поскольку она не принадлежит какой -либо функции. Когда программа запускается, срабатывают инструкции, которые не принад­ лежат какой-либо из функций. Выполнение инструкций происходит в том порядке, в котором они указаны в тексте программы, сверху вниз. Поэтому последняя строка про­ граммы запускается одной из первых, сразу после настрой­ ки переменных. В это1':'1 строке происходит вызов функции game _ loop (). Функция draw () автоматически запускается всякий раз, когда необходимо обновить изображение на экране. Так ра­ ботает Руgап1е Zего. YЧE&HAll МИССИll № 4 Давай добавим к нашему космическому костюму ускорители. Можешь придумать, как сделать так, чтобы вверх или вниз астронавт двигался быстрее, чем влево и вправо? Каждый раз при нажатии клавиш «вверх» или «вниз» астронавт должен ускоряться, но нажатие кнопок «влево» или «вправо» не дол­ жно влиять на скорость движения . ТВОЙ ПЕРВЫЙ ВЫХОД В ОТКРЫТЫЙ КОСМОС 59
Наслаждайся захватывающими дух видами во время вы­ хода в открытый космос и проведения ремонтных работ на корабле. Во второй главе я покажу тебе несколько хитро­ стей, которые помогут безопасно выходить в космос. rотов ли ты к ПОЛЕТУ? Отметь следующие пункты, если чувствуешь, что материал этой главы тебе понятен. Если же какой-то из вопросов вызывает затруднение, вернись к тексту главы и изучи тему еще раз. D Ты используешь IDLE для создания программы, чтобы ее Л<южно было сохранять, редактировать и запускать снова. Перейди к окну ввода сценариев, выбрав команду меню File ~ Ne,v File (Файл ~ Новый файл), или отредактируй существующий файл, выбрав File ~ Open (Файл ~ Открыть). D Строки это фрагменты текста в коде. Двойные кавычки - от1\1ечают начало и конец строки. Строка может содержать цифры, однако они будут рассматриваться просто как символы. D Переменные хранят информацию, будь то числа или строки. D Функция print () выводит информацию на экран. Ты можешь использовать ее для работы со строками, числами, вычислениями или значениями переменных. D Символ # в программе обозначает комментарий. Pytlюn игнорирует все, что находится в строке после символа #. Комментарии - это удобный способ напомнить и себе, и другиl\1, зачем нужен тот или иной код в программе. D Используй переменные WIDTH и HEIGHT, чтобы задать раз­ мер окна игры. D Чтобы запустить программу Руgагне Zего, открой из папки, в которой находится твоя программа на командной строки и введи в нем Python, окно pgzrun filename .ру для запуска программы. D Функция - это набор инструкций, которые можно запу­ скать, когда это требуется в твоей программе. Для создания 60 rЛАВА 1
новых объектов или обновления игрового экрана Руgаше Zero D использует функцию draw (). Используй screen.Ыit(images.имя _графического_ файла , (х, у)), чтобы задавать изображению положение относительно системы координат (х, у) на экране. Координаты по осям х и у задаются числа1\1и, точка с коор­ дината.ми (О, D Кортеж - 0) расположена в левом верхнем углу экрана. это группа чисел или строк, заключенная в круг­ лые скобки и разделенная запятой. После создания кортежа его содержимое нельзя изменить. D Чтобы завершить программу Руgаше закрытия окна или клавиши Cti-l +C Zero, нажми на кнопку в окне командной строки. D Если одно изображение перекрывает другое, то изображе­ ние, нарисованное последним, окажется поверх всех остальных. D Команда elif - это сокращение от для объединения условий else if. И спользуй ее if, чтобы программа выполняла только один набор инструкций. В нашей программе мы используем эту команду для того, чтобы игрок не мог дви­ гаться в двух направлениях одновременно. D Если мы хотим изменить значение переменной, которая находится внутри функции, а затем использовать ее в дру­ гой функции, нам необходимо сделать эту переменную гло­ бальной. Мы объявляем ее вне како1"::'1-либо из функций, а затем при необходимости добавляем эту переменную в нужную функцию, используя ключевое слово D global. Мы можем настроить интервал запуска функции, используя таймер, доступный в Руgа1ле Zего. ТВОЙ ПЕРВЫЙ ВЫХОД В ОТКРЫТЫЙ КОСМОС 61
ОБСУЖДЕНИЕ МИССИИ Ниже приведены ответы на учебные миссии из этой главы. УЧЕБНАЯ МИССИЯ № 1 Ответ будет разным в зависимости от того, как тебя зовут, но при­ мерно он должен выглядеть так: >>> print ( "Юрий Гагарин" ) УЧЕБНАЯ МИССИЯ № 2 Если добавить звездное небо в самом конце, оно скроет планету и космический корабль. Хитро! Выведи изображения в следующем порядке: --лролуск-­ dе f draw(): (50, 50)) (130, 150)) screen.Ыit(images.backdrop, (0, 0)) screen.Ыit(images.mars, screen.Ыit(images.ship, УЧЕБНАЯ МИССИЯ № Э Измени значение переменной player _ у, расположенной в начале программы, с 350 на любое меньшее число, например 150 . Второе число в кортеже для инструкции screen . Ыit (),относящейся к изо­ бражению корабля, тоже измени на меньшее число, например 50. Другие числа можно использовать в том случае, когда корабль нахо­ дится в правом верхнем углу, а астронавт следует за кораблем. УЧЕБНАЯ МИССИЯ № 4 Чтобы вверх или вниз передвигаться быстрее, чем влево или вправо, измени значение, на которое увеличивается значение переменной player _ у при каждом нажатии клавиши. Если ты задашь значение больше 5, то при каждом нажатии клавиши «вверх» или «вниз» игрок будет перемещаться на большее расстояние в соответству­ ющем направлении. Визуально астронавт должен двигаться быстрее. Но если ты задашь слишком большое значение, иллюзия анимации будет потеряна, и появится впечатление, что твой персонаж будто бы телепортируется в пространстве. Поэкспериментируй с различными значениями и понаблюдай, как это работает. --лролуск-- е1 i keyboard.up: player_y -= 15 elif keyboard.down: player_y += 15 --пропуск--
СПИСКИ СПАСУТ ТЕ&Е ЖИЗНЬ Астронавты работают, ориентируясь на списки. Контрольные перечни мер по технике безопасности, которые они используют, позволяют убедиться перед полетом, что все системы работают исправно. Например, памятки по мерам реагирования в чрезвычайных ситуациях указывают астронавтам, что делать в слу­ чае нештатной ситуации, чтобы не впадать в панику. Процедурные контрольные списки позволяют удостовериться, что оборудо­ вание используется правильно, и потому во время путешествия ничего не сломается и ничто не помешает астронавтам вернуться домой. Однажды эти списки даже могут спасти им жизнь. СПИСКИ СПАСУТ ТЕ&Е ЖИЗНЬ 63
В этой главе ты узнаешь, как работать со списками в Python и как использовать их для составления контроль­ ных перечней, карт и почти всего остального в этой вселен­ ной. Во время работы над игрой «Побег» тебе обязательно понадобятся списки для хранения информации о планиров­ ке коо.н1ческой станции. СОСТАВИМ ТВОЙ ПЕРВЫЙ СПИСОК: ПЕРЕЧЕНЬ ТРЕ&ОВАНИЙ К ЗАПУСКУ Взлет - один из самых опасных моментов в космических путешествиях. Когда за ракету отвечаешь ты, появляется желание дважды проверить все механизмы перед стартом. Ниже базовый контрольный список того, что нужно сде­ лать перед взлетом. D D D D Надень скафандр. Задрай люк. Проверь давление. Пристегни ремни. В Python есть идеальный способ хранения этой инфор ­ мации- список. Он похож на переменную, в которой хра ­ нится несколько значений. Чуть позже ты узнаешь, что спи­ сок r•ложно использовать для хранения чисел и текста как по отдельности, так и их комбинации. Давай составим для нашего астронавта список off _ checklist. take _ Поскольку для эксперимента мы исполь­ зуем небольшой фрагмент кода, предлагаю ввести код в обо­ лочку Python. Пока мы не будем создавать программу. (Если тебе нужно освежить знания о том, как редактиро­ вать код в оболочке Python, см . раздел «Знакоr-.1ство с обо­ Python» в главе 1.) Введи в оболочке следующий нажимая клавишу Enter в конце каждой строки для лочкой текст, того, чтобы перейти в списке на новую строку: >>> take off checklist [ " Надень " Задрай " Проверь скафандр " , люк " , давление " , " Пристегни 64 rЛАВА 2 ремни " ]
O>JФJi·jj Убедись, что скобки, кавычки и точки расставлены верно. Если оболочка сообщит о каких - либо оzаибках, введи код списка еще раз и снова проверь, что скобки, кавычки и запятые находятся на своих местах. Чтобы не вводить код повторно, с 11ол1ощью л1ыzиu выдели текспz в оболочке, U.{елкни по нему правой кнопкой мыи.J.и, выбери команду Копировать (Сору) в контекстном л1еню, затем снова щелкни правой кнопкойл1ыши и выбери Вставить Давай разберемся, как составляется список checklist. (Paste). take _ off _ Н ачало списка от1\1ечается открывающей ква­ дратной скобкой. До тех пор пока Python не обнаружит за­ крывающую квадратную скобку, он будет считать, что список продолжае т ся. Э т о означает, что ты можешь нажи­ мать клави ш у после завер ш ения ввода каждой строки, Enter чтобы начать новую, и Python будет знать, что список п р о­ должается, ведь скобки еще не закрыты. Кавычки говорят Python о том, что здесь расположен не­ который текст, и о том, где этот текст начинае т ся и где за­ канчивается. Каждая запись нуждается в собственном обособлении кавычками (как открывающей, так и закры­ вающей). Кроме того, необходимо разделять разные фраг ­ менты текста запятыми, однако п оследнюю строку не нуж­ но заканчивать запятой, так как после нее нет следующего элемента. ПРОСМАТРИВАЕМ НАШ СПИСОК Чтобы просмотре т ь свой контрольный список, ты можешь воспользоваться функцией ранее в главе 1. print (),как мы это делали Добавь название своего списка в функцию print (),например так: >>> print (take_ off_checklist) [ ' Надень скафандр ', ' Пристегни ' Задрай люк ', ' Проверь давление ', ремни ' ] Н е нужно обрамлять кавычками take _ off _ checklist, поскольку это имя переменной, а не фрагмент текста. Но если ты все же поставишь кавычки, то Python просто выведет на экране следующш':'r текст: СПИСКИ СПАСУТ ТЕ&Е ЖИЗНЬ 65
take _ off _ checklist вместо содержимого списка. По­ пробуй так сделать и увидишь, что произо1':'щет. ДО&АВЛЯЕМ И УДАЛЯЕМ ЭЛЕМЕНТЫ Даже если список уже создан, ты все еще J\IОЖешь добавить в него какой-либо элемент с помощью команды append (). Слово аррепd означает «добави ть что-то в конце». Команда append () >>> работает слелуюrт(им образом: take_off_checklist . append( "Cooбщи в ЦУП , что проверки завершены ") Мы вводим имя списка (без кавычек), за которым следует точка и команда append (),а затем помещаем элемент, кото­ рый хотим добавить, в круглые скобки. Элемент будет до­ бавлен в конец списка, что можно видеть при повторном выводе содержимого списка: >>> pri nt (take_off_checklist) [ ' Надень ремни ', скафандр ', ' Сообщи в ' Задрай ЦУП , что люк ', ' Проверь проверки давление ', ' Пристегни завершены '] Удалять элементы из списка можно при помощи коман­ ды remove (). Например, давай уберем элемент " Задрай люк": >>> take_off_checklist.remove( >>> pri nt (take_off_c hecklist) ['Надень ' Сообщи скафандр ', в ЦУП , что ' Проверь проверки "Зaдpaй люк " ) давление ', ' Пристегни ремни ', завершены '] Снова введи имя списка, за которым следует точка и команда remove (),а затем укажи в круглых скобках эле­ мент, который хочешь удалить. il>Jфlf.jj Когда ты удаляеu1ь эле.мент из списка, убедись, что вве­ денное пzобой слово точно соотвепzствует содержи.маму этого эле.мента. Проверяй прописные буквы и знаки препи­ нания. В противном случае Pyt!?On мый элемент и выдс~ип оuтбку. 66 rЛАВА2 не найдет запраzиивае­
ИСПОЯЬЗУЕМ ИНДЕКСЫ Хм, думаю, нам стоит вернуть в список удаленный элемент " Задрай люк " , пока кто - нибудь из центра управления полетами не заметил пропажу. Ты 1\·ЮЖешь поместить эле­ мент в любую позицию в списке, используя индекс. Индекс это пози ц ия элемента в списке. - элементов не с 1, Python ведет счет а с О, поэтому индекс первого элемента в списке всегда равен О, второго - 1 и т. д. ВСТАВЛЯЕМ ЭЛЕМЕНТ Используя индекс, вернем элемент "Задрай люк" на место: >>> take_ off_ checklist.insert(l, >>> print (take_off_checklist) [ ' Надень ремни ', скафандр ', ' Сообщи в ' Задрай ЦУП , что люк ', " Задрай люк " ) ' Проверь проверки давление ', ' Пристегни завершены ' ] Фух! Я думаю, мы легко отделались. Поскольку нумера­ ция элементов начинается с О, то, когда мы вставили люк, он оказался на первой позиции, будучи при этом вторым эле­ ментом в списке. Остальные элементы списка перемести­ лись вниз, как бы освобождая место соседям. Соответствен­ но и их индексы изменились в большую сторону, как показано на рис. [ "Надень скафандр ", "Проверь индекс О ["Надень индекс скафандр", t индекс 1 "Пристегни ремни ", t индекс 1 "Задрай люк " , индекс О Рис. давление" , t t 2.1. 2 t индекс 2 "Проверь давление", индекс "Сообщи в ЦУП "Пристегни ремни", ." ] 3 "Сообщи в ЦУП.""] t индекс 3 индекс 4 2.1. Вставка элемента с и н дексом, р а вным 1. Верхняя строка: перед вставкой. Нижняя ст рока: п осле вставки ДОСТУП К ОТДЕЛЬНОМУ ЭЛЕМЕНТУ Ты можешь обращаться к элементам списка и по отдельно­ сти при помощи имени списка и индекса нужного эле­ мента, воспользовавшись квадратными скобками. СПИСКИ СПАСУТ ТЕ&Е ЖИЗНЬ 67
Например, чтобы вывести определенные элементы в списке, rvюжно сделать так: >>> print (take_off_checklist[O]) Надень скафандр >>> print (take_of f_checklist [l]) Задрай люк >>> print (take_off_checklist[2]) Проверь давление Теперь ты знае ш ь, как можно вывести отдельный элемент списка! llJИ.if.51 Не путай скобки. Прои<е говоря: указывая Руt!юп на опре­ деленный элемент из списка, используй квадр.ипные скобки. А круглые скобки нужны при работе со сmtском или элементами в не.м, напри.л.tер когда создаешь список или добавляеzиь в него элементы. Каждая открывающая скобка должна zмtеть закрываюzцую скобку того же пzипа. ЗАМЕНА ЭЛЕМЕНТА KpoJ1.ie того, зная индекс элемента, ты можешь изменить зна­ чение элемента. Просто введи название списка, а затем индекс нужного элемента, после чего введи знак равенства, Таким образом, ты сообщаешь =. какое новое значение Python, ты хочешь записать в элемент списка, например так: >>> take_off_checklist[ЗJ = " Сделай >>> print (take_off_checklist) [ ' Надень селфи ', скафандр ', ' Сообщи в ' Задрай ЦУП , что люк ', селфи " ' Проверь проверки давление ', ' Сделай завершены '] Предыдущий элемент с индексом 3 был удален и заменен новым элеJ\1ентом. Имей в виду, что при замене элемента Python не запоминает исходное значение. Вспомни, о чем мы только что говорили, и верни элемент, вот так: >>> take_off_checklist[ЗJ = " Пристегни >>> print (take_off_checklist) ['Надень ремни ', 68 rЛАВА2 скафандр ', ' Сообщи в ' Задрай ЦУП , что люк ', ремни " ' Проверь проверки давление ', завершены '] ' Пристегни
УДАЛЕНИЕ ЭЛЕМЕНТА Если ты знаешь, на какой позиции в списке находится эле ­ мент, ты сможешь удали т ь его с помо щ ью индекса, напри ­ мер т ак: >>> del take_off_checklist[2] >>> print (take_off_checklist) [ ' Надень в ЦУП , скафандр ', что проверки ' Задрай люк ', ' Пристегни ремни ', ' Сообщи завершены ' ] Как видно, п ункт « Пр ове р ь давление» исчез из списка. УЧЕliНАЯ МИССИЯ No 1 Пришло время опробовать свои навыки на практике! Мы толь­ ко что удалили из списка пункт 2. Сможешь вернуть его в спи­ сок на нужное место? Выведи список на экран, чтобы убедиться, что все сделано правильно. СОЗДАЕМ КОНТРОЯЬНЫЙ СПИСОК ДЯll ВЫХОДА В ОТКРЫТЫЙ КОСМОС П омнишь, в главе 1 мы говорили о том, что одна из главных о п асностей для астронавта - э т о выход в отк ры т ы й KOCJ\IOC, где совер ш енно нет кисло р ода? П оэтому тебе и нуже н ска­ фандр. Давай посмотрим, ч т о следует сделать, чтобы чувство ­ вать себя безопасно в о т к р ытом космосе. D D D D D Н адеть костюм. П роверить уровень кислорода. П роверит ь гермети ч ность ш лема. Работает ли радио. Открыть шлюз. Давай превратим этот контроль н ый сп и сок в электрон­ ный с п исок Python. Назовем его spacewalk _ checklist, вот так: СПИСКИ СПАСУТ ТЕ&Е ЖИЗНЬ 69
>>> spacewalk_c hecklist [ " Надень скафандр " , " Проверь " Надень кислород " , шлем " , " Проверь " Открой связь " , шлюз " ] Н е забудь проверить скобки и запятые. УЧЕliНАЯ МИССИЯ № 2 Советую всегда тестировать свой код, чтобы убедиться в том, что все работает должным образом. Попробуй вывести на эк­ ран все элементы списка, чтобы проверить, находятся ли они на своих местах? СПИСОК СПИСКОВ: РУКОВОДСТВО К ПОЛЕТУ Теперь у нас есть два контрольных списка: один для взлета и один для выхода в открытый космос. Мы можем объеди­ нить эти перечни, поместив их в другой список, чтобы создать « памятку пилота». Представь, что это руководство просто папка, а внутри нее - два листа бумаги, на которых напечатаны наши списки. СОЗДАЕМ СПИСОК СПИСКОВ Вот так можно создать список списков, чтобы создать руко­ водство к полету: >>> fli ght_ma nu al = [t a ke_o ff_che ck li s t, spacewalk_c h eck li s t ] Печатаем в I DLE имя списка flight _ rnanual, ставим =, а затем указываем те два списка, которые ПО!\Iестить в итоговый flight _ rnanual. Заключаем знак равенства хотим их в квадратные скобки. Как и ранее при составлении списков, разделяем элементы запятой. Итак, наш новый flight _ rnanual содержит два элемента: take _ off _ checklist и spacewalk _ checklist. Если выве­ сти список fligh t _ rnanual на экран , выгля д еть это бу­ список дет так: 70 rЛАВА2
>>> print (f light_manual ) [[ ' Надень скафандр ', ' Пристегни [ ' Надень связь ', ремни ', скафандр ', ' Открой ' Задрай ' Сообщи в ' Проверь люк ', ЦУП , ' Проверь что давление ', проверки кислород ', завершены '], ' Надень шлем ', ' Проверь шлюз ' ]] СОВЕТ Не забывай, что названия списков не нужно заключать в кавыч­ ки. Они используются только при добавлении текста в список. O>J:J.Ji.jj В твоем списке нет пункта «Проверь давление»? Значит, настала пора вернуться к лщссии N° 1 и все-таки вьтол­ нить ее. При необходилtости ты можещ. ь свериться с отве­ тами к учебнылt миссиям в конце главы. Выведенная инфорl\.~ация смотрится неаккуратно, дава1':'r - ка наведем на экране порядок! Чтобы не запутаться, внима­ тельно посмотри на скобки. Квадратные скобки обозначают начало и конец каждого списка. Если представить, что в на­ ших списках нет элементов, выглядеть это будет так: [ [первый_ список] , [в торой_ список]] Заметь, что в середине кода конец первого списка зак р ы ­ вается скобкой, за которой следует запятая, и только после этого с открываю щ ей скобки начинается следующий спи­ сок. Итак, что же произойдет, если ты обратишься к перво­ му элементу списка flight _ rnanual? >>> print (f light_manual[ O]) Первый элемент - это список take _ off _ checklist, поэтому вывод будет выглядеть так: [ ' Надень ремни ', скафандр ', ' Сообщи в ' Задрай ЦУП , что люк ', ' Проверь проверки давление ', ' Пристегни завершены ' ] СПИСКИ СПАСУТ ТЕ&Е ЖИЗНЬ 71
УЧЕ&НАЯ МИССИЯ Попробуй добавить во flight _ manual No З другие контрольные списки и вывести их на экран . Например, ты можешь добавить контрольный список для приземления на планету или стыковки с другим космическим кораблем. ПОИСК ЭЛЕМЕНТА В РУКОВОДСТВЕ К ПОЛЕТУ Если ты хочешь просмотреть конкретный элемент в одном из списков в flight _ manual, вить некоторую информацию: список, в котором Python тебе необходиl\ю предоста­ находится этот элемент, и порядковыi:'t номер элемента в этом списке. В обоих случаях ты можешь использовать индексы, например: >>> print (f l ig ht_man u a l [O ] [ 1]) Задрай люк Сверь полученный результат с выводом контрольного списка в оболочке. Элемент «Задрай люк » находится в пер­ вом списке (индекс О) под И.'.,1енем take _ off _ checklist, 1). а сам элемент является вторым в своем списке (индекс Это и есть те дв а индекса, которые мы использовали для по­ иска. Теперь давай выберем элемент из второго списка: >>> print (f l i g h t_manual[ l] [3 ]) Проверь связь На этот раз мы работаем со вторым списком (индекс и выводим из него четвертый элемент (индекс 3). 1) Может по­ казаться странным, ч т о Р уt!:юп начинает отсчет с О, но со­ всем скоро ты привыкнешь к этому и автоматически будешь уменьшать на единицу нужный индекс. Только будь внима­ тельнее в магазине, чтобы случайно не купить на один па­ кет молока меньше! 72 rЛАВА2
СОВЕТ Чтобы вывести на экран список или переменную, ты можешь не набирать команду print (), если вводишь код в оболочке: >>> flight_manual [0 ] [ 2 ] ' Проверь давление ' Однако этот прием работает только в оболочке и не рабо­ тает при запуске программы из файла. Впоследствии тыча­ сто будешь встречаться с тем, что в Pythoп одно и то же действие можно выполнить разными способами. В этой книге основное внимание уделено приемам, которые помогут тебе создать игру «Побег». Изучая Pythoп, ты сформируешь соб­ ственный стиль программирования, основываясь на своих предпочтениях. О&ЪЕДИНЕНИЕСПИСКОВ Чтобы объединить два списка, используй знак плюс +. Давай составим список всех навыков, необходимых для взлета и выхода в открыты1":'1 космос, и назовем его skills list: >>> skil l s _l ist = take_ off_ checklist + spacewa l k_ checklist >>> print (skills_list) [' Надень ремни ', скафандр ', ' Сообщи скафандр ', ' Открой в ' Задрай ЦУП , ' Проверь что люк ', ' Проверь проверки кислород ', давление ', завершены ', ' Надень шлем ', ' Пристегни ' Надень ' Проверь связь ', шлюз ' ] П олученны1":'1 результат - это единый список, в которшл перечислены все у!'v-~ения, необходимые астронавтам. Сюда входят навыки, собранные из двух составленных ранее списков. Кроме того, мы можем указать и дополнительные навыки, введя имя объединенного списка и знаки +=,чтобы добавить в общий перечень новые элементы или другие списки. (В главе 1я рассказывал, как использовать символы +=,чтобы увеличить значение переменной.) Мало кому удается побывать в космосе, поэтому очень важно, чтобы астронавт мог п оделиться п олучен н ым СПИСКИ СПАСУТ ТЕ&Е ЖИЗНЬ 73
опытом. Давай создадим список и перечислим pr _ list в неi\1 навыки, которые могут пригодиться астронавту для общения с землянами . Мне кажется, умение делать селфи не будет тут лишним! >>> pr_l is t = [ " Сделай селфи " , " Прочитай " Дай лекцию " , интервью " Организуй журналистам " , конференцию " ] >>> sk il ls _li st += pr_li st >>> print (s kills_l is t ) [ ' Надень ремни ', скафандр ', ' Сообщи скафандр ', ' Открой в ' Проверь шлюз ', журналистам ', ' Задрай ЦУП , что кислород ', ' Сделай селфи ', ' Организуй Теперь люк ', ' Проверь проверки давление ', завершены ', ' Надень шлем ', ' Прочитай ' Пристегни ' Надень ' Проверь лекцию ', ' Дай связь ', интервью конференцию ' ] skills _ list содержит элементы из списка pr _ list. skills _ list по-прежнему представляет со ­ бой единый список, к которому мы добавили некоторые до ­ полнительные элементы, в отличие от flight _ manual, со­ стоящего из двух отдельных списков. СОВЕТ Обрати внимание на эту строку: skills_list += pr_list Это просто краткая запись следующего кода: skills list skills list + pr_list Очень полезный прием! СОЗДАНИЕ КАРТ ИЗ СПИСКОВ: ОТДЕЛЕНИЕ НЕОТЛОЖНОЙ ПОМОЩИ Умение ориентироваться в пространстве - важнейший навык для астронавта . Тебе всегда нужно знать, где нахо­ дишься ты, где находится ближайшее безопасное место и где добыть воздух, чтобы справиться с чрезвычайными ситуациями. Наша игра « Побег » будет хранить в себе карту 74 rЛАВА2
комнаты, в которой находится игрок, чтобы J\·1Ы могли пра­ вильно нарисовать помещение и дать игроку возможность взаимодействовать с объекта!-.111. Давай посмотрим, как с помощью списков создать карту для отделения неотлож­ ной помощи. СОСТАВЛЯЕМ КАРТУ Теперь, когда ты у1\1еешь управлять как отдельными спис­ ками, так и списками в списках, мы можем приступить к созданию карты. На этот раз мы оформим наш код как программу. Это значит, что теперь мы работаем не в обо­ лочке. В верхней части окна File ~ New File Python выбери команду меню (Файл ~ Новый файл), чтобы открыть новое окно. Введи листинг room_map 2-1 [ [ 1, О, О, О, о ] , [О, О, О, 2, о ] , [О, о, О, О, о ] , [О, 3, О, О, о ] , [о, о, о, о, 4] в окно открывшейся программы: print (ro om_map) Листинг 2-1. Настраиваем отделение неотложной помощи Обрати внимание, что здесь не нужно добавлять запятую в конце последней строки списка. Эта программа создает и отображает список под названием room _ map. Размеры нашего нового отделения неотложной помощи будут со ­ ставлять пять на пять метров. Список room _ map содержит в себе пять списков. Каждый из них содержит по пять чисел, которые представляют собой одну строку карты. Я изобра­ зил список так, чтобы он выглядел как сетка. Посмотри на рис. 2.2. На нем показана карта помещения. Сравни сетку с программой. Ты увидишь, что первый список относится к верхней строке, второй список - ко второй строке и так да­ лее. О означает пустое пространство в сетке, а числа от 1 до 4 обозначают находящиеся в помещении различные предметы первой необходимости. Числа, которые мы будем использо­ вать в этой главе, символизируют следующие элементы. СПИСКИ СПАСУТ ТЕ&Е ЖИЗНЬ 75
1. Удобрения. 2. Кислородные баллоны. 3. Ножницы. 4. Зубная паста. 5. Спасательные жилеты. 6. Аварийная радиостанция. 1 1 1 1 2 3 4 1 1 1 1 Рис. 2.2. Наша первая карта iiJjфif·jj Убедись, что скобки и запятые стоят на своих месrпах. Одна из причин, по которой л1ы работаем с листинголt 2-1 в программе, а не в оболочке, заклюttается в том, что так налt будет гораздо проzце вносить исправления в код, если мы допустил~ какую-нибудь оzиибку. Выбери команду r-.леню File ~ Save (Файл ~ Сохранить) и сохрани программу в файле с именем listing2- l.py. Эта программа не использует Руgап1е Zего, поэтому мы можем запустить ее в IDLE. В строке меню, расположенной в верх­ ней части окна, открой раздел Run (Выполнить), а затем вы­ бери пункт Run Module (Выполнить модуль). В окне дол­ жно появиться следующее: [[1, О, 0], [0, О, О, 0], о, о, о, [0 , 4]] О, О, 2, 0], [О, О, О, О, О], [О, 3, О, О, Разобраться в списке, выведенном таким образом, непро­ сто, поэтому я и выстроил числа в сетку. Тем не менее вывод нашей программы - это все та же карта с теми же самыl\1и данными, все осталось на своих местах, только представле­ но все немного иначе. В главе 3 ты узнаешь, как вывести данные карты так, чтобы они были похожи на созданный нами ранее список. ПОИСК ПРЕДМЕТОВ ПЕРВОЙ НЕО&ХОДИМОСТИ Чтобы узнать, какой элемент находится в той или иной точке карты, тебе необходимо указать Python координаты дляп оиска. Координ.аты представляют собой комбинацию 76 rЛАВА2
позиции у (сверху вниз) и позиции х (слева направо), именно в таком порядке. Позиция у - это список в room _ который требуется проверить (строка в сетке). Пози­ map, циях элемент в списке, который требуется просмотреть - (столбец) (01. рис. 2.3). На всякий случай напомню, что индексы нумеруются, начиная с О. х - о (\)• б Б о Какой список? 1 1 3 2 4 1 2 .... ::i: С1) ~ С1) :;; 3 >:S: ~ о ~ Рис. 2.3. Координата у указывает 4 4 1 на нужный список. Координатах >1 1 1 указывает на элемент в этом списке 1 llJИ·Ji·il Если тебе уже приходилось работать с координатами, то ты знаеzиь, чrпо координаrпа х идет перед координа­ той у. Но здесь мы лtеняем их местами, чrпобы упростить код. Если мы сначала укажем координату х, то нам при­ дется изменить каждый список из room _ тар таки.м обра­ зо.м, чтобы он предопавлял собой сrполбе~{ карты сверху вниз, а не строку слева штраво. В связи с этим карта выглядела бы странно, она будпzо бы «лежала» на боку и отображалась зеркально. Очень легко запутаться! Потому просто запол1ни, что здесь мы используел1 следую­ щий порядок координат: сначала у, а затем х. Рассмотрим следующий пример: выясним, какой элемент находится в по::шции, ОТJ\·1еченной числом 2 на нашей про­ стой схеме карты. Для этого нам нужно знать следующее. • Число 2 находится во второй строке (сверху вниз), соот­ ветственно, предмет находится во втором списке в room _ map. Нумерация индексов начинается с О, поэто­ му мы вычитаем из 2 единицу, чтобы получить номер индекса для позиции у, которыi"1 соответственно равен Воспользуйся рисунком 2.3, 1. чтобы проверить индекс: СПИСКИ СПАСУТ ТЕ&Е ЖИЗНЬ 77
номера индексов для строк расположены слева от сетки и выделены красным цветом. • Число 2 находится в четвертом столбце (слева на п раво) списка. Н ам вновь нужно вычесть единицу, чтобы получить номер индекса для позиц и и х, который, соответственно, ранен 3. Обрати внимание на рисунок чтобы п р оверить 2.3, это т индекс. Номера и н дексов для коло н ок отоб р ажаются в ве р хней части сетк и , выделенной к р асным цветом. П ерейди в оболочку и введи команду print(), чтобы узнать номер э т ой позиции на кар т е. >>> print (room_ map[l] [3]) 2 К ак и ожидалось, результат - число 2, которое обозна ­ чает кислородный балл о н . П оздравляю, ты изучил(а) свою п ервую ка рт у! УЧЕ&НАЯ МИССИЯ No 4 Попробуй сначала угадать, какой будет результат выполнения этой команды, и лишь потом вводи ее в оболочку: >>> print (room_map[ЗJ [1]) Чтобы самостоятельно решить эту задачу, обратись к карте на рис. 2.2 и к нашему коду со списком. Это должно тебе по­ мочь найти ответ. Если понадобится дополнительная подсказ­ ка, обратись к рис . 2.3. Затем проверь свой ответ, введя инструкцию в оболочке. MEHREM МЕСТАМИ ПРЕДМЕТЫ, РАСПОЛОЖЕННЫЕ В ПОМЕЩЕНИИ Ты можешь переставлять п р едметы, р асположенные в поме­ щении. Давай проверим, какой элемент находится в верх­ нем левом у глу карты. Для это г о снова воспользуемся обо­ лочкой: 78 rЛАВА2
>>> print (room_map[O] [0]) Под номером 1 находятся удобрения. Вряд ли они когда­ нибудь потребуются в отделении неотложной помощи, поэто­ му давай заменим их на спасательные жилеты, они могут при­ годиться в какой-либо чрезвычайной ситутации. Жилеты мы обозначим числом 5. Помнишь, что i\'lЫ использовали знак ра­ венства= для изменения значения эле1\1ента в списке? То же са­ мое можно сделать и для изменения номера на карте, вот так: >>> room_map[O] [0] = 5 Мы вводим координаты, а затем указываем новое значе­ ние для замены исходного. Чтобы узнать, правильно лира­ ботает наш код, можно снова ввести значение для этой ко­ ординаты, которое только что равнялось давай выведем room _ map 1. Кроме того, и убедимся, что спасательные жилеты дейс т вительно размещены там, где мы хотели: >>> print (room_map[O] [0] ) 5 >>> print (ro om_map) [[5 , [О , О , 3, О , О , О , О , 0] , О] , [0 , [О , О , О , О , О , 2, О , 0] , 4]] [0 , О , О , О , 0] , Отлично! Если что, спасательные жилеты будут нас ждать в левом верхнем углу помещения. Предмет под номе­ ром 5- первый в первом списке. УЧЕliНАЯ МИССИЯ № 5 Отделение неотложной помощи очень просторное! Замени зуб­ ную пасту (4) аварийным радиопередатчиком (6). Сначала тебе необходимо найти координаты для числа 4, а затем вве­ сти команду для их изменения. Если затрудняешься при работе с индексами, обратись к рисункам 2.2 и 2.3. В игре «Побег» список room _ map используется для хранения различных предметов в помещении, в котором СПИСКИ СПАСУТ ТЕ&Е ЖИЗНЬ 79
в данный 1\110мент находится игрок. Карта содержит номер объекта, который отображается в каждой позиции на карте . Пустое место обозначено нулем. Размер помещений в нашей игре будет больше, чем сетка room _ map 5 х 5, поэтому размер списка будет варьироваться в зависимости от ширины и высоты ПО"-'Iещения, в котором находится игрок. rотов пи ты к nonETY? Отметь следующие пункты, если чувствуешь, что материал этой главы тебе понятен. D Списки Pythoп могут содержать слова, числа или D Для просмотра любого элемента в списке введи его индекс и то и другое вместе. в квадратных скобках, например pr i nt (take _ off _ checklist [2]). D D Функция append () добавляет новые элементы в конец списка. D Индексы можно использовать для удаления или вставки Функция remove () удаляет элементы из списка, например spacewalk _ checklist. remove ("Надень шлем " ). элемента в определенную позицию в списке. D D Нумерация индексов начинается с О. Ты можешь изменить элемент в списке, используя знак равенства=, например take _ off _ checklist [3] = "Test comms ". D Ты можешь создать список, содержащий вложенные списки, D Ты можешь узнать, какой элемент находится в том или ином чтобы сделать простую карту. месте карты, используя координаты, например введя room _ map [координата _ по _ х]. [координата D по _ _ оси _ оси _ у] П орядок следования координат важен : сначала координата у, а затем х. В космосе все перевернуто с ног на голову. D Координаты а не с D - это индексы, поэтоl\IУ они начинаются с О, 1. Ты можешь использовать сочетание знаков+= для того, чтобы добавить новый элемент в список или чтобы объеди­ нить два списка. 80 rЛАВА2
ОБСУЖДЕНИЕ МИССИИ Ниже представлены ответы на учебные миссии из этой главы. УЧЕБНАЯ МИССИЯ № 1 >>> take_off_checklist.insert(2, УЧЕБНАЯ МИССИЯ № "Проверь давление' ) 2 Вьrведи на экран элементы списка, ссылаясь на их индексы: >>> print (spacewalk_checklist[O]) Надень скафандр >>> print (spacewalk_checklist[l]) Проверь кислород >>> print (spacewalk_checklist[2]) Надень >>> шлем print (spacewalk_checklist[З]) Проверь связь >>> print (spacewalk_checklist[4]) Открой шлюз УЧЕБНАЯ МИССИЯ № Э = >>> docking_checklist [ "Включи ручной режим" , "Поверни ] >>> flight_manual.append(docking_checklist) >>> print (flight_manual) ручку" , "Потяни [['Надень и открой' скафандр', 'Пристегни ремни', ['Надень скафандр', 'Проверь связь', 'Поверни ручку', >>> print 'Задрай люк', 'Сообщи в 'Проверь ЦУП, давление', проверки кислород', 'Открой шлюз'], 'Потяни и 'Проверь что 'Надень завершены'], шлем', ['Включи ручной режим', открой']] (flight_manual[2]) ['Включи ручной режим', УЧЕБНАЯ МИССИЯ № 'Поверни ручку', 'Потяни и 4 3 УЧЕБНАЯ МИССИЯ № >>> room_map[4] [4] >>> print (room_map) = 5 6 [[1, О, О, О, О], [О, О, О, 2, О], [О, 3, О, О, О], [О, О, О, О, 6]] [О, О, О, О, О], открой']

ПОВТОРЯЙ ЗА МНОЙ Считается, что полеты в космос - событие романтическое и героиче­ ское, однако для некоторых людей это не более чем рутина. Когда космо- навты делают уборку на корабле, занима­ ются садоводством в теплице на борту кос­ мической станции или тренируются, чтобы поддерживать себя в форме, они следуют подробным планам, которые разработаны, чтобы команда станции была в безопасно­ сти и могла спокоино заниматься своими делами. К счастью, часть тяжелой работы берут на себя роботы, и при этом не жалу­ ются на утомительную монотонность своего труда. ПОВТОРllЙ 3А МНОЙ 83
Независимо от того, хочешь ты запрограммировать ро­ ботов или создать игру, тебе не обойтись без такой основы програl\1мированияк акц иклы. Цикл - это часть програм­ мы, которая может выполняться повторно: иногда она по­ вторяется определенное количество раз, а иногда работает до тех пор, пока не произойдет некоторое событие. Бывают и вечные циклы. В этой главе ты узнаешь, как работать с ци­ клами, чтобы инструкции выполнялись заданное количе­ ство раз подряд. Ты будешь работать с циклами, опираясь на уже имеющиеся знания о списках, чтобы отобразить кар ­ ту и нарисовать трехмерное изображение помещения. ОТО&РАЖАЕМ КАРТЫ С ПОМОЩЬЮ циклов В игре « Побег » мы будем очень часто применять циклы. Они понадобятся нам для извлечения информации из спис­ ков , чтобы использовать ее в игре. Итак, давай узнаем, как с помощью циклов можно выве­ сти карту на экран. СОЗДАЕМ КАРТУ ПОМЕЩЕНИЯ В этой главе в качестве примера мы создадим новую карту, в которо1'::'1 число 1 будет обозначать стену, а О - пол. Наша комната п редставляет собой пространство, ограниченное стенами, с колонной в центре. Колонна это все равно что часть стены, поэтому ее мы тоже будем обозначать числом 1. Положение колонны выбрано так, чтобы она хорошо смо­ трелась в нашей 3D-комнате, которую мы чуть позже нари­ суем в этой главе. Других предметов в помещении нет, так что никакие иные числа нам здесь не понадобятся. Создай в стинга 3-1, IDLE новую программу и внеси в нее код из ли­ сохранив результат в файл под названием listingЗ-1.py: ro om_map 84 rЛАВА З [ [1 , [1 , [1 , [ 1, 1, 1, 1, О, О, о, О, 1, О, О, О, О, 1] 1] 1] 1] ' ' ' '
[1 , [1, [1, О, О, О, О, О, О, 1, 1, 1, 1], 1], 1] print (room_map) Ли с тин г 3 - 1. Вводим да н ные, чтоб ы созда т ь карту п омещения Эта программа создает список room _ map, который со­ держит семь других списков. Каждый из них начинается и заканчивается квадратными скобками и отделяется от со­ седа запятой. Как уже говорилось в главе 2, за последним элементом списка запятую ставить не нужно. Каждый спи ­ сок это одна из строк карты . Запусти программу, выбрав - команду меню Ru n ~ Run Module (Выполнить ~ Выпол­ нить модуль), после чего в оболочке должно появиться сле­ дующее: [[1 , 1 , 1], [1, 1, 1, 1], О, О, О, [1, О, 1] , [1 , О, О, О, Вспоминая главу О, 2, 1] , [1, О, 1, О, 1], [1 , 1], [1, 1, 1 , 1 , 1]] О, О, О, О, понимаешь, что список карт вывелся на экран сплошным текстом, и читать его очень неудобно. Чтобы сделать карту более аккуратной, воспользуемся ци­ клами. Отображение карты с помощью цикnов Чтобы отобразить карту в виде строк и столбцов, удали последнюю строку программы и добавь две новые инструк­ ции, указанные в листинге 3-2. На всякий случай напоми ­ наю, что строки , выделенные cepыJ\I цветом, вводить не нужно, это своего рода указатель , помогающий тебе - ориентироваться в программе. Сохрани свою программу в файл listingJ-2.py. --пгопус..к- [1 , [1, О б for о, о, о, 1, 1, 1, 1] , 1] у in range ( 7 ) : print (room_ma p [ y] ) Л и сти н г 3-2. И с п ол ьзуем ц и кл, ч т обы в ывести на экран ка рт у помещения ПОВТОРllЙ 3А МНОЙ 85
if>J:J.Ji.jj Не забудь поспzавипzь двоеточие в кою{е первой из новых строк! Без него програм.ма не будет работать. Перед вто ­ рой из новых строк должен быть отступ в чепzыре пробела, чтобы показать Python, какие инструкции ты хочеzиь повторять. Если в кон. z{е строки for ты поставиz.иь двое­ точие, пробелы появятся автоматически после наJ1сапzия клавиzии Enter и перехода н.а следующую спzроку. После повторного запуска программы в оболочке дол­ жно появиться следующее: [1 , [1 , [1 , [1 , [1 , [1 , [1 , 1, 1, 1, О, О, О, О, 1, О, О, О, О, О, О, О, о, о , о , 1, 1, 1, 1] 1] 1] 1] 1] 1] 1] Так-то лучше. Теперь хорошо видно, что стена (представ­ ленная единицами) является границей помещения. Итак, как же работает этот код? Команда for О - почти его двига­ тель. Это и есть инструкция цикла, которая указывает Python повторять некоторы1'::'1 фрагмент кода определенное количество раз. В листинге 3-2 говорится, что Python дол­ жен повторить инструкцию print () для каждого элемента из списка room _ map б . Каждый элемент в room _ map представляет собой список, содержащий одну строку карты, поэтому при последовательном выводе фрагментов карты они выводятся по одной строке за раз, благодаря чему карта приобретает упорядоченный вид. Давай разберем код более подробно. Чтобы создать по ­ следовательность чисел , мы используем функцию С помощью range (7) мы говорим range (). Pytl10n, что нам нужно 7, не вклю­ сгенерировать последовательность чисел от О до чая саму 7. Н о почему последнее число не используется? Потому что функция range ()работает именно так! Когда Python считает, что 1\•1Ы хо­ тим начать отсчет с О. Таким образом, range (7) задает по­ следовательность чисел О, 1, 2, 3, 4, 5 и 6. мы задаем ей только одно число, 86 rЛАВА З
Каждый раз, когда код повторяется, переменная в коман­ де for принимает значение следующего элеме н та последо­ вательности . Таким образом, переменная у принимает по очереди значения О, индексам в 1 , 2, 3, 4, 5 и 6, ч то соответс т вует room _ map. В качестве имени п е р еменной я выбрал обозначение у, поскольку оно ис п ользует с я для обозначения строки ка рт ы, ко т о р ую мы хотим вывести н а экран, а строка на карте соот­ ветствует коо рдинате у. Команда print (room _ map [у ] ) б содержит отступ из чет ы рех п робело в , указ ы вая P yt hon на то, что э т о фраг ­ мент кода, который долже н п ов т орить ц икл for О. П ри первом за п уске ц икла значение пе р еме н ной у рав­ но О, поэтому мент в print (room _ map [у]) выводи т первый эле ­ room _ map, который представляет собой список, со ­ держа щи й данные пе р вой ст р оки ка р т ы. Во вто р ой р аз з н аче н ие пе р емен н ой у р авно map [у]) 1, поэтому print (room _ выводит вторую строку. В ы пол н ение инструкции повто ря ется до тех по р , пока на экран не будут выведены все ceJ\IЬ списков из room _ map. YЧE&HAlll МИССИll No 1 Если на космической станции произойдет какая-нибудь ава­ рия, тебе может пригодиться возможность подать сигнал бед­ ствия. Создай программу, которая трижды выводит слово «Помогите!» Используй цикл. Если что-то пошло не так, вернись к листингу 3-2, который мы использовали для вывода данных карты. Просто измени текст, который печатает эта программа, и количество раз, ко­ торое он выводится. ЦИКЛ В ЦИКЛЕ Итак, тепе р ь нашу карту стало читать гораздо удобнее, но нам еще есть куда стремиться . П режде всего, запятые и скобки создают беспо рядок. Еще один минус заключается IЗ том , что мы не можем н и ч его сдела т ь с отдель н ыми ПОВТОРtlЙ 3А МНОЙ 87
стенами нашего помещения или пространством КО1\'1Наты в целом. Нам нужен какой-то способ доступа по отдельно­ сти ко всему, что находится в помещении, чтобы правильно отображать объекты. Для решения этой задачи нам понадо­ бится большее количество циклов. ИСПОЯЬЗОВАНИЕ ВЯОЖЕННЫХ ЦИКЯОВ ДЯll ПОЯУЧЕНИll КООРДИНАТ ПОМЕЩЕНИll В листинге 3-2 мы использовали цикл для извлечения по отдельности каждой строки карты. Нам потребуется еще один цикл, чтобы извлекать из строки каждую позицию, так мы сможем получить доступ ко всем объектам по отдельно ­ сти и будем полностью контролировать отображение эле1\>1ентов. Ты уже знаешь, что цикл может много раз выполнять один и тот же фрагмент кода. Но этим фрагментом кода мо­ жет быть и другой цикл, это явление называется вложенным цикло.м. Чтобы продемонстрировать работу такого цикла, выведем на экран координаты каждого места помещения. Отредактируй код таким образом, чтобы он соответствовал листингу 3-3: --пr CJr1ycк-- [1, [1, О for б О, О, О, 1, 1, 1, 1], 1] у in range (7): х in range (5): print ( "y=" , у, print () for С) О "х=" , х) Листинг 3-3. Вывод координат ii>Jфlf.jj Каждый астронавт знает, что в космосе его подстере­ гают опасности. То же самое можно сказать и о пробелах. Если отспzуп в цикле задан неправильно, не стоит ждаrпь от програ.имы нужного результата. Отст;т для первой команды print () С) должен состоять из восьми пробелов, только так она окаJ1сется частью циклах. Убедись, что последняя инспzрукция print () О находтпся на одном уровне со второй командой for б (не забудь о четырех 88 rЛАВАЗ
пробелах в качеспzве отступа), они должны принадлежать внеиmему циклу. Когда ты переходиzиь на новую строку, Pytlюn аюпо.мапzически устанавливает такой же отспzуп, как и в предыдуzи,ей строке, но если он тебе не нужен, ты .можеutь удалить его. Сохрани программу в файл с именем listingЗ-3.py и запу­ сти ее, выбрав команду меню Run ~ Run Module (Выпол­ нить ~ Выполнить модуль). На экране появится следующее: у= о х= о у= о х= 1 у= о х= у= о х= 2 3 у= о х= 4 у= 1 1 1 1 х= о х= х= 1 2 3 х= 4 у= у= у= у= у= у= у= 2 2 2 х= х= о х= 1 2 х= --пропуск-- В самом конце работы программы переменные будут иметь следующие значения: у = 6 х = 4. Мы настроили цикл у так, чтобы он повторялся семь раз О , по одному разу для каждого числа от О до 6. Это значение помещается н переменную у. По сравнению с предыдущим примером разница заключается в следую­ щем: в цикле у мы запускаем новы1':'1 цикл f or, который ис­ пользует переменную х и задает ей диапазон из пяти зна­ чений, от О до 4 б . В первый раз в цикле у значение переменной у равно О, а затем х поочередно принимает значения О, 1, 2, 3 и 4, при этом значение переменной у все еще равно О. Во второй раз в цикле у значение пере­ менной у равно 1. Мы начинаем новый цикл х, и х снова принимает значения О, остается равным 1, 2, 3 и 4, а значение переменной у 1. Внешний цикл продолжается до тех пор, пока значение переменной у не станет равно чение переменной х - 6, а зна­ 4. ПОВТОРtlЙ ЗА МНОЙ 89
СОВЕТ В качестве имен переменных в наших циклах мы использова­ ли у и х , однако ты можешь выбрать любые другие названия, хоть сосиски или яйца. На работу программы это никак не по­ влияет, хотя код может стать более тяжелым для восприятия. Поскольку мы работаем с х- и у -координатами, будет разум­ но взять эти буквы в качестве имен переменных. Чтобы понять, как работают циклы, обрати внимание на выходные данны е программы: в цикле х мы вводим зна­ чения для переменных у и х при каждом повторении цикла х €). Когда цикл х заканчивается, мы используем пустую строку О перед следующим повторением цикла у. При этом мы оставляем пустыми круглые скобки функции print (). Пустая строка появляется там, где цикл у заканчивает оче­ редной проход, а значения переменных х и у отображаются на экране благодаря внутреннему циклу х. Как можно заме­ тить, эта программа выводит координаты у и х для каждого места в помещении. НАВОДИМ ПОРЯДОК НА КАРТЕ Чтобы вывести нашу карту без каких-либо скобок и запя­ тых, в нашем цикле будут использоваться координаты. Отредактиру1":'1 свою программу так, как показано в листинге 3-4, чтобы вложенный цикл выглядел следую­ щим образом: --пµoпy(I<-- for у in range(7): х ·n ange(5): print (room_map[y] pr' nt () for [х], end= "" ) Листинг 3-4. Приводим нашу карту в порядок Сохрани программу в файл с именем listz:ngЗ-4.py и запу­ сти ее, выбрав команду 1\Iеню Run ~ Run Module (Выпол­ нить ~ Выполнить модуль). В оболочке должно появиться следующее: 90 rЛАВАЗ
11111 10001 10101 10001 10001 10001 11111 Карта выглядит чище и стала более понятной. Все рабо­ тает точно так же, как это было в листинге 3-3. Програм1\1а перебирает все координаты по отдельности. Цикл у по оче­ реди обращается к каждой строке, а внутри него цикл х проходит по всем позициям в этой строке. На этот раз вме­ сто координат мы выводим содержимое каждой позиции карты room _ map. В главе 2 я говорил, что можно убрать любо1'::'1 элемент карты, используя координаты в формате room _ map [к о ордината _ по _ оси _ у] [к о ордината _ по _ оси _ х]. То, как мы отформатировали данные, придает карте вид помещения: мы по порядку выписываем все числа из одной строки и переходим на следующую строку только в том слу­ чае, когда появляется новая строка карты (заново повторяет ­ ся цикл у). Инструкция end="" print () в цикле х заканчивается кодом (между кавычками пробел не ставится). Он предот­ вращает переход на новую строку после каждого числа. Иначе функция pr int () по умолчанию будет завершать каждый выводимый на экран фрагмент, добавляя в вывод код перехода на новую строку. Именно поэтому мы гово­ ри!\1 програJ\,1J\1е ничего не выводить (" ") в конце, чтобы все элементы из одного полного циклах (от О до 4) находились В ОДНОМ ряду. После вывода к;~ждой строки мы используем пустую команду print (),чтобы начать новую строку. Отступ этой команды равен четырем пробелам, поэтому она при­ надлежит циклу у и не является частью кода, который по­ вторяется в цикле х. Это означает, что данная функция вы­ полняется при каждом прохождении цикла у лишь один раз, после того, к;~к цикл х завершит печ;~ть одного ряд;~ чисел. ПОВТОРtlЙ ЗА МНОЙ 91
YЧE&HAlll МИССИll Заключительную команду print () No 2 предваряет отступ, состоя­ щий из четырех пробелов. Что будет, если сделать отступ в во­ семь пробелов? А если вообще его убрать? Запиши результаты для каждого из этих вариантов. Сколько раз в каж­ дом из этих случаев выполняется цикл и как разница в отсту­ пах влияет на вывод данных? РИСУЕМ ТРЕХМЕРНУЮ КОМНАТУ Теперь ты з наешь псе, что нужно, чтобы изобра з ить поме­ щение в трех ИЗJ\Iерениях. Из главы с помощью Руgаше Zero 1 ты узнал(а), как можно разместить изображения на экране. Давай совместим эти знания с приобретенными навыками извлечения данных из room _ map, чтобы наша карта выглядел а как н а стоящая , а не была простой схемой из нулей и единиц. Выбери команду меню File ~ New File (Файл ~ Python, а затем файл), чтобы создать новый фа1":.'1л в как показано в листинге 3-5. Ты Новый введи код можешь скопировать код room _ map из своей последней программы и использовать его при прочтении этой главы. room_ ma p = [ [ 1 , 1, 1 , 1 , 1] ' [1 , [1 , [1 , [1 , [1 , [1 , О WI DTH = 80 0 # О, О, О, 1] О, 1, О, о, О, о , О, о , о , 1] ' 1] ' 1] , О, О, О, 1, 1, 1, размер 1 1] ' 1] окна 6 HEIGHT = 8 0 0 top_left_x top_l eft_y С) 1 00 150 DEMO_OB JE CTS = [ima ge s. floor , image s. pi ll ar] r oom_ height = 7 r oom_width = 5 92 rЛ АВ А З
О def draw (): for у in range (room_height): for х in range (room_width): image_to_draw = DEMO_OBJECTS[room_map[y] [х]] screen.Ьlit(image_to_draw, (top_left_x + (х*ЗО), top_left_y + image_to_draw.get_height())) (у*ЗО) - Листинг 3-5. Код, позволяющий отобразить помещение в трехмерном реж и ме Сохрани программу в файл с именем listingЗ-5.py, кото­ рый должен находиться в директории escape, поскольку на­ шей программе понадобятся файлы из каталога Не нужно сохранять файл с кодом в папке images. i·mages, програl\1- ма должна находиться рядом с ней. Если ты еще не загру­ зил(а) файлы игры «Побег», обратись к разделу «Загрузка игровых файлов», который находится в начале этой книги. Поскольку листинг 3-5 требует наличия Pygame Zero, крой командную строку и введи инструкцию от­ pgzrun listingЗ - 5.py, чтобы запустить п рограмму. При необхо­ димости обратись к разделу «Запуск игры» во введении, чтобы получить рекомендации по запуску программ, рабо­ тающих с Pygame Zero, включая финальную версию игры «Побег». Код листинга 3-5 обращается к графическим файлам для создания изображения помещения игры «Побег» . На рис. 3.1 показана комната, в которой находится всего одна колонна. В нашей игре мы будем использоват ь упро­ щенную трехмерную перспективу, благодаря которой мы сможем видеть переднюю и верхнюю поверхности объекта. Объекты в передней и задней частях п омещения будут иметь одинаковый размер. В первой главе мы создавали симулятор выхода в откры ­ тый космос, и ты наверняка помнишь, как порядок рисова­ ния объектов определял их расположение относительно друг друга. В игре «Побег» в целом и в листинге 3-5 в част­ ности объекты будут рисоваться по схеме «Сначала дальняя часть комнаты, потом ближняя», это позволит нам придать комнате ощущение трехмерного пространства. Объекты, которые расположены ближе к игроку (сидящему ПОВТОРllЙ ЗА МНОЙ 93
за компьютером), как бы находятся перед теми предмета!\IИ, что расположены в задней части помещения. Задняя стена Колонна - Боковая стена Пол Передняя стена Рис. 3-1. Твое первое трехмерное помещение (слева) и то же самое помещение с обозначением объектов {справа) СОЗДАНИЕ TPEXMEPHOro ПОМЕЩЕНИll Как же работает программа listingЗ-5.py? Большая часть кода уже знакома тебе из глав 1 и 2. Переменные WIDTH О и HEIGHT б отвечают за размер окна, а функцию draw () мы используем для того, чтобы сообщить Pygame Zero, что именно следует вывести на экран О . Циклы у и х взяты из листинга 3-4 и позволяют нам обращаться к координатам каждого предмета в помещении. Вместо использования в функции range () чисел для указания количества раз повторения циклов у и х, мы доба­ вим в программу две новые переменные: и room _ width. room _ height Они обозначают рал·rер карты помещения и сообщают Python, сколько раз нужно повторять циклы. room _ height значение 10, цикл у повторится 10 раз и будет запускаться по одному разу на каждые 10 строк. Переменная room _ width определяет, сколько раз повторяется цикл х, так что Например, если мы присвоим переменной мы можем создавать помещения разной ширины. ii)B·IOI Если ширина и высопzа помещения окажутся больше фак­ тических данных в 94 rЛАВА З room _ тар, Pytlюn сообщит об оииtбке.
В листинге 3-5 программа использует два изображения из папки inzages: напольную плитку (jlooY.png) (pillaY.png), как показано на рис. 3.2. PNG (Порт<пивная сетевая графика) - и колонну это формат графических фай- лов, который использует Руgаше Zero. Благодаря PNG изображение l\IОЖет быть прозрачным, это поможет нам создать перспективу в нашей floor.png трехмерной игре. В противном слу- чае мы, например, не смогли бы уви- Рис. 3.2. Эти изображе- ~ , pillar.png ния мы будем использо- деть фоновыи пеизаж через просветы ме- вать для создания твоего жду листьев растения, а наш астронавт первого трехмерного был бы окружен квадратной каймой. помещения В функции draw () О мы работаем с циклами у их, что­ бы по очереди обращаться к каждой позиции на карте поме­ щения. Как я уже показывал ранее, мы можеl\I найти номер каждой позиции на карте, воспользовавшись командой room _ map [у] [х]. На той карте, которую мы использовали до сих пор, это число будет равняться либо 1 (колонна), либо О (если место пустует). На этот раз вместо вывода чис­ ла мы будем использовать его для поиска изображения эле­ мента в списке DEMO _ OBJECTS 0. Этот список содержит два изображения €): напольную плитку с индексом О, ко­ лонну с индексом в room _ map OBJECTS 1. Например, если в искомоГr позиции указано значение 1, из списка DEMO _ мы возьмем элемент с индексо!\'1, равным 1- это будет колонна. Ее изображение мы сохраним в переменной image _ to _ draw 0. Затем мы воспользуемся инструкщrе1'::'1 screen.Ыit(), чтобы вывести данное изображение в нужной точке экрана 0, для чего воспользуемся координатми х и у нужных пик­ селов. Эта инструкция состоит из трех строк, что сильно облегчает ее прочтение. Размер отступа во второй и третьей строках не имеет значения, поскольку они заключены в скобки screen. Ыit (). ПОВТОРllЙ 3А МНОЙ 95
УЗНАЕМ, rдЕ НЕО&ХОАИМО НАРИСОВАТЬ КАЖДЫИ ПРЕДМЕТ Чтобы понять, где будет нарисован каждый из предметов, что находятся в помещении, выполним вычисле­ ния в строке 0 . Чуть позже мы раз­ берем, как работают эти расчеты, но сначала я хотел бы рассказать о том, как спроектировал космиче­ скую станцию. Все изображения должны размещаться на сетке. Еди­ ницы измерения, которые будут использованы для создания изобра­ жений на компьютере, называются пикселал1и и представляют собой наи­ меньшую точку экрана. Каждый Р ис. 3.3. Узор из плиток, нал оженный поверх тво­ его первого помещения отдельно взятый квадратик сетки называется плиткой. Каждая плитка имеет размер, равный 30 пикселям в ширину и 30 пикселя!\1 в длину. Напольная плитка обладает те!\1и же п араметрами. Поскольку мы размещаем объекты в виде плиток, размер стула может быть равен 4 плиткам в длину и 4 плиткам в ширину, начиная от верхнего левого угла. На рис. 3.3 показано только что созданное нами помеще­ ние, поверх которого наложена сетка. Каждая напольная плитка и колонна имеют ширину, равную одно1':1 плитке. Колонна довольно высокая, поэто!\1у она занимает сразу три плитки: высота передней поверхности колонны равняется двум плиткам, а сама верхняя поверхность колонны занима­ ет еще одну плитку. Переменные top _ left _ х и top _ left _у хранят координаты, с которых мы начинаем отрисовывать первое изображение помещения в окне игры. В это1":'1 главе мы не будем менять значения этих переменных. Рисовать изо­ бражение мы начнем с позиции, где значение переменной х равно 100, а у равно 150, чтобы наше помещение было ограничено некоторым пространством. 96 rЛАВА З
Чтобы определить местоположение будущих стены или пола, нам нужно превратить позиции на карте (на­ при1\1ер, в диапазоне от О до 4 по оси х) в позиции пиксе­ лей в окне. Площадь каждой плитки составляет селей, умножаем число из циклах на к положению top _ left _ Python top _ left _ символ х равно * что равняется вывается исходя из 100. обознача­ 100, му первое изображение отрисовывается исходя из (0 * 30), пик­ его х, тем самым получая коорди­ нату х для этого изображения. В ет умножение. Значение 30 квадратных 30 и прибавляем поэто­ 100 + Второе изображение отрисо­ 100 + (1 * 30), что равняется 130, то есть на одну плитку правее предыдущей. Третье изобра­ жение отрисовывается из расчета няется 100 + (2 * 30), что рав­ 160. Этот интервал гарантирует, что изображения будут идеально пристыкованы друг к другу. Позиция у рассчитывается аналогичным образом. Мы используем top _ left _ у в качестве начальной позиции по вертикали и добавляеr-.1 к ней значение, равное у * 30, чтобы изображения сходились идеально. Здесь, в отличие от top _ left _ х, мы вычитаем высоту рисуемого нами изображения, тем самым гарантируя, что внизу изображе­ ния будут выровнены в одной и той же точке. В результате высокие объекты могут выходить за пределы пространства, тем самым заслоняя пейзаж или напольную плитку, находя­ щиеся за ними, что сделает изображение помещения трех­ .мерным. Если не выровнять изображения внизу, они будут выровнены вверху, а это «сломает» 3D-эффект. Например, второй и третий ряды напольно1":"1 плитки попросту перекро­ ют поверхность задней стены. В настоящей игре « Побег » самые высокие колонны будут использоваться только по краям комнат. В центре же поме­ щения они будут выглядеть немного странно, особенно при соприкосновении с дальней стеной. Немного позже мы до­ бавим тени, и тогда объекты, расположенные в центре поме­ щения, перестанут выглядеть так, словно они плавают в пространстве. В общем, моделирование трехмерной пер­ спективы - штука непростая. ПОВТОРllЙ ЗА МНОЙ 97
YЧE&HAlll МИССИll No 3 Теперь, когда ты умеешь создавать трехмерные помещения, попробуй настроить карту так, чтобы изменить планировку по­ мещения, добавив новые колонны или предметы. Ты можешь отредактировать список room _ map, чтобы добавить несколь­ ко строк или столбцов на карту. В этом случае не забудь изме­ нить значения переменных room _ height и room _ width . Кроме того, предлагаю тебе попробовать создать более широкое помещение и сделать в нем дверной проем, заменив единицы, используемые для основных элементов, на О . В фи­ нальной версии игры «Побег» каждый дверной проем будет со­ стоять из трех пространств. Чтобы все выглядело красиво, спроектируй помещения с шириной и высотой равными нечет­ ным значениям, так ты сможешь поставить дверь в центр стены. На рис. 3.4 показана созданная мной комната с шириной и высотой, равными 9. Если хочешь, можешь взять за основу мой дизайн. Я добавил сетку, чтобы упростить работу со спис­ ком room _ map. Колонны стен приподняты над полом на две плитки, поэтому высота сетки составляет 11 плиток. Чтобы по­ нять, где расположить колонны, смотри на их низ, а не верх. В конце главы находится весь необходимый для создания этого помещения код. Рис. 3.4. Один из вариантов дизайна помещения 98 rЛАВАЗ
rотов пи ты к nonETY? Отметь следующие пункты, если чувствуешь, что материал этой главы тебе понятен. D Цикл for повторяет фрагмент кода заданное количество раз. D D Функция Функцию range () задает последовательность чисел. range ()можно использовать, чтобы задать коли­ чество повторов цикла for. D Наличие двоеточия в конце строки с for очень важно . D Чтобы Python понимал, какие именно строки относятся к циклу, они должны иметь отступ в четыре пробела. D D Цикл в цикле называется вложенным циклом. Изображения следует выравнивать по нижней части, чтобы с ПОJ\ющью объектов, приподнятых над полом, создать иллюзию D 3D. Переменные room _ height и room _ width игры «Побег» хранят размеры комнаты и используются для настроики цикла, внутри которого и отрисовывается это помещение. ПОВТОРllЙ 3А МНОЙ 99
ОБСУЖДЕНИЕ МИССИИ Ниже представлены ответы на учебные миссии из этой главы. УЧЕБНАЯ МИССИЯ № for у in range (З): print ( " Помогите!' 1 ) УЧЕБНАЯ МИССИЯ № 2 Если не поставить отступ в последней команде pri n t (), программа не будет ее повторять. Вместо этого последняя команда p rin t о выполнится только один раз и только после завершения обоих циклов. В результате все выходные данные окажутся в одной строке, поскольку программа не добавит разделительные строки между рядами чисел. Если команду prin t ( )будут предварять восемь пробелов, она станет частью цикла х . Это значит , что инструкция будет выпол­ няться всякий раз, когда программа будет печатать число, соот­ ветственно, все числа будут располагаться на отдельных строках. УЧЕБНАЯ МИССИЯ № Э Ниже приведены данные для дизайна помещения, изображенного 3.4. Необходимо изменить значение переменной r oo m _ he i ght на 9 и переменной room _ widt h - на 9. на рис . room_map = [ [1, 1, 1, 1, 1, 1, 1, 1, 1]' [1, 1, о, о, о, о, о, 1, 1]' [1, о, о, о, о, о, о, о, 1]' [1, о, о, о, о, о, о, о, О]' [1, о, о, 1, о, о, о, О]' [1, о, о, о, 1, 1, о, о, о, О]' [1, о, о, о, о, о, о, о, 1]' 1]' 1] [1, о, о, о, о, о, о, о, [1, 1, 1, о, о, о, 1, 1,
СОЗДАЕМ КОСМИЧЕСКУЮ СТАНЦИЮ В этой: главе мы создадим карту для нашей: космической: станции на Марсе. С помощью простой: программы Обзор, код которой: мы изучим в этой: главе, ты сможешь взглянуть на каж­ дую из комнат станции и начнешь ори­ ентироваться в пространстве. Для работы с картографическими данными и отобра­ жением помещения в трехмерном виде нам вновь пригодятся списки, циклы и методы, изученные в главах 1-3. СОЗДАЕМ КОСМИЧЕСКУЮ СТАНЦИЮ 101
АВТОМАТИЗАЦИJI ПРОЦЕССА СОЗДАНИJI КАРТ Проблема, касающаяся текущих данных в списке map, room _ заключается в том, что это довольно большой кусок информации. Игра «Побег » состоит из 50 добавлять данные в список для каждого место­ room _ map локаций. Если положения отдельно, это отнимет очень много времени и будет крайне неэффективно. Например, если помещение состоит из плиток размероr.1 состоит из 4050 81 9 х 9, значит, вся ко1111ната элемента, что в сумме по всем локациям дает элементов. Один лишь вывод всех этих данных занял бы в этой книге 10 страниц. Обрати внимание, что большая часть элементов списка повторяется: О мы обозначаем пол и выходы, а Из главы 3 1- стены. ты знаешь, что мы можем использовать циклы, чтобы эффективно управлять повторяющимся кодом. Эти знания понадобятся нам для создания программы, которая на основании данных о размере помещения и расположении выходов будет автоматически генерировать данные для списка room _ map. КАК РА&ОТАЕТ АВТОМАТИЧЕСКИЙ КАРТОfРАФ Игра « Побег » будет работать следующим образом: когда игрок оказывается в пш.лещении, наш код получает сведения об этой комнате (ее размер и расположение выхода) и пре­ образует их в данные для списка room _ map room _ map. Список состоит из столбцов и строк, которые обозна­ чают пол, стены и пустые пространства, в которых распола­ гаются выходы. Для создания комнаты с полом и стенами в нужных местах нам и понадобятся данные На рис. 4.1 room _ map. изображена карта космической станции. Я буду называть каждую локацию помещением, хотя факти­ чески номера 1-25 обозначают поверхность планеты, огра­ ниченную территорией станции, так что это пространство было бы правильнее назвать садом. Номера с 26 по 50 - это помещения, расположенные в самой космической станции. 102 rЛАВА4
!_ Внутренняя планировка пред- ставляет собой типичный лаби- 4 ринт со множеством коридоров , 6 тупиков и помещений для иссле- 10 11 12 13 14 15 собственных карт, старайся еде - 16 17 18 19 20 лать как можно больше извили- 21 22 23 24 25 стых путей и закоулков, которые 26 27 28 29 30 31 32 33 34 35 чаях, когда карта не очень боль- 36 37 38 39 40 шая. Обязательно вознагради 41 42 43 44 45 46 47 48 49 50 дования. Работая над создание!\1 интересно исследовать. Я рекомендую так делать даже в тех слу- игроков за их стремление к исследованию нового, для этого хоро- шо подойдет полезный или кра - Р ис. 4.1. Корта космической сивый п редмет в конце каждого станции коридора. Как правило, игроки, исследуя мир, начинают двигаться слева направо, поэтому наш персонаж будет начинать свое путешествие из левой ч а ­ сти карты, а именно из помещения 31. Снаружи здания игроки могут бродить где угодно, одна­ ко забор не позволит им покинуть территорию станции (или сойти с игровой карты). Выйдя за пределы ограничен­ ного пространства космической базы, игроки будут чув­ ствовать себя гораздо свободнее, ведь там можно прогулять­ ся в открытом космосе. Играя в финальную версию игры «Побег », ты всегда мо­ жешь обратиться к карте, однако, на мо1':'1 взгляд, гораздо ин­ тереснее исследовать пространство без нее или же создав свою собственную. На этой карте не показано, где находят­ ся двери, поэтому игроки не смогут попасть в некоторые помещения, пока не найдут ключи от них. СОЗДАНИЕ КАРТОf РАФИЧЕСКИХ ДАННЫХ Итак, давай приступим к созданию картографических дан­ ных. Все пшлещения нашей космической станции будут объединены в сеть, поэтому нам достаточно знать только СОЗДАЕМ КОСМИЧЕСКУЮ СТАНЦИЮ 103
расположение выхода с одной стороны стены. Например, выход в п р авой части помещения помещения 32 31 и выход в лево1':'1 части будут представлять собой один и тот же двер­ ной проем, соединяющий два помещения, поэтому мы не будем указывать этот выход отдельно для каждой ком ­ наты. Наличие выхода в помещении будем указывать вверху или справа. Программа сама определит, имеется ли внизу или слева выход (скоро ты поймешь почему). Благодаря такому подходу можно не опасаться разнобоя в данных и что какие - то выходы 1'.·югут исчезнуть после того, как игрок пройдет через дверной проем. Пройдя в одну сто­ рону через ту или иную дверь, ты всегда с легкостью смо ­ жешь вернуться. Для каждого помещения на кар т е необходиJ\lо указать следующие данные. • • Краткое описание комнаты. Высота в плитках, которая представляет собой раз!\-·1ер ком­ наты сверху вниз на экране. (Эта информация не имеет никакого отношения к расстоянию от пола до потолка.) • Ширина в плитках, равная размеру помещения слева направо. • • Есть или нет выход наверху Есть или нет выход справа (True или False). (True или False). СОВЕТ Значения Tr ue и False называются логическими значения­ ми. В Pythoп эти слова пишутся с прописной буквы, и их не нужно обособлять кавычками, поскольку они не являются строками. Мы называем единицу измерения размера помещения плиткой, потому что она имеет такой же размер, как и на ­ польная плитка. В главе 3 мы говорили, что плитка является основной единицей измерения всех объектов. Например, один предмет в помещении, такой как стул или шкаф, зача­ стую будет равен размеру одной плитки. В главе 104 rЛАВА4 3
(см. рис. 3. 1 и листинг 3-5) мы составили карту помещения, которая представляет собой семь строк с пятью элементами внутри, таким образом помещение будет иметь высоту п семь плиток и ширину в пять плиток. Н аличие помещений разных размеров вносит разнообра­ зие в карту: некоторые комнаты можно сделать узкими, как коридоры, а некоторые наоборот - просторными, как залы для отдыха. Чтобы уместиться в игровом окне, максималь­ ный размер п омещения не должен превышать в высоту и 25 15 плиток плиток в ширину. Обрати внимание, что большие помещения или помещения с большим количе­ ством предметов могу т вызвать «Подписание» старых ком­ пьютеров. Рассмотрим пример кода для помещения помещение высотой в 13 ходом вверху, но без выхода справа (см . [ " Шлюз " , 13, 5, 26: это узкое 5 плиток с вы ­ карту на рис. 4.1). плиток и шириной в True , False ] Мы задаем пш.лещению название (или описание), высоту и ширину соответственно, а также значения True/False для обозначения наличия выхода сверху и справа. В нашей игре в каждой из стен может быть только один выход, и он будет автоматически располагаться посередине стены . Когда програм!\,1а прочитает данные мещения 27, room _ map для по­ она сначала проверит коrv·1нату по соседству, 26, чтобы узн:пь, есть ли там выход справа. И поскольку в пра­ вой части помещения что в помещении 27 26 выхода нет, программа будет знать, выхода слева нет. Мы будем хранить списки данных для каждого помеще­ ния в общем списке GAME _ МАР. СОДЕРЖИМОЕ СПИСКА GАМЕ _ МАР Выбери команду меню File ~ New File (Файл ~ Н овый файл), чтобы создать новый файл. Введи код из листинга 4-1, чтобы начать строительство кос!\lической станции. Сохрани свой список в файл listing4-1.py. Не забудь, что он должен лежать (как и другие твои про­ граммы, над которыми ты работаешь в этой книге) в папке СОЗДАЕМ КОСМИЧЕСКУЮ СТАНЦИЮ 105
escape, чтобы каталог с изображениями находился в доступ­ ном для нашей игры месте (см. «Загрузка игровых файлов» во введении). СОВЕТ Регулярно сохраняй свою работу. Как и во многих других при­ ложениях, в IDLE ты тоже можешь использовать сочетание кла­ виш Ctrl+S для сохранения файла. # # # - Побег Шана Графика на языке Python / www . sean . co . uk приключения Макмануса Рафаэля Пимента import time, random, math ################ ## ПЕРЕМЕННЫЕ ## ################ WIDTH = 800 # HEIGHT = 800 # переменные О PLAYER_NAМE б размер для окна описания игроков = " Шан " # измени на собственное имя! FRIENDl_NAМE " Кэрол " FRIEND2_NAМE " Лео " current room 31 # top_left_x top_left_y # # измени измени начальное на на имя имя своего еще помещение друга! одного своего друга! = 31 100 150 €) DEMO_OBJECTS = [images.floor, images.pillar, images.soil] ############### ## КАРТА ## ############### 0 МАР _WIDTH МАР МАР 0 HEIGHT SIZE = 5 = 10 МAP_WIDTH * GАМЕ_МАР [[ " Помещение предметы " , О, О, МАР HEIGHT О - здесь False , False ]] хранятся неиспользуемые outdoor_rooms = range (l, 26) @ for planetsectors in range (l, 26): #rooms 1 to 25 are generated # here 106 rЛАВА4
append ( [ " Пьmьная True ] ) GАМ Е _МАР . б GАМЕ_МАР планеты " , 13 , 13 , True , += [ #[ " Название # поверхность Выход помещения ", высота , ширина , выход вверх? , справа?] 1 3 , 5 , True , False ] , #помещение 26 13 , 13 , False , False ] , #помещение 27 [ " Система управления посадочным модулем " , 9 , 13 , False , True ], # помещение 28 [ " Обсерватория " , 9 , 15, False , False ] , #помещение 29 [ " Санузел " , 5 , 5 , False , False ], #помещение 30 [ " Ш~юзовой отсек" , 7, 1 1 , True , True ] , #помещение 31 [ " Проход слева " , 9 , 7 , True , False ] , #помещение 32 [ " Проход справа " , 7 , 1 3 , True , True ] , #помещение 33 [ " Библиотека " , 13 , 1 3 , False , True ] , #помещение 34 [ " Теплица " , 13 , 13 , True , False ], #помещение 35 [ " Каюта " + P LAYER_NAМ E + " а " , 9 , 11 , False , False ], # помещение 36 [ " Западный коридор " , 1 5 , 5 , True , True ] , #помещение 37 [ " Переговорная " , 7 , 1 3 , False , True ] , #помещение 38 [ " Комната отдыха" , 1 1 , 13 , True , False ], #помещение 39 [ " Центр управления полетами " , 1 4 , 14 , False , False ], # помещение 40 [ " Лазарет " , 12 , 7 , True , False ], #помещение 41 [ " Западный коридор " , 9 , 7 , True , False ], #помещение 42 [ " Диспетчерская " , 9 , 9 , False , True ], #помещение 43 [ " Инженерный отсек " , 9 , 1 1, False , False ], #помещение 44 [ " Секретный проход в ЦУП " , 7 , 7 , True , False ] , # помещение 45 [ " Каюта " + FRI E N Dl _N AМE, 9 , 1 1, True , True ] , # помещение 46 [ " Каюта " + FRIEND2_NAМE, 9 , 11 , True , True ] , # помещение 47 [ " Насосная " , 13 , 11, True , False ] , #помещение 48 [ " Каюта главного научного сотрудника " , 9 , 7 , True , True ] , # помещение 49 [ " Робототехническая мастерская " , 9 , 11 , True , False ] # помещение 50 [ " Шлюз " , [ " Лаборатория " , ~ # простая проверка работоспособности карты для проверки ввода # данных assert len ( GAМE_МAP)-1 == МAP _ SIZE , " Размер карты и GАМЕ_МАР не совпадают " Листинг 4-1. Код GАМЕ _МАР Рассмотрим подробнее код, который генерирует карт у помещений. И м ей IЗ IЗиду, что по мере создания игры « По­ бег » мы будем прод олжать вносить р а зличные и з менения СОЗДАЕМ КОСМИЧЕСКУЮ СТАНЦИЮ 107
в программу. Чтобы тебе было легче ориентироваться в ней, я буду отI\1ечать различные разделы следующим образом: ################ ## ПЕРЕМЕННЫЕ ## ################ Символом общая Python # мы обозначаем комментарий, тем самым со­ о необходимости игнорировать все, что нахо­ дится после данного знака в это1':'1 строке. Работа игры не за­ висит от наличия комментариев в ее коде. Они нужны програi\1мисту, чтобы сориентироваться IЗ программе, на­ пример понять, где ты находишься или где необходимо до­ бавить новые инструкции в процессе улучшения програм­ мы. Я нарисоIЗал рамки, используя символы #, которыми обозначаются коммен т арии, чтобы при прокрутке кода за­ головки бросались в глаза. На космической станции находятся три астронавта. В коде О можно дать им любые другие имена. Измени зна­ чение переменной PLAYER _ NAME на сIЗое собственное имя и добавь имена двух друзей в качестве значений переменных FRIENDl _ NAME и FRIEND2 _ NAME. На протяжении всего кода мы будем использовать эти значения везде, где потре­ буется указать имя одного из твоих друзей: например, у каждого астронавта есть своя каюта. Эти переменные нужно настроить прямо сейчас, потому что в дальнейшем мы будем использовать их для некоторых описаний комнат. Итак, кого ты IЗозьме ш ь с собой на Марс? Помимо этого программа содержит некоторые перемен­ ные, которые нам понадобятся в конце этой глаIЗы, чтобы нарисовать комнату: переменные left _ у 6 top _ left _ х и top _ указывают на то, с чего следует начинать созда­ ние помещения; список DEMO _ OBJECTS содержит изобра­ жения €) . Сначала мы настраиваем переменные, содержащие высо­ ту, ширину и общий размер карты, измеряемый в плит ках О . Далее мы создаем список GAME _МАР 0 и передаем ему данные комнаты О: это помещение предназначено для хранения предметов, которые пока не используются в игре, например если игрок их еще не обнаружил или не создал. 108 rЛАВА4
Конечно, эта комната ненастоящая, и игрок не сможет туда попасть. Затем мы используем цикл 0 , чтобы добавить одни 25 «помещений», располо­ и те же данные для каждого из женных на поверхности планеты и составляющих террито­ рию комплекса. Функция нам код 25 range (1, 26) выполнит нужный раз. Числа в скобках задают границы: начинаем мы с первого числа, и постепенно доходим до второго, при­ бавляя единицу при каждом прохождении цикла (не забудь, что функция range () не использует последнее число в ин­ тервале). При каждом повторе цикла программа добавляет одни и те же данные в конец списка GAME _МАР, поскольку все «Помещения», находящиеся на поверхности планеты, имеют одинаковый размер, и в каждом из них имеются вы­ ходы во всех направлениях. Данные для каждого «по:меще­ ния» выглядят так: [ " Пыльная поверхность планеты " , 13, 13, True , True ] По завершении цикла в списке GAME _МАР будет нахо­ диться помещение О, а также все данные «поверхности пыль­ ной планеты» для «помещений» от ли диапазон outdoor _ rooms от 1 до 25. Мы также указа­ 1 до 25, который означает номера «помещений». Этот диапазон понадобится в тех слу­ чаях, когда нам будет нужно узнать, где находится комната - внутри станции или снаружи. И, наконец, мы «Вносим» в список GAME _МАР б поме­ щения с ноr-.1ерами 26-50. Для этого мы воспользуемся сим­ волами +=,чтобы добавить новый список в конец списка GAME _МАР. Все эти комнаты различаются между собой, так что мы должны по отдельности ввести данные каждой из них. Я уже говорил о помещении 26: данные содержат название комнаты, ее высоту и ширину, а также наличие вы­ ходов вверху и справа. Каждый фрагмент информации о по­ мещении представляет собой список, поэтому данные за­ ключены в квадратные скобки. В конце каждого фрагмента (кроме последнего) следует ставить запятую, чтобы отде­ лить его от соседа. В конце каждой строки я написал ко:м­ ментарий, в котором указан номер кш..1наты, это пригодится тебе в процессе создания игры. РекоN1ендуется СОЗДАЕМ КОСМИЧЕСКУЮ СТАНЦИЮ 109
комментировать свой код так, чтобы при его просмотре можно было легко понять, зачем он нужен. Пш,.-1ещения 46 и 47 добавляют к описанию комнаты пе­ ременные FRIENDl _ NAME и FRIEND2 _ NAME. Так что те­ перь в твоем распоряжении есть два помещения с названия ­ ми по типу «Каюта Кэрол». Пусть эти комнаты носят имена твоих друзей 0 . Символ + можно использовать не только для сум1\н1рования чисел или объединения списков, но и для сложения строк. В конце листинга с помощью функции 4-1 мы выполняем простую проверку assert (), благодаря которой мы смо­ жем проверить, что инфорJ1.1ация карты имеет смысл 0 . Мы проверяем, совпадает ли длина списка GAME _МАР (количе­ ство помещений в данных карты) с размером карты, кото­ рый мы рассчитали в строке О , умножив ширину карты на ее высоту. Если данные не совпадают, это означает, что нам чего-то не хватает, или же наоборот, данных слишком много. Из длины GAME - МАР мы ДОЛЖНЫ вычесть единицу, по­ скольку этот список включает в себя комнату О, которую мы не учли при расчете размера карты. Конечно, эта проверка не выявит все ошибки в коде, но она может подсказать тебе, что при вводе данных карты была пропущена строка. Везде, где это возможно, я постараюсь вставить простые тесты, по­ добные этому, так ты сможешь своевременно замечать ошибки, допущенные при вводе программного кода. ТЕСТИРОВАНИЕ И ОТЛАДКА КОДА Из командной строки перейди в папку оттуда программу с помощью команды listing4-1.py. escape и запусти pgzrun Перед тобой должно открыться пустое игровое окно . Ожидаемый результат, ведь мы пока не сде ­ лали в нашей программе ничего, кроме установки некото­ рых переменных и создания списка. Но если в процессе набора листинга ты что - то сделал(а) неправильно, в окне командной строки может появиться сообщение об ошибке. В этом случае перепроверь следующую информацию. 110 rЛАВА 4
• Правильно ли расставлены кавычки? Текстовые строки выделяются зеленым цветом в окне Python. Обрати внима­ ние на большие области, выделенные зеленым цветом. Они указывают на то, что в конце строки нет кавычки. Если опи­ сания помещениJ':'1 выделены черным цветом, это означает, что кавычки нет в начале строки. В каждом из этих случаев программа указывает на отсутствие кавычек. • Круглые и квадратные скобки расставлены в нужных J1v1ecтax? В этом листинге элементы списка заключены в ква­ дратные скобки, а круглые скобки (изогнутые скобки) используются для функций, таких как Фигурные же скобки • range () и append (). {... } вообще не используются в Pytlюn. Не пропущены ли какие-либо скобки? Простой способ про­ верить это - подсчитать количество открывающих и закры­ вающих скобок каждого вида. Любая открывающая скобка должна иметь пару • - закрывающую скобку той же формы. Помни, что закрывать любые виды скобок необходимо в порядке, обратном то11лу, в котором ты их открывал(а). Если первой была открыт;~ круглая скобка, ;i потом кв;~драт­ ная, то закрыть сначала нужно квадратные скобки, а затем уже круглые. Правильно: • ( [".]). Неверно: ( [".)]. Правильно ли расставлены запятые? Помни, что в списке по1\1ещений GAME _МАР должна стоять запятая после каж­ дой закрывающей квадратной скобки (за исключением последней), тем с;~мым отделяя строки списка друг от друга. СОВЕТ Почему бы не попросить друга помочь с созданием игры? Программисты часто работают вдвоем, чтобы делиться инте­ ресными идеями, и, главное, чтобы код проверяли две пары глаз. Ты тоже можешь вместе с другом или подругой по очере­ ди печатать код! СОЗДАЕМ КОСМИЧЕСКУЮ СТАНЦИЮ 111
СОЗДАНИЕ ПОМЕЩЕНИЙ НА ОСНОВЕ ДАННЫХ Теперь в нашем списке GAME _ МАР находится карта косми­ ческой станции. Следующий шаг добавление функции, - которая берет данные текущего помещения из списка GAME _МАР и переносит их в список room _ map. Послед­ ний будет использоваться в игре «Побег», чтобы увидеть, что находится в различных точках помещения. В списке room _ map хранится информация о комнате, в которой в данный I\Юмент находится игрок. Когда игрок входит в другое помещение, мы заменяем данные в room _ map картой иового помещения. Чуть позже я расскажу, как можно добавить декорации и реквизит в список map, room _ чтобы у игрока появились предметы, с которыми он сможет взаимоде~":1ствовать. Данные map (), room _ map создаются функцией как показано в листинге Введи код из листинга 4-2 generate _ 4-2. в конец листинга 4-1. Выделен­ ный сер ы м цветом код показывает, где заканчивается ли­ стинг 4-1. Убедись, что все отступы указаны правильно. От­ ступ определяет, какой функции принадлежит код или generate _ map (). Кроме Python принадлежность кода к какой-либо из команд: if или for. Сохрани программу в фа1":'1л с именем listing4-2.py и вос­ пользуйся командой pgzrun listing4-2 .ру, чтобы запу­ - get _ floor _ type () того, это позволяет указать стить программу и проверить, не всплывают ли в окне ко­ мандной строки сообщения об ошибках. ij)jфiOI Не запускай прямо сейчас новую програ.мму с кодо.J.1, указанныАt в листинге листинг 4-2 4-2. Убедись, что ты добавил(а) в конец листюиа 4-1. По .J.1epe чтения этой книги ты будеиiь совершенствовать свою программу, чтобы в результате получилась игра «Побег». пропуск # # г pocтd>f 112 rЛАВА4 ВВС'ДН. rро::.ос:ркс~ Дrс ~ НЬ1Х рdбо Г.)<',JО '('ен. )r'':'И I<'с!рты длq ~роверки
assert len (GАМЕ_МАР)-1 -= "РазмС'р МAP __ SIZE, кар'I r и GАМЕ_МАР не совпадаот" ############### ## ПОМЕЩЕНИЕ ## ############### О б €) О 0 @ б е def get_floor_type () if current room in outdoor rooms : return 2 # грунт else : return О # кафельный пол def generate_map () : # Эта функция создает карту для текущего помещения , # используя данные о комнате , декорациях и реквизите. global room_map , room_width, room_height , room_name , hazard_map global t op_left_x , top_left_y , wall_transparency_frame room_ data = GAМE_МAP[current _room] room_ name = room_ data[O] room_h eight = room_data[l] room_ width room_ data[2] floor _ type = get_ floor _type () if current room in range (l, 21) bottom_edge = 2 # грунт side _ edge = 2 # грунт if current room in range (21 , 26) bottom_edge 1 # стена side _edge = 2 # грунт if current room > 25 : bottom_edge 1 # стена side _edge = 1 # стена # Создание верхней строки карты помещения. room_map=[[side_ edge] * room_ width] # Добавление средних строк карты помещения (стена , # для заливки по ширине , стена) . for у in range (room_height - 2) : room_map . append([side_edge]+ [floor_type]* (room_ width - 2) + [side_ edge]) # Создание нижней строки карты помещения . room_map.append([bottom_edge] * room_width) пол # Добавление дверных проемов . midd l e_row = int (room_height / 2) middle_ column = int (room_ width / 2) if room_data[4] : # Если выход в правой части помещения room_ map [midd l e _ row ] [ room_ width - 1] = floor _ type room_map [middle_row +l ] [ room_width 1] floor_type room_map[middle_row-1] [ room_width - 1] = floor_type СОЗДАЕМ КОСМИЧЕСКУЮ СТАНЦИЮ 113
~ if current_roorn% МAP_WIDTH != 1: # Если помещения room_to_left = GAМE_МAP[current_room - 1] # Если в помещении слева есть выход справа , # добавляем выход слева в это помещение if room_to_left[4]: room_rnap[middle_row] [О] = floor_type room_map[middle_row + 1] [0] floor_type room_map[middle_row - 1] [0] = floor_type if 1]) room_data[ЗJ: #Если выход в слева нет верхней части помещения room_map[O] [middle_column] = floor_type roorn_map[O] [middle_colurnn + 1] floor_type room_map[O] [middle_column - 1] = floor_type if current room <= МАР SIZE - МAP_WIDTH: # Если помещения # снизу нет room_below = GAМE_МAP[current_roorn+МAP_WIDTH] # Если в помещении внизу есть выход вверху , # добавляем выход внизу в это помещение . if room_below[З]: room_map[room_height-1] [middle_column] floor _type roorn_rnap[roorn_height-1] [rniddle_colurnn + 1] floor_type roorn_rnap[roorn_height-1] [rniddle_colurnn 1] floor_type Листинг 4-2. Код room _ map Создать игру «Побег» и сделать собственные игровые карты ты можешь и без понимания, как работает код room _ map. Однако если тебе все же интересна данная тема, читай дальше, а я постараюсь рассказать обо всем как можно более подробно. КАК РА&ОТАЕТ КОД, СОЗДАЮЩИЙ КОМНАТУ Давай вспомним, что должна делать функция map ().В generate _ зависимости от высоты и ширины помещения, а также расположения выходов, мы хотим, чтобы она создавала карту помещения, которая будет выглядеть примерно так: [ 1, 1, [ 1, О, [ 1, О, [ 1, о, [ 1, о, [ 1, О, [ 1, 1, 1, 1, О, О, О, 1, 1, 1, О, О, О, О, о, о, о, о, О, о, О, О, о, О, О, о, 1]' 1]' 0]' о, о, о, о, о, о, о, о, О]' о, о, о, о, о, о, о, о, О]' о, О, О, О, О, О, О, О, 1, 1, 1, 1, 1, 1, 1, 1, 1]' 1] ] 114 rЛАВА4
Так выглядит на карте помещение номер 31. Именно здесь начинается игра. Высота помещения составляет ток, ширина 7 пли­ плиток. Также в нем расположены выходы 11 - вверху и справа. Свободные участки пола (и выходы, разме­ щенные в стенах) отмечены числом О. Стены, возведенные по периметру помещения, отмечены числом 1. На рис. 4.2 показано одно и то же помещение в виде сетки с обозначен­ ными индексами для столбцов. Найти их можно вверху и слева. о о 6 4 9 10 1 1 1 1 о о о 1 1 1 1 1 о о о о о о о о о 1 1 о о о о о о о о о о 1 о о о о о о о о о о 4 1 о о о о о о о о о о 5 1 о о о о о о о о о 1 6 1 1 1 1 1 1 1 1 1 1 1 Рис. 4.2. Сетка, представляющая помещение 31; ц ифрой 1 обозначены колон ны, выстроенные вдоль стен, О - не занятое простр анство Номер помещения, в котором на данный мо1\1ент нахо­ дится игрок, хранится в переменной current _ room. Ее мы настраивали в разделе ПЕРЕМЕННЫЕ (см. листинг 4-1). Работа функции generate _ map () начинается со сбора данных о помещении для текущей комнаты из списка GAME _МАР б. После чего программа вносит эти данные в список room data. Вспомни процесс настройки GAME _МАР. Информация в [ " Шлюз " , room _ data теперь будет выглядеть примерно так: 13, 5, True , False ] Такой формат списка позволяет нам задать название по­ мещения, обратившись к первому элементу из этого списка (индекс 0). Взяв следующие за ним элементы, мы можем найти высоту помещения по индексу 1 и ширину по индек­ су 2. Функция generate _ map () хранит в переменных room _ height и room _ width информацию о высоте и шир ине . СОЗДАЕМ КОСМИЧЕСКУЮ СТАНЦИЮ 115
СОЗДАНИЕ ПОМЕЩЕНИЯ ПРОСТОЙ ФОРМЫ Следующий шаг - настройка материалов, которые мы будем использовать для строительства помещения, и созда­ ние с их помощью комнаты простой формы. Выходы созда­ дим позже. При работе с каждым из по1'.1ещеннй мы будем использовать три элемента. • Тип пола, который хранится в переr-.ленной floor _ type. Внутри космической станции мы используем напольную плитку (обозначенную О (обозначенный • в 2 13 room _ map), room _ map). а снаружи - грунт Тип края, который отображается в каждом элементе, располо­ женном на границе помещения. Для внутренних помещений это колонны, представленные 1. Для внешних «помещений» это грунт. • Тип нижнего края, который представляет собой стену вну­ три станции и, как правило, грунт снаружи. Нижняя стро­ ка, находящаяся за пределами помещения - поверхности планеты, то есть место, где происходит пересечение с кос­ мической станцией, является особым случаем, поскольку здесь 13ид1ы стена станции. Вот почему тип нижней грани­ цы изображения - bottom _ edge 4.3). это колонна, распо­ ложенная вдоль стены (см. рис. «Помещение» «Помещение» поверхности планеты, Помещение внутри поверхности планеты граничащее с космической станцией космической станции Рис. 4.3. Для краев и нижних краев помещений используются различные материалы, в зависимости от того, где находится помещение. (Обрати внимание на то, что астронавта и дополнительных декораций в игре пока нет.) Для определения типа пола функцию 13 get _ floor _ type () помещении мы используем О. Как ты уже, наверное, мог(лt:t) заметить, функции с помощью оператора 116 rЛАВА4 ret urn
могут возвращать какие-либо данные в другие части про­ граммы. Функция get _ floor _ type () проверяет, находится ли current _ room в диапазоне outdoor _ rooms. так, функция возвращает число 2, которое обозна­ значение Если это чает марсианский грунт. В противном случае программа возвращает число О, которое обозначает напольную плитку. Эта проверка выполняется в отдельной функции, поэтому ее l\югут использовать и другие части программы. Функция generate _ map () помещает число, возвращаемое функци­ ей get _ floor _ type (), в переменную floor _ type. Ис­ пользуя инструкцию €) , функция generate _ map () при­ сваивает п еременной floor _ type значение, соответствующее возвращенному функцией get _ floor _ type () значению, и указывает функции get _ floor _ type () выполнять команды параллельно. Кроме того, функция generate _ map ()настраивает пе­ ременные bottom _ edge и side _ edge. Эти переменные хранят тип материала, который будет использоваться для создания краев изображения комнаты, как показано на рис. 4.3 . Материал side _ edge используется для верхне­ го, левого и правого краев, а материал bottom _ edge - для нижнего края. Если номер помещения находится в диа­ пазоне от 1 до 20 включительно, значит, мы имеем дело с поверхностью планеты. В этом случае и нижний, и боко­ вые края это грунт. Если номер помещения находится - в диапазоне от 21 до 25, то это «помещение» расположено на поверхности планеты и соприкасается с космической станцией нижним краем. Обрати внимание: материал боко ­ вого края - грунт, а нижни1'::'1 край состоит из колонн, вы­ строенных вдоль стены. Если номер помещения больше 25, боковые и нижний края сделаны из колонн, выстроенных вдоль стены, поскольку это поl\1ещение расположено внутри космической станции. (Как видишь, пригодились нам номе­ ра помещений, см. рис. Сформируем 4.1.) список room _ map, начав с верхней стро­ ки, которая будет или внешним рядом грунта, или задней стеной помещения, расположен н ого внутри станции. Верх­ ний ряд полностью состоит из одного и того же материала, СОЗДАЕМ КОСМИЧЕСКУЮ СТАНЦИЮ 117
так что мы може:м упростить себе задачу. Введи следующую команду в оболочке: * 10) >>> print ([l] [1 , 1, 1, 1, 1, [1] 1, 1, 1, в функции 1, 1] print () - это список, в котором нахо­ дится только один элемент. Умножая список на чаем новый список, который содержит 10 10, мы полу­ значений, и все они будут равны изначальному элементу. В нашей програм­ ме мы у1\1ножаем используемый нами тип края на ширину помещения О. Если в верхней части должен быть выход, мы его добавим, но чуть позже. Средние строки помещения были созданы с помощью цикла 0, который по очереди добавляет каждую строку в конец карты room _ map. Все средние строки помещения одинаковы и состоят из следующих элементов. 1. Плитка по краю (стена или грунт) для левой стороны помещения. 2. Пол посередине. Вновь воспользуемся краткой записью: умножаем floor _ type на размер пустого пространства посередине помещения. Это значение переменной равно room _ width 3. 1\111нус 2 (так как есть две краевые точки). Край с правой стороны. Внизу добавляем еще один ряд 0, который создается тем же способом, что и верхняя строка. СОЗДАНИЕ ВЫХОДОВ Теперь нам нужно добавить проходы в стенах, там, где они необходимы. Выходы будут располагаться посередине стен, поэтому перед «прорубанием» стен нужно понять, где нахо­ дятся средняя строка и средний столбец б . Для этого разде­ лим высоту и ширину помещения на 2. Иногда частное получается с остатком. Но поскольку индекс должен быть целым числом, нам понадобится функция int (),чтобы избавиться от дробной части б . Функция int () округляет число, то есть преобразует десятичное число в целое. Сначала мы проверяем наличие выхода справа что 118 rЛАВА 4 room _ data 0. Помни, содержит информацию о помещении,
которая изначально была взята из GAME _МАР. Значение room _ data [4] указывает нам на наличие выхода справа от этого помещения . Следующая инструкция: if room_data[4]: является краткой версией этого кода: if room_data[4] == True: Оператор== используется для проверки равенства значе­ ний двух переменных . Логические значения используются очень часто, потому что значительно упрощают чтение и написание кода, что и демонстрирует данный пример. Если выход должен быть с п рава, мы меняем три значения в середине правой стены с типа «край» на тип «ПОЛ», тем са­ мым создавая про!-.tежуток в стене. Значение width-1 room _ находит позицию х справа: вычитаем 1, поскольку нумерация индексов начинается с О. Например, на рис. 4.2 ты може ш ь видеть, что ширина помещения со ­ 11 плиток, при этом показатель индекса для правой равен 10. Если этот код применить к поверхности ставляет стены планеты, то ничего не произойдет, так как в этом случае от­ сутствует стена, в которой можно сделать проем. Тем не ме ­ нее будет проще позволить про г рамме добавить плитки пола самостоятельно, чтобы нам не п ришлось писать код для особых случаев. П режде чем мы проверим, нужен ли нам выход слева, следует убедиться, что помещение расположено не в левой части карты, где не может быть выхода ратора % мы 0. С помо щ ью опе­ получаем остаток при делении одного числа на другое. Если мы разделим текущий номер помещения на ширину карты, равную лучим 1. 5, используя оператор%, мы по­ Это случай, когда помещение находится слева. Но­ мера помещений, граничащих с левым краем: 26, 31, 36, 41 и 46. 1, 6, 11, 16, 21, Будем проверять помещен и я на наличие выхода слева только в том случае, если остаток не равен (!= 1 означает «Не равно»). Чтобы понять, нужно ли нам создавать выход в левой ча­ сти этого помещения, мы определяем, какое помещение СОЗДАЕМ КОСМИЧЕСКУЮ СТАНЦИЮ 119
находится по другую сторону этой стены, вычитая 1 из те­ кущего номера помещения. Затем мы проверяем, есть ли в этом помещении выход справа. Если это так, нам необхо­ димо создать выход слева в текущем помещении, что мы и делаем. Выходы вверху и внизу выстраиваются аналогичным об­ разом @ .Мы проверяем список room _ data, чтобы уви­ деть, есть ли выход в верхней части помещения, и, если да, делаем проход в стене. Далее мы можем проверить поме ­ щение на наличие выходов внизу, чтобы понять, стоит ли делать там проход. ТЕСТИРУЕМ ПРОf РАММУ Запусти программу и проверь, что в окне командной строки нет никаких ошибок. Также работоспособность кода можно проверить, введя следующие две строки в конце своей про­ граммы и запустив ее снова: generate_map () print (room_map) В окне командной строки должно появиться следующее: [ [1 , 1, 1, [1, [1 , 1, О, О, О, 1, 1, 1, 1] ' о , о , О, О, О, О, О, О, О, 0] ' [1, О, О, О, О, О, О, О, О, О, [1 , 1, 1, 1, 1, 1, 1, 1, о , 1] ' О, О, о, о] ' о , о , о, о , о , 1] ' Переменная на помещение поэтому current _ room 31 - О, О, О, О, О, О, О, [1 , О, О, О, О, о , О, 0) ' [ 1 , О , 1 , 1 , 1]] О, О, О, по умолчанию ссылается исходное помещение в игре. Именно room _ map GAME _МАР (и рис. О, и выводит эти данные. Из данных 4.2) мы види.м, что помещение состоит из 7 строк и 11 столбцов, что и подтверждает вывод про­ граммы: 7 списков, каждый из которых содержит 11 чисел. Идеально! Более того, мы видим, что в первой строке есть четыре колонны, выставленные вдоль стены, три свободные единицы, а зате.м еще четыре колонны, выставленные вдоль стены, поэтому, как мы и ожидали, функция разместила здесь выход. Все три списка заканчиваются числом О, что указывает на наличие выхода справа. Похоже, наша про­ грамма и вправду работает! 120 rЛАВА4
После завершения всех эк сп ериментов убедись, что ты удалил(а) в с е д о полнительные инструкции в конце своей программы. УЧЕ&НАЯ МИССИЯ No 1 Измени в оболочке значение переменной current _ room в конце своей программы, чтобы создать новую комнату. Про­ верь выходные данные на карте и в коде GAME _МАР, чтобы убедиться, что результаты соответствуют твоим ожиданиям. Пример: current room = 45 generate_map () print(room_map) Что произойдет, если ты введешь эти значения для одного из «помещений» на поверхности планеты? ИССЛЕДОВАНИЕ КОСМИЧЕСКОЙ СТАНЦИИВЗD Давай-ка превратим созданные нами карты в самые настоя­ щ ие поме щ ения! Для этого нам потребуется объединить код, который мы создали для п р евра щ ения карт в трех1'лер­ ные помещения в главе 3, с нашим кодом для извлечения карт ы помещений из игровой карты. Сделав э т о, мы сможем осмо тр еть косми ч ескую станцию и научимся в ней ориен­ тироваться . Код в разделе ОБЗОР позволит нам оо,1 атривать любое из поме щ ений, п ри сутствую щ их на космической стан ц ии . Э т о временная ме р а, которая п озволит нам б ы стро добить­ ся желаемого результата . П озже, в главах 7 и 8, мы заменим этот код, отвечающий за просмотр комнат, на более слож ­ н ы й и совершенный . Добавь код из листинга из листинга 4-2, 4-3 в ко н ец своей пр о г раммы после инструкций, выделенных серым цветом. Затем сохран и программу в файл с именем lz:stz:ng4-3.py. СОЗДАЕМ КОСМИЧЕСКУЮ СТАНЦИЮ 121
room_map[room_height-1] [middle_column] = floor_type room_map[room_height-1] [middle_column + 1] floor_type room_map[room_height-1] [middle_column - 1] floor_type ############# ## ОБЗОР ## ############# О def draw (): global room_height, generate_map() screen . clear() 6 for room_width, room_map у in range (room_height) : for х in range (room_width): image_to_draw = DEMO_OBJECTS[room_map[y] [х]] screen.Ыit(image_to_draw, (top_left_x + (х*ЗО), top_left_y + (у*ЗО) - image_to_draw.get_height())) €) def movement (): global current_room old_room = current room if keyboard.left : current room -= 1 i f keyboard.right: current room += 1 if keyboard.up: current room МАР - WIDTH if keyboard.down: current room += МАР - WIDTH О if current room > 50: current room = 50 if current room < 1 : current room = 1 0 @ if current room != old_room: print ( " Tы вошел 6 в комнату :" Q clock.schedule_interval(movement, + str (current_room)) 0.1) Листинг 4-3. Код в разделе ОБЗОР Вероятно, новые строки из листинга 4-3 могут тебе пока­ заться не такими уж и новыми. Здесь мы снова вызываем ф ункцию map 122 rЛАВА4 generate _ map () для настройки списка room _ 6, для текущего по1\1ещения О. Затеl\1 мы отображаем его
используя код из листинга в главе 3-5 3. Воспользуеr..1ся кла­ вишами клавиатуры для изменени я значения переменной current _ room ~ аналогично тому, как мы изменили поло­ жение по осям х и у нашего астронавта, выходящего в откры­ тый космос, в главе 1 (см. листинг 1-4). Чтобы на карте перей­ ти вверх или вниз по строкам, мы меняем номер текущего помещения на величину, равную ширине игровой карты. На­ при.IV1ер, чтобы перейти в поме щ ение 27, переместившись вверх из помещения 5 32, мы вычитаем (см. рис. 4.1). Если но­ мер комнаты изменился, программа выводит значение пере­ менной current _ room 0. Функция str () преобразует но ­ мер помещения (число) в строку б , чтобы ее .можно было соединить со строкой «Ты во ш ел в комнату:». Без функции str () тебе не удастся присоединить число к строке. Теперь нам нужно сделать так, чтобы функция movement 0 , по­ запускалась через одинаковые промежутки времени добную задачу мы уже решали в главе жуток времени будет больше (0,1 1. На этот раз проме­ секунды), что скажется на скорости отклика клавиш . Запусти пр ограмму из командной строки с помощью команды pgzrun listing4-3 .ру. Изображение на экране 4.4, на котором видны стены дверные проемы помещения 3 1. должно быть похоже на рис. и Рис. 4.4. Код в разделе ОБЗОР демонстрирует трехмерное помещение, в котором начинается игра СОЗДАЕМ КОСМИЧЕСКУЮ СТАНЦИЮ 123
Теперь при изучении карты ты можешь пользоваться кла­ вишами -, j, l и -. Программа нарисует текущее помеще­ ние и позволит передвигаться в соседние комнаты при по­ Ivющи «стрелок». Сейчас ты видишь только «оболочку» помещения: стены и пол. Чуть позже мы немного обставим комнаты и добавим на станцию ее главного героя. А пока ты J\южешь передвигаться в любом направлении, в том числе и проходить сквозь стены: программа никак не проверяет корректность твоего п ути. Если ты пройдешь в левую часть карты до самого конца, то затем появи ш ься справа, на строку выше. Если же у1'::'rдешь вправо до конца, очутишься слева, на строку ниже. Если попытаешься под­ няться выше верхней или опуститься ниже нижней части карты, то программа вернет тебя в помещение или помещение ния больше(>) 1 (вверху) 50 (внизу). Например, если номер помеще­ 50 О , он станет равен 50 0 . В этом коде я уменьшил чувствительность клавиш, чтобы не проскаль­ зывать из помещения в помещение. Если ты обнаружишь, что элементы управления не реагируют или работают с за­ держкой, то тебе, возможно, придется зажимать клавиши немного дольше. Рис. 4.5. В ок н е команд н ой с т роки выводится номер помещени я , в котором ты н аходишься Поброди по космической станции и посравнивай то, что ты видишь на своем экране, с картой на рис. 4.1. Если обна­ ружишь какие-либо ошибки, вернись к данным списка GAME _МАР и проверь их, после этого проверь функцию generate _ map (),чтобы убедиться, что все данные были введены правильно. Чтобы тебе было проще ориентировать­ ся на карте, при переходе в новую комнату ее номер будет 124 rЛАВА4
отоб р ажаться в окне командной строки, где ты вводил(а) команду pgzrun (см . рис. 4.5). Кроме того, убедись, что выходы имеются с обеих сто­ рон : если ты проходишь через дверь, а с другой стороны ее нет, то данные для функции generate _ map () были введе­ ны неправильно. Прежде чем перейти к настройке карты, рекомендую походить по ней, чтобы убедиться, что, пройдя до конца в одну сторону, ты не п оявляе ш ься с другой сторо­ ны. Задержись и п отрать немного времени, чтобы убедиться в правильности данных и карты, иначе ты не сможешь пол­ ноценно сыграть в игру «Побег»! УЧЕl&НАЯ МИССИЯ № 2 Чтобы получить удовольствие как от самой игры «Побег», так и от решения ее головоломок, советую воспользоваться дан­ ными, которые я предоставил для игровой карты. Не меняй их до тех пор, пока не пройдешь игру и не захочешь поэкспери­ ментировать с ее дизайном. В противном случае объекты могут оказаться в недоступных для игрока местах, и игру будет по­ просту невозможно пройти. Тем не менее не бойся расширять карту. Самый простой спо­ соб это сделать - добавить еще од и н ряд помещений в нижней части карты. Только убедись, что по крайней мере между одним из новых помещений и уже существующим есть дверь. Не за­ будь изменить значение переменной МАР_ HEIGHT. Кроме того, тебе нужно будет изменить число 50 в коде в разделе ОБЗОР (listing4-3.py) на максимальный допустимый номер поме­ щения (см. О и 0). Попробуешь создать коридор прямо сейчас? СОЗДАНИЕ СО&СТВЕННЫХ КАРТ После того как ты закончишь работать над игрой «Побег» и сыграешь в нее, ты СJ\южешь настроить карту по своему желанию или же создать собственные игровые карты, используя данный код. Если ты захочешь добавить собственные карты для «ПО­ мещею11'::'1» с 1 по 25, удали код, который автоматически их СОЗДАЕМ КОСМИЧЕСКУЮ СТАНЦИЮ 125
генерирует (см. 0 в листинге 4-1). После этого ты сможешь добавить свои данные для этих «помещений» . В случае если тебе не нужны «помещения», расположен­ ные на поверхности планеты, то ты можешь, например, про­ сто заблокировать выход к ним. Выход на поверхность пла­ неты находится в помещении 26. Измени код этого помещения в списке GAME _МАР, чтобы из него нельзя было выйти наверх. Используй номера комнат, начиная с 26, а за­ тем расширь карту вниз, чтобы действие игры проходило исключительно внутри станции. Так тебе не придется вно­ сить какие-либо изменения в код, который относится к по­ верхности планеты. Если ты удалишь с карты какой-либо дверной проем (в том числе и в помещении 26), тебе, скорее всего, придется удалить и дверь. Двери некоторых выходов в верхней и нижней частях помещения будут закрыты. (В главе 11 мы добавим в игру двери.) rотов пи ты к nonETY? Отметь следующие пункты, если чувствуешь, что материал этой главы тебе понятен. D В списке GAME _МАР хранятся основные данные карты игры «Побег». D В списке GAME _ МАР необходимо хранить ли ш ь данные о выходах в верхней и правой части помещения. D Когда игрок входит в помещение, функция map () создает список Список room _ map room _ map generate _ для текущей комнаты. содержит данные о том, где в помеще­ нии расположены стены и объекты. D « ПО1\,1ещения» с 1 по 25 находятся на поверхности планеты, и цикл генерирует их данные автоматически. Помещения с 26 по 50 расположены непосредственно внутри космиче­ ской с т анции, и эти данные нужно вводить вручную. D Мы используем КОJlлментарии, чтобы нам было проще ори­ ентироваться в коде. D При добавлении данных в программу в окне ввода сценари ­ ев ты можешь использовать оболочку для проверки 126 rЛАВА4
содержимого списков и переменных, чтобы убедиться в правильности работы программы. Не забудь сначала запу­ стить игру для генерации данных! О Код в разделе ОБЗОР позволяет тебе просматривать каждое помещение на игровой карте посредством клавиш +---, L и -. j, О Игровая карта должна соответствовать схеме на рис. 4.1. В противном случае игроки не смогут полностью пройти игру « Побег ». Чтобы предотвратить это, воспользуйся кодо:м в разделе ОБЗОР. ОБСУЖДЕНИЕ МИССИИ Ниже представлены ответы на учебные миссии из этой главы. УЧЕБНАЯ МИССИЯ № 1 Если ты зайдешь в одно из «помещений», расположенное на поверх­ ности планеты, и при этом вся карта состоит из марсианского грунта, на экране должно быть лишь повторяющееся число 2. Если ты зай­ дешь в «помещение», расположенное на поверхности, но при этом «комната» граничит с космической станцией, внизу экрана должна быть стена космической станции. УЧЕБНАЯ МИССИЯ № 2 Чтобы усложнить игру, я добавил секретный проход в нижней части карты, который соединяет помещения 46 и 50. Для этого в разделе КАРТА измени значение переменной МАР_ HEIGHT с 10 на 11: МAP_HEIGHT ния = 11 В списке GAME _МАР добавь запятую в конце строки помеще­ 50, но перед комментарием #: [ "Робототехническая мастерская" , помещение 7, 9, True , False ], # 50 Добавь несколько помещений в список GAME _МАР после помещения 50. После каждого списка помещения должна идти запятая, за исключением последнего. Все списки должны нахо­ диться внутри закрывающей квадратной скобки GAME _МАР: --пропуск-- [" Робототехническая мастерская" , # помещение 50 [ "Секретный проход 11 , 9, 15, True , 11 [ Секретный проход" , 9, 9, False , [ "Секретный проход 11 , 9, 15, False , 9, 11, True, False], True ], # True ], # True ], # помещение помещение помещение СОЗДАЕМ КОСМИЧЕСКУЮ СТАНЦИЮ 51 52 53 127
[ " Секретю.IЙ проход" , [ " Секретю.rй проход" , 9, 9, False , True ], 9, 15, True , False ] #помещение #помещение 54 55 --пропуск-- Чтобы тебе было легче распознавать переходы из комнаты в комнату, я чередую ширину помещений в проходе, исполь- зуя значения 15 и 9. Если все комнаты похожи друг на друга как близнецы, довольно сложно понять, действительно ли ты сме­ нил(а) комнату. В сринальной версии игры ты сможешь четко различать, попал(а) ли ты из одного похожего помещения в дру­ гое, так как персонаж выйдет из одной двери и войдет через противоположную. Кроме того, я изменил код в разделе ОБЗОР (listing4-3.py), 55: чтобы продемонстрировать, что помещений стало --пропуск-- current_room > 55 current_room = 55 if current_room < 1: current_room = 1 i f --пропуск--
noдrOTOBKA О&ОРУДОВАНИЯ ДЛЯ КОСМИЧЕСКОЙ СТАНЦИИ Теперь, когда наша космическая станция обрела стены, мы можем при- ступить к ее обустройству. Нам пона­ добится детальная информация о различных объектах, включая мебель, системы безопас­ ности и оборудование для экспериментов. В этой главе мы добавим в игру сведения обо всех предметах, расположенных на кос­ мической станции, включая их изображения и описания. Также мы поработаем с дизай­ ном помещений и посмотрим на них с помо­ щью кода в разделе ОБЗОР из главы 4. подrОТОВКА О&ОРУДОВАНИSI ДЛSI КОСМИЧЕСКОЙ СТАНЦИИ 129
СОЗДАНИЕ ПРОСТОrо СЛОВАРЯ ПЛАНЕТ Для хранения информации об оборудовании космической станции мы будем использовать концепцию программирова­ ния, называемую словарями. Слов ари немного похожи на списки, но в отличие от последних они имеют встроен­ ную поисковую систему. Давай посмотрим, как это работает. РАЗНИЦА МЕЖДУ СПИСКОМ И СЛОВАРЕМ Как и в случае с бумажным словарем, ты J\IЮЖешь задать слово или фразу для поиска инфор,11.1ации в словаре Python. Это слово или фраза называется ключом, а информация, связанная с ключом, называется значение.л1. В отличие от бумажного словаря, записи в словаре Pytl10n могут рас­ полагаться как угодно. Порядок следования ключей не обя­ зан быть алфавитным. Pyt110n может перейти непосред­ ственно к нужной тебе записи, где бы она ни наход илась. Представь, что у тебя есть список, содержащий информа­ цию о предыдущих космических миссия х . Ты можешь полу­ чить первый элемент из этого списка, введя следующее: rint (mission_i n fo[O ]) А вот если бы mission _ info был словарем, а не спис­ ком, ты бы мог(ла) вместо индекса просто ввести название миссии, чтобы получить о ней информацию, например: print (mission_info [ " Mapc 3 " ]) Ключом может быть как слово или фраза, так и число. По­ началу мы будем работать только со с л овами, поскольку так гораздо легче понять разницу между списками и словарями. СОСТАВЛЕНИЕ СЛОВАРR-WПАРfАЛКИ ПО АСТРОНОМИИ Все астронавты обязаны знать Солнечную систем у как свои пять пальцев, поэтому пусть наш первый словарь будет посвящен именно планетам. Мы станем использовать их названия в качестве ключей и свяжем каждое имя планеты с информацией о ней. 130 rЛABAS
Взгляни на листинг словарь planets. 5-1, с помощью которого мы создаем При составлении словаря мы используем фигурные скобки {},чтобы оп.~етить начало структуры и ее конец. Квадратные же скобки используются при работе со списками. Каждая запись в словаре состоит из ключа, разделитель­ ного двоеточия и информации, связанной с этим ключом. Как и в случае со списком, мы разделяем записи за пятыми и заключаем фрагменты текста в двойные кавычки. Создай новый файл в IDLE (File ~ New File (Файл ~ Новый файл)) и введи указанный ниже код. Сохрани файл под именем planets lz:sting5-J.py. { " Меркурий " : к " Самая маленькая Солнцу ", длится " Венера " : примерно " Единственная есть жизнь " , планета " , " Марс " : " Юпитер " : гигант " , планета - гигант с газовый системой от Солнца на земных известная газовый дальний 243 планета , " День ближайшая Венере дня " , планета , " Вторая " Земля " : на самая которой маленькая " Самая большая планета , " Сатурн " : " Вторая по гигант " , " Уран " : " Ледяной колец " , " Нептун " : " Самый ледяной гигант " величине О while True : query = input ( " Пpo какую {) print (planets[query]) 6 Листинг планету ты хочешь узнать? ") 5-1. Твоя первая программа, содержащая словарь Эта программа работает не с Pygame Zего, поэтому ты Run ~ Run можешь запустить ее, выбрав команду меню Module окна (Выполнить ~ Выполнить модуль) в верхней части IDLE. (Програ.11.1ма запустится и в случае использова­ ния команды pgzrun, но проще использовать меню.) Когда ты Jапустишь пр<.н·рамму, она спросит тебя, о какой планете ты хочешь узнать. Для вывода вопроса используется встро­ енная функция input () 6. Попробуй ввести название пла­ неты Земля или Юпитер. Про какую планету Единственная Про какую Самая планету большая ты хочешь известная ты хочешь планета , узнать ? планета , на узнать? газовый Земля которой есть жизнь Юпитер гигант подrОТОВКА О&ОРУДОВАНИll ДЛll КОСМИЧЕСКОЙ СТАНЦИИ 131
Какое бы название планеты ты ни ввел(а), оно сохраняет­ ся в переменной query. В дальнейшеI>.1 эта переменная ис­ пользуется для поиска сведений в словаре planets ~-Здесь нам не нужен индекс в квадратных скобках, как это было в случае списка, для получения информации мы используем введенное слово, которое сохраняется в переменную В Python query. есть цикл while О для повторения набора ин­ струкций. В отличие от цикла for, который мы используем для повторения инструкций определенное количества раз, цикл while обычно повторяет их до тех пор, пока не изме­ юпся какое-то условие. Часто в игре команда while прове­ ряет переменные, чтобы определить, следует ли дальше по­ вторять инструкции. Например, инструкция > while lives О будет продолжать игру до тех пор, пока у игрока не кончатся жизни. Когда значение переменной lives ста­ нет равным О, цикл закончит свою работу. Команда while ге 5-1, True, которую мы используем в листин­ будет повторяться бесконечно, поскольку условие while True истинно всегда. Чтобы команда while True работала, убедись, что слово True набрано с прописной буквы, а в конце строки стоит двоеточие. В команде while мы используем четыре пробела для от­ ступа перед инструкциями, которые должны повторяться. Здесь мы сделали отступы перед строками, в которых запра­ шивается название планеты, а затем выводится информация о планете. Эти инструкции - повторяющиеся. После того как ты введешь название планеты и получишь информацию, программа запросит у тебя другое название планеты, а за­ тем еще одно, и так до бесконечности. Или, по крайней мере, до тех пор, пока ты не остановишь программу, нажав сочетание клавиш Ctгl+C. Несмотря на то что программа работает, она еще не за­ вершена. Например, если ты введешь название планеты, ко­ торой нет в словаре, на экране появится ошибка. Давай из­ меним код так, чтобы в этом случае программа выводила какое-нибудь полезное сообщение. 132 rЛABAS
ПРОВЕРКА СЛОВАРЯ НА ОWИ&КИ Когда ты вводишь ключ, которого нет в словаре, высвечива­ ется сообщение об ошибке. Python ищет точное совпадение. Поэтоr.1у если ты попытаешься найти что-то, чего нет в сло­ ва р е, или сделаешь даже самую малую орфог р афическую ошибку, то н ужной информац и ю тебе не видать. Ключи в словаре, как и имена п еременных, чувствитель ­ ны к регистру, поэтому, если ты введешь земля вместо Земля, программа не с р аботает. Если ты введешь название несуществующей планеты, произойдет следующее: Про какую планету ты хочешь узнать? Плутон Traceback (most recent call last) : File " C : \Users\Sean\Documents\Escape\listingS-1.py ", line 13, in <module> print(planets[query]) KeyError : ' Плутон' >>> Бедный Плутон ! В 2006 году после 76 лет верной службы его исключили из числа планет, вот почему его нет в н а ш ем словаре planets. УЧЕ&НАЯ МИССИЯ № 1 Сможешь ли ты самостоятельно добавить данные о Плутоне в словарь? Удели особое внимание расположению кавычек, двоеточий и запятых. Данные о Плутоне можно поместить в любую позицию словаря. Когда программа ищет элемент, которого нет в словаре, о н а останавливается и возвращает тебя к команд н ой строке Python. Ч т обы избежать этого, нам нужно сдела ть так, что ­ бы программа с н ачала пр оверила, является ли введенное слово одним из к л ючей в словаре, а затем уже искала значе­ ние этого ключа. Ч тобы узнать, какие ключи находятся в словаре, введи его имя, а затем точку и keys ().На языке п р ог р аммистов это н азывается методо.м. Грубо гово р я, метод - это набор инструк ц ий, котор ы е ты може шь добавить к фр а г менту подrОТОВКА О&ОРУДОВАНИSI дng КОСМИЧЕСКОЙ СТАНЦИИ 133
данных с помощью точки. Выполни следующий код в обо­ лочке Python: >>> print (pl a nets . keys ()) dict_keys([ ' Mapc ', ' Плутон ' Сатурн ', ' Меркурий ', ', 'Юпитер', ' Нептун' , ' Земля ', ' Уран ', 'Венера']) Похоже, здесь происходит что-то странное ... Когд а ты выполнял(а) учебную миссию №2 1, то добавил(а) Плутон в словарь последним пунктом. Но теперь в списке ключей он находится на втором месте. Когда ты добавляешь элемен­ ты в список, они помещаются в его конец, но в случае со словарем это не всегда так. Поведение словаря зависит от того, с какой версией версии Pytl10n ты работаешь. ( В последней Python элементы словаря хранятся в том порядке, в котором их добавили.) Как я уже говорил, порядок ключей в словаре не имеет значения. Python самостоятельно опре­ деляет, где находятся ключи в словаре, поэтому не стоит за­ морачиваться по этому поводу. Чтобы предотвратить сбой програмr..rы при запросе поль­ з ователем информации о планете, которой нет в словаре, измени свою програм~Iу, как показано в листинге 5-2. --пpnr,yc_y-­ wh 1le True: query input("П ак.1 ПJТct"C'iY О if query in p l anets . keys (): 6 p r int (pl an ets[query] ) else : print ( " Heт информации! ты XOY.E...!l.', уЗЧJТЬ? ") Извини! " ) Листинг 5-2. Проверка ошибок при поиске по словарю Сохрани программу в файл с имене м сти ее, выбрав команду меню listing5 -2.py и запу­ Run ~ Run Module (Выпол­ нить ~ Выполнить м одуль). Убедись, что все работает вер­ но, введя правильное название планеты, а з атем запроси планету, которой нет в списке ключей. Вот пример: Про День какую на планету Венере Про какую Нет информации! 134 rЛABAS ты длится планету ты хочешь узнать? примерно хочешь Извини ! 243 узнать? Венера земных Ц ерера дня
Мы защищаем нашу программу от сбоя, проверяя ключ, сохраненный в переменной query, до того как програJ1лма попытается его использовать О . Если ключ существует, мы работае.11.1 с переменной query как раньше 6. В противном случае мы отправляем пользователям сообщение о том, что в нашем словаре ~ нет интересующей их информации. Те ­ перь программа выглядит более дружелюбной. РАЗМЕЩЕНИЕ СПИСКОВ В СЛОВАРЯХ Пока наш словарь планет мало что умеет . Что, если мы захо­ тим добавить еще какую-нибудь информацию о той или иной планете, например, есть ли у нее ко л ьца или сколько у нее лун? Для хранения нескольких фрагментов информа­ ции о планете мы rv1ожем воспользоваться списком, а затем поместить его в словарь. Вот, например, новая информация о Венере: " Венера ~ : False , [ " День на Венере длится примерно 243 земных дня " , О ] Квадратные скобки обозначают начало и конец списка, а сам список содержит три элемента: краткое описание, зна­ чение True или False, указывающее на наличие у планеты колец, а также количество лун, которыми она обладает. По­ скольку у Венеры нет колец , вторая запись является ложной (False). Лун у нее тоже нет, поэтому значение третьей за­ писи равно О. ij>Jфlf.il Значения True и False нужно писать с прописной буквы, и их нельзя закл ючать в кавычки. Если эпzи слова введены в IDLE nр?zвильно, они будут выделены оранжевым цветпом. Измени код словаря таким образом, чтобы у каждого ключа был свой список, как показано в листинге 5-3, сохра­ нив остальную часть кода неизменной. Помни, что словар­ ные статьи разделены запятыми, поэтому после закрываю­ ще1':'1 скобки для всех списков, кроме последнего, стоит запятая. Сохрани обновленную программу в файл с именем lis6ng5-3.py. подrОТОВКА О&ОРУДОВАНИll ДЛll КОСМИЧЕСКОЙ СТАНЦИИ 135
Как видишь, я добавил в словарь информацию и о Плу­ тоне. Некоторые астрономы предполагают, что у него есть кольца, и их исследования продолжаются. К то.\·1у времени, когда ты прочтешь эту книгу, возможно, мы будем знать о Плутоне немного больше. planPts - { " Меркурий " : к [ " Самая False , О] примерно 243 Солнцу " , длится [ " Единственная маленькая , планета, " Венера " : земных известная [ " День дня " , планета , ближайшая на Венере False , 0] , на которой " Земля " : есть False , 1], " Марс " : [ " Вторая самая маленькая планета ~ , False , 2] , " Юпитер " : [ " Самая большая планета , газовый гигант " , True , 67 ], " Сатурн " : [ " Вторая по величине планета - газовый гигант " , True , 62 ], " Уран " : [ " Ледяной гигант с системой колец " , True , 2 7], " Нептун " : [ " Самый дальний от Солнца ледяной гигант " , True , 14 ] , " Плутон " : [ " Крупнейшая карликовая планета в Солнечной системе " , False , 5 ] жизнь " , --rzропуск-- Листинг 5-3. Добавление списка в словарь Запусти программу, выбрав команду меню Run ~ Run Module (Выполнить ~ Выполнить модуль). Теперь, когда ты запрашиваешь информацию о той или иной планете, про­ грамма должна отображать весь список этой планеты: Про какую [ ' День Про на какую [ ' Вторая планету Венере планету самая ты хочешь длится ты узнать? примерно хочешь маленькая Венера 243 узнать? планета ', земных дня' , False , 0] Марс False , 2] ИЗВЛЕЧЕНИЕ ИНФОРМАЦИИ ИЗ СПИСКА, ПОМЕЩЕННОrо в СЛОВАРЬ Теперь мы знаем, как получить список информации из сло­ варя, поэтому следующий шаг - извлечь отдельные фраг­ I-ленты информации из этого списка. Например, ложная запись (False) сама по себе мало что нам говорит. Если мы сможем выделить ее в списке, то сможем и добавить к ней пояснение. Ранее мы использовали списки внутри списков для составления карты помещения в главе 4. Сейчас, как и раньше, мы будем использовать индексы для получения элементов из списка в словаре. 136 rnABAS
Поскольку это весь список целиком , planets [query ] - увидеть описание планеты (первы1'::'1 элемент в списке) можно, используя Благодаря planets [query] [0]. planets [query] [1] мы 1\южем узнать, имеются ли у планеты кольца. Если вкрат­ це, то делаем мы вот что. 1. Для доступа к определенному списку из словаря planets мы используем название планеты, сохраненное в перемен­ нои 2. query. Далее мы используем индекс, чтобы выбрать отдельный эле­ мент и з полученного списка. Измени свою программу так, чтобы она выглядела, как показано в листинге 5-4. Как и прежде, измени только те строки, которые не выделены серым цветом. Сохрани свою программу в файл с именем выбрав команду меню Run ~ listing5 -4.py Run Module и запусти ее, (Выполнить ~ Выполнить модуль). nprJпycк - while True: query = input( Пpn к~Уую пл~нРту if query in planets.keys(): print (planet s[ q u e ry] [0 ]) 11 О print ( 6 11 Ecть else: print ( 11 Нет ли у нее кольца? инФсf>Мd:.~ии ! ты хоч~~ь 11 planets[ qu er y ] [1] ) Изъини узd~гь? 11 ) ! ") Листинг 5-4. Отображение информации из списка, находящегося в словаре Когда ты запустишь программу lz:sting5-4.py, на экране должно появиться следующее: Про какую планету Единственная Есть Про ли какую Вторая Есть у ли по у нее ты кольца? планету ты величине нее хочешь известная на Земля которой есть жизнь False хочешь планета кольца? узнать? планета , узнать? - газовый Са т урн гигант True П рограмма должна работать при запросе любой планеты из словаря! Когда ты вводишь название планеты, которая есть в сло­ варе, программа выводит первый элемент из списка данных подrОТОВКА О&ОРУДОВАНИSI ДЛSI КОСМИЧЕСКОЙ СТАНЦИИ 137
(описание) О . В следующей строке программа спра ш ивает, есть ли у этой планеты кольца, а затем выводит True или False, что является вторым пунктом в списке данных об этой планете 6. Ты можешь вывести на экран некоторый текст и некоторые данные, используя одну и ту же инструк­ цию print (),а также разделительную запятую . В таком виде инфор11.1ация восприни1\1ается легче, чем вывод всего списка целиком. YЧEliHASll МИССИЯ No 2 Сможешь изменить программу так, чтобы она сообщала о ко­ личестве лун планеты? СЛОВАРЬ О&ЪЕКТОВ КОСМИЧЕСКОЙ СТАНЦИИ Давай применим полученные знания о словарях и списках внутри них к нашей космической станции. Н а ней нахо­ дится немало предметов: мебель, оборудование для жизне­ обеспечения, инструменты, личные вещи. Н ам нужно отслеживать информацию обо всех этих объектах . Хранить данные о предметах мы будем в словаре objects. На этот раз ключами будут числа. Это проще, чем ис­ пользовать слово для каждого объекта. Кроме того, при ис­ пользовании чисел тебе будет легче читать карту помеще­ ния в том случае, если ты решишь вывести ее на экран, как мы это делали в главе 4. Кроме того, так мы значительно снизим риск появления ошибок из - за опечатки при вводе. Когда позже мы займемся созданием кода для головоломок, ключи IЗ виде чисел помогут наы сделать решение менее оче­ видным, чтобы ты встретил(а) как можно меньше спойле­ ров, прежде чем закончишь создание игры. Возможно, ты помнишь, что в главе числа О, 4 мы использовали 1 и 2 для обоз н ачения напольной плитки, стеновых колонн и грунта. Пусть эти объекты останутся под своими номерами, а НОIЗЫМ прещ.летам будут присвоены числа от до 138 rЛABAS 81. 3
Каждая запись в словаре представляет собой список, со­ держа щ ий информацию об элементе, аналогично словарю planets, созданному ранее в этой главе. Информация о каждом объекте содержится в списке, который состоит из следующих частей. Гр афич еский файл с изображением объекта. Разные объекты могут использовать один и тот же файл изобра­ жения. Напри.11.~ср , все карты доступа используют одно и то же изображение. Гр афический файл с изображением тени объекта. Тени используются, чтобы усилить 3D-эффект. Стандартнь1.11ли видами теней объекта являются: images.full_sl1adow, кото­ рая заполняет все пространство плитки и относится к большим объектам, и i:rлages.half_shadow, которая за­ полняет половину пространства плитки при работе с объектами небольшого размера. Объекты с характер­ ныi\1 очертанием, такие как кактус, обладают собствен ­ ным файлом изображения тени, который используется только для этого объекта. Для некоторых предметов, та­ ких как стул, тень размещена в файле с изображением объекта. Кроме того, существует класс предметов, кото­ рым тени попросту не нужны, например кратеры и лю­ бые предметы, которые игрок может поднять и унести. Когда у изображения отсутствует тень, мы используем слово None там, где в словаре указывается имя файла с изображением тени объекта. Слово тип данных в None - это особый P ython. Как и в случае с True и Fa lse, тебе не нужно заключать его в кавычки, при этом слово должно начинаться с прописной буквы. Если ты все ввсл(а) правильно, слово None будет выделено оранже­ вым цветом. Подробное описание. Подробное описание отображает­ ся, когда игрок изучает предмет или нажимает на него. Некоторые из подробных описаний могут содержать подсказки, другие же просто описывают объект. Краткое описание. Обычно состоят всего из нескольких слов, например « Карта доступа». Краткое описание выво­ дится на экран в тех случаях, когда игрок подrОТОВКА О&ОРУДОВАНИSI ДЛSI КОСМИЧЕСКОЙ СТАНЦИИ 139
взаимодействует с объектом. Например, «Ты уронил(а) карту доступа». Краткое описание требуется только для тех предметов, которые игрок может выбрать или исполь­ зовать, например карта доступа или торговый автомат. Игра может неоднократно использовать один и тот же элемент словаря стоит из 60 objects. Например, если помещение со­ или более одинаковых стеновых колонн, игра может сколько угодно раз использовать один и тот же объ­ ект такой колонны. Поэтому в словаре его нужно указать только один раз. Если некоторые элементы используют одни и те же фай ­ лы изображений, но при этом отличаются друг от друга в каких-либо деталях, такие объекты нужно сохранять в словаре каждый отдельно. Например, карты доступа име­ ют разное описание в зависимости от того, кому они при­ надлежат, а описание двере1':'r зачастую содержит указание на тот или иной ключ для их открытия. Каждая карта досту­ па и каждая дверь обладают собственной записью в словаре objects. СОЗДАНИЕ ПЕРВЫХ О&'ЬЕКТОВ ДЛЯ Иf РЫ «ПО&Еf» Откройф айл lz:sting4-3.py, созданный в главе 4. Этот список содержит карту игры и код для создания карты помещения. Мы добавим их в нашу программу и продолжим работу над игрой. Во-первых, нам нужно настроить некоторые дополни­ тельные переменные. Перед началом приключения исследо­ вательский аппарат, называеN1ый посадочным модулем, со­ вершает авари11ное приземление на поверхность планеты. В новых пере1\1енных мы и сохраним координаты случайного места приземления. Давай создадим эти переменные, по­ скольку они нужны при описании объекта 1\арты (номер 27). 5-5, в раз­ символами # в фай­ Добавь новые строки, как показано в листинге дел ПЕРЕМЕННЫЕ. Эти строки отмечены ле listing4-3.py. Я рекомендую добавить их после всех остальных переменных, чуть выше раздела КАРТА, чтобы 140 rЛABAS
наши с тобой списки соответствовали друг другу. Сохрани свою программу в файл с именем listing5-5.py. Пока она не делает ничего нового, но если ты хочешь в этом убедить­ ся, набери pgzrun listingS-5 . py. --пr опуск-- # ## # ## ## ## # ## ## # ## ПF РЕМЕ Н ЫЕ ## ################ --проr,уск - DEMO_OBJECTS = [images.floor, images.pillar, images.soil] LANDER_SECTOR = random.randint(l, 24) LANDER_X random.randint(2, 11) LANDER_Y = random.randint(2, 11) ############### ## КАРТА ## ############### -пропуск Листинг 5-5. Добавление переменных, содержащих координаты места приземления модуля Эти новые инструкции создают переJ'\1енные, предназна­ ченные для хранения координат х и у сектора аварийной по­ садки (или номера «помещения»), и присваивают им нуж­ ные значения. Координаты сектора выбираются произвольным образо:м, за это отвечает функция randint (), random. которая генерирует случайное число в интерва­ ле между дву;11.1я заданными числами. Эти инструкции вы­ полняются один раз в начале игры, поэтому местоположе­ ние посадочного модуля меняется п ри каждом новом запуске игры, но остается неизменным во время нее. Теперь давай добавим первый фрагмент данных о пред­ метах, как показано в листинге 5- 6. В этом разделе пред­ ставлены данные для объектов от О до 12. Эти объекты нель­ зя подобрать или использовать, поэтому краткого описания у них не будет. Помести этот фрагмент списка прямо над разделом ПО ­ МЕЩЕНИЕ текущей программы (listz>ng5-5.py). Чтобы тебе было проще ориентироваться в коде, нажми сочетание кла­ виш Ctrl+F в I DLE для поиска определенного слова или подrОТОВКА О&ОРУДОВАНИll ДЛll КОСМИЧЕСКОЙ СТАНЦИИ 141
фразы. Попробуй, например, найти слово пом.еzи,ение, что­ бы перейти туда, где нужно вставить код из листинга 5-6. После завер ш ения поиска закрой диалоговое окно, нажав в нем кнопку (Закрыть). На всякий случай напомню, Close что в приложении А находится полный листинг игры, по­ этому, если у тебя возникли какие-то затруднения, ты все­ гда можешь обратиться к нему. Если ты не хочешь вводить код вручную, воспользуйся файлом clata-c/1apter5.py из папки с примерами. В нем содер­ жится словарь objects, поэтому ты 1\южешь просто скопи­ ровать и вставить его в свою программу. Для начала добавь первые 12 объектов. --пропуск-- assert НЕ' len(GAМE_МAP)-1 - МAP_SIZE, "PdЗ'l.1ep '<'dР'Ы и GАМЕ_МАР со 'Н :~дают" ############### ## ОБЪЕКТЫ ## ############### objects = { О: [images.floor, None , " Чистый и блестящий пол " 1: [images.pillar, images.full_shadow, " Гладкая и 2: холодная 3: " Похоже на пустыню . Или тут живет? " ], [images.pillar_low, images.half_shadow, и 4: стена " ], [images.soil, None , кто-то ], холодная " Гладкая стена " ], [images.bed, images.half_shadow, " Аккуратная и удобная кровать " ], 5: [images.taЫe, images.half_shadow, " Стол из прочного пластика " ], 6: 7: 8: [images.chair_left, None , " Стул с мягким сиденьем " ], [images.chair_right, None , " Стул с мягким сиденьем " ], [images.bookcase_tall, images.full_shadow, " Высокий 9: [images.bookcase_small, images.half_shadow, книжный шкаф " ], " Маленький книжный шкафчик " ], 10: [images.cabinet, images.half_shadow, 11: [images.desk_computer, images.half_shadow, " Небольшой " Компьютер . шкафчик для Используй хранения для личных диагностики вещей " ], систем жизнеобеспечения " ], 12: [ i mages. plant, images. plant_shadow, ягода , 142 rЛABAS выращена здесь " ] " Космическая
############### # # ПОМЕЩЕНИЕ. # # #######Н###### --пропуск-- Листинг 5-6. Создание первых объектов Обращай внимание на подсветку кода, она поможет тебе в поиске ошибок. Если строки, содержащие текст, не окра­ шены в зеленый цвет, значит, пропущена открывающая двойная кавычка. Если же, наоборот, зеленого цвета слиш­ ком много, возJ\южно, ты забыл(а) поставить закрывающие двойные кавычки. Бывает так, что элеJ'.ленты списка занима­ ют несколько строк, в этом случае Г ytl1on не «завершит» с п исок, пока не найдет закрывающую скобку. Если у тебя не получается добиться от программы нужного результата, воспользуйся моим кодом (см. «Использование моих приме­ ров с кодом» в главе 1) и продолжай работу над п роектом с нужной точки. Листинг 5-6 похож на созданный нами ранее словарь planets : для обозначения начала и конца словаря мы ис ­ пользуем фигур н ые скобки, а каждая запись в словаре пред­ ставляет coбoi:I список, поэтому она заключена в квадратные скобки. Главное отличие от предыдущего примера состоит в том, что на этот раз ключом является число, а не слово. Сохрани свою новую прог р амму в файл с именем listz:ng5 - 6.py. Для создания г р афики п р ограмма использует Гуgа111е Zего, поэтому для ее запуска тебе нужно ввести команду pgzrun listingS-6.py. Результат работы про­ граммы не должен измениться, ведь мы пока просто добави­ ли новые данные. Н о програм11лу все равно стоит запустить, чтобы проверить, нет ли ошибоI<. Так ты сможешь испра­ вить код перед тем, как продолжи ш ь работу над созданием игры . ПРОСМОТР О&'ЬЕКТОВ С ПОМОЩЬЮ КОДА В РАЗДЕЛЕ ОБЗОР Чтобы вывести объекты на экран, нам нужно сооб щ ить игре, что нам нужен новы1~1 словарь. Измени следующую строку программы в разделе ОБЗОР: подrОТОВКА О&ОРУДОВАНИll ДЛll КОСМИЧЕСКОЙ СТАНЦИИ 143
irnage_to_draw DEMO_OBJECTS[roorn_rnap [ y] [х]] на эту: irnage_to_drdw objects [roorn_rnap[y] [х]] [0] Таким образом, :мы указываеl\1 програ:мме использовать наш новый словарь objects вместо списка DEMO _ OBJECTS, на который ссылались ранее. Обрати внимание, что теперь мы используем строчные буквы вместо прописных . В эт01'::'1 программе я использую прописные буквы для констант, то есть переменных, значе­ ния которых не меняются. В список DEMO _ OBJECTS мы еще ни разу не вносили изменения: он использовался толь­ ко для поиска имен файлов изображений. Но содержимое словаря objects будет меняться по ходу игры. Еще одно отличие заключается в том, что значение [О] сейчас находится в конце строки. Так произошло потому, что когда мы извлекаем из словаря объектов элемент, то в каче­ стве результата к нам приходит целый список данных. Но сейчас н ам нужно только изображение, которое нахо­ дится в первом элементе этого списка, поэтому мы исполь­ зуем индекс [О] для его извлечения. Сохрани программу и запусти ее снова. По1\1ещения вы­ глядят как и рань ш е, никаких новых объектов мы еще не до­ бавляли. Пока мы просто сохранили номера таких объек­ тов, как пол, стены и грунт с теми же индексами, которые использовались для них и раньше. ПРОЕКТИРОВАНИЕ ПОМЕЩЕНИЯ Давай займемся обставлением нашей комнаты. В разделе О БЗ ОР добавь новые строки так, как показано в листинге --пропуск-- ####JfHH#### ## о ;зор ## ############# def draw(): global roorn_height, roorn_width, print(current_roorn) 144 fЛABAS roorn_rnap 5-7:
generate_map () screen. lear () room_map[2] [4] room_map[2] [6] room_map[l] [1] room_map[l] [2] room_map[l] [8] room_map[l] [9] - 7 6 8 9 12 9 про Листинг 5-7. Добавляем предметы в помещение Таки:м образом, прежде чем отрисовать помещение, :мы добавляем в список room _ map П омни, что в списке нужные нам объекты. room _ map сначала указывается ко­ ордината у, а затем координатах. Первое число показывает, как далеко от задней части помещения находится объект; чем меньше число, тем дальше эти предметы находятся от нас. Наи11.1еньшее значение обычно равно 1, потому что стена находится в строке О. Второе число указывает, как далеко от стены расположены объекты, если идти слева направо. Обычно в столбце О рас­ положена стена, поэтому и здесь 1- наименьшее ВОЗJ\ЮЖ­ ное число. Число, которое находится справа от знака равенства, яв ­ ляется ключом к определенному объекту. Чтобы узнать, какой объект представляет то или иное число, посмотри на словарь objects в листинге 5-6. Таким образом, эта строка: room_map[l] [1] = 8 помещает объект 8 (высокий книжный шкаф) в верхний левый угол помещения. А эта строка: room_map[2] [6] = 6 помещает стул с мягким сиденьеl\t (объект 6) в третью строку сверху, седьмой столбец слева. (На п оминаю, что индексы отсчитываются с О.) Сохрани свою программу в файл с именем и выполни команду ска. На рисунке 5.1 pgzrun listing5-7 .ру listing5-7.py для его запу­ показано, что должно появиться на экра­ не. подrОТОВКА О&ОРУДОВАНИll ДЛll КОСМИЧЕСКОЙ СТАНЦИИ 145
Рис. 5.1. Уют н о! Некоторые объекты были созданы в разделе ОБЗОР Поскольку 1юд в разделе ОБЗОР - это всего лишь демон­ страционная версия, кое-чего здесь не хватает. Например, под некоторыми объектами ты можешь заметить черный квадрат, поскольку там нет напольной плитки. KpoJ\,1e того, все помещения выглядят одинаково, так как мы закодирова­ ли объекты в разделе ОБЗОР таки:rv1 образом, чтобы они по­ являлись в каждом поJ\1ещении, в которое мы переходим. В частности, именно поэтому невозможно посетить абсо ­ лютно все помещения станции, потш\1у что в н екоторые из КОJ\1нат объекты п росто не поместятся, и ты не оюжешь использовать клавиши +---, j, l и - для просмотра такой ком ­ наты. Также пока программа неправильно отображает ши­ рокие объекты, такие как, например, кровать. Чуть позже мы исправим все эти нюансы, а сейчас п р едлагаю продол­ жить обустройство космической станции. YЧEliHAlll МИССИll № 3 Поэкспериментируй с кодом, который ты добавил(а) в раздел ОБЗОР, и расположи мебель по своему вкусу. Советую тебе поиграть с этим кодом, так ты сможешь лучше понять, как размещаются объекты в комнате. Если тебе понадобится помещение большего размера, измени значение переменной current _ room в разде­ ле ПЕРЕМЕННЫЕ с 31 на 40 (это максимальный размер помещения в игре). Сохрани программу в файл с именем сти ее с помощью команды missionS-3.py и pgzrun mission5-3.py. сделать резервную копию кода из раздела ОБЗОР для выполнения учебной миссии 146 rЛABAS NO 4. запу­ Не забудь (listingS-7.py)
ДО&АВЛЯЕМ ОСТАЛЬНЫЕ О&'ЬЕКТЫ Ранее мы добавили в словарь Всего в нашей игре будет 81 objects объекты с О по 12. предмет, так что давай добавим в словарь все оставшиеся объекты с помощью новых строк в листинге екта 12, 5-8. Не забудь поставить запятую после объ­ а затем уже .можешь добавлять в словарь остальные элементы. Если несколько объектов используют одно и то же имя файла или описание, ты можешь просто скопировать этот фрагмент и вставить в нужное 1\Iесто. Чтобы скопировать код, в начале фрап1ента кода нажми кнопку мыши, затем, удерживая ее, перемести мышь, чтобы выделить нужный текст, после чего нажми сочетание клавиш Ctrl+ C. Теперь щелкни мышью по тому месту, куда ты хочешь вставить ско­ пированное, и нажми сочетание клавиш Ct гl +V. Если ты хочешь сэкономить время на вводе кода, можешь просто скопировать и вставить код словаря целиком из файла data- c/Japter5.py. Сохрани программу в файл с именем listing5-8.py. верь, работает ли программа, введя команду listing5-8 .ру, Про­ pgzrun хотя результат пока ничем не отличается от п редыдущего вывода. Далее приведен листинг 5-8: #########Н#### ## О"'LЕКТЫ # # ############### objects = { О: [irnages.f::.oor, None, 11 '-:!У1стый и б.rестящий пол"], --пропуск-- 13: [irnages.plant, irnages.plant shadow, "А spaceberry plant, grown оса ly" , [irnages.e lectricall , irnages .ha lf_s hadow, 14: [ irnages. electrical2, 15 : 16: [ irnages. cact us, irnages . cact us_shadow, [irnages.shrub, irnages.shrub_sha dow, ::.2: О " Электрические " Электрические " Космическая системы для работы космической станции " ], irnages. half_shadow, системы петрушка . для работы Слегка космической " Ай! подвяла, Это же но все станции " ], кактус! еще "], \ растет! " ], подrОТОВКА О&ОРУДОВАНИll ДЛll КОСМИЧЕСКОЙ СТАНЦИИ 147
17: " Водоочистные [images.pipesl, images.pipesl_shadow, \ трубы " ], 18: [images.pipes2, images.pipes2_shadow, 19: [images.pipes3, images.pipes3_shadow, 20: [images.door, images.door_shadow, " Трубы систем " Трубы б систем жизнеобеспечения " ], Открывается рабочий 21: 23: " Безопасный выход. космонавт безопасности надел доступа " + " Дверь открывается [images.door, images.door_shadow, карта доступа " + PLAYER_NAME + [images.door, images.door_shadow, двумя \ \ доступа " \ шлюза . людьми ." ], " Закрыто . Нужна \ " Закрыто. Нужна \ " Закрыто . Нужна \ " а " ], FRIENDl_NAМE], [images.door, images.door_shadow, карта + FRIEND2_NAМE], 25: [images.door, images.door_shadow, 26: [images.door, images.door_shadow, 27: [images.map, images.full_shadow, 28: " Сказано , что крушение произошло здесь : " \ + str (LANDER_SECTOR) + " // Х : " + str (LANDER_X) //У :" + str (LANDER_Y)], [images.rock_large, images.rock_large_shadow, " Закрыто. Можно " Запертая ~ если скафандр ." ], целях карта 24: автоматически , [images.door, images.door_shadow, В 22: жизнеобеспечения " ], попасть дверь в из центра инженерный управления полетами " ], отсек. " ], + \ 11 " Порода . Поверхность похожа на точильный камень " , " порода " ], 29: [ images.rock_small, images.rock_small_shadow, 30: 31: [images.crater, None , [images.fence, None , 32: [images.contraption, images.contraption_ shadow, 33: [images.robot_arm, images.robot_arm_shadow, 34: [images.toilet, images.half_shadow, " Небольшой , но " Мелкоячеистый " Результат для марсианской на Защищает станцию эксперимента . подъема породы " ], поверхности от Мягко планеты " ], пыльных вибрирует " ], тяжестей " ], " Сверкающий 35: 36: [images.sink, None , " Раковина с текущей [images.globe, images.globe_shadow, 37: [images.science_lab_taЫe, 38: [images.vending_machine, images.full_shadow, 39: [images.floor_pad, None , 40: [images.rescue_ship, images.rescue_ship_shadow, 41: [images.mission_control_desk, images.mission_control_ desk_shadow, " Стол для " Торговый " Нажимная глобус анализа автомат . плита , " Спасательная rЛABAS бурь " ], \ унитаз " ], " Гигантский 148 кусок " Кратер забор . научного " Манипулятор чистотой тяжелый планеты . грунта Требует чтобы Слегка водой " , " краники " ], светится " ], None , и пыли с поверхности денежку ." , никто не " торговый выходил планеты " ], автомат '' ], один ." ], шлюпка !" ], " Панели ЦУПа ." ],
42 : [ images . button , images.button_shadow , 43 : [ images . whiteboard , images . full_shadow , " Кнопка , " Доска и на для время открывающая записей во время дверь в мозговых инженерный штурмов отсек ." ] , \ переговоров ." ] , 44 : [ images.window , images.full_ shadow , 45 : [ images . robot , images . robot_shadow , 46 : [ images . robot2 , images . robot2_shadow , " Из окна открывается вид на поверхность планеты. " ] , " Робот-уборщик , выключен ." ] , " Робот для ожидает 47 : исследования планеты, [ i mages . rocket , images . rocket _ shadow , одноместная 48 : 49 : 50 : 5 1: 52 : поверхности \ инструкций ." ], " Ремонтная [ images . toxic_ f l oor , None , " Ядовитая жижа - не [ images. drone , None , " Дран-курьер " ] , [ images . energy_ball , None , " Сгусток плазмы [ images . energy_ ball2 , None , " Сгусток плазмы [ images . computer , images . computer_ shadow , " Рабочая станция космической для управления наступай !" ] , опасно! " ] , опасно !" ], системами станции. " ] , 53 : [ images . clipboard , None , 54 : [ images . bubЫe_gum , " Доска-планшет . " Кусочек \ шлюпка ." ], Кто-то липкой изрисовал ее ." , " доска-планшет " ] , None , жвачки . Аромат космической ягоды ." , " жвачка " ], 55 : [ images . yoyo , None , и пластика . Не " Игрушка подвержена из тонкой , гравитации. " , 56 : + "а"] , [ images . thread , None , 57 : [ image s . need l e , None , 58 : [ images . threaded_needle, None , 59 : [ images . canister , None , прочной нити\ " йо-йо " + PLAYER_N AМE " Кусок тонкой " Острая " Игла игла кактуса " Баллон баллон 60 : с с прочной нити " , кактуса " , с " игла нитью " , воздухом , нити " ], кактуса " ] , " иголка с ниткой " ] , негерметичен ." , " негерметичный \ воздухом " ], [ images . canister , None , " Похоже , с теперь утечки нет !" , " герметичный баллон\ воздухом " ], 6 1: [ images . mir ro r, None , 62 : [ images.bin_ empty , None , 63 : [ images . bin_ful l, None , 64 : [ images. rags , None , " Зеркало " Почти " Тряпка , если отбрасывает новый " Тяжелый 65 : " кусок бак , бак из круг легкого полный пропитанная нужно !" , света " тряпка воды " , маслом. в на стены ." , пластика " , " бак с Возьми " зеркало " ], " бак " ] , водой " ], за уголок , \ масле " ], [ images.hammer , None , " Молоток . Пригодится , чтобы сломать что-нибудь ... " , подrОТОВКА О&ОРУДОВАНИll ДЛll КОСМИЧЕСКОЙ СТАНЦИИ 149
" молоток " ] , 66 : 67 : [ images . spoon , None , " Большая [ images . food_p ouc h, None , " Пакетик с обезвоженной столовая пищей . Нужна ложка " , " ложка " ], вода. " , " сухпаек " ] , 68 : [ images . food , None , 69 : 100% энергии ." , " готовая еда " ], [image s .boo k, None , " На обложке " Пакетик ' Не с едой. паникуй !' . Используй Обнадеживает !" , 70 : [images . mp3_p l ayer , None , 71 : [images . l an der , None , "М Р3-плеер черном с ящике его , последними есть чтобы этой получить книги \ написано \ " книга " ], хитами " , " Небольшой "МР3-плеер " ], посадочный радиопередатчик. " , модуль . В его " посадочный модуль " ], посадочного \ 73 : 74 : [i mages .gps_module , None , " GРS-модуль " , " GРS-модуль " [images . positioning_system, None , " Неработающая \ навигационная система. Необходимо вставить \ ], 75 : [images .positioni ng_sys t e m, None , 72 : [images . ra di o , None , модуля " , GРS-модуль ." , " Рабочая " Радиопередатчик из " радиопередатчик " ] , " детали навигационная системы навигации " ] , система " , " навигационная \ " Ножницы. Слишком чтобы система " ] , 76 : [images . scissors , None , резать . Может , их как-то заточить? " , 77 : [images . scisso r s , None , 78 : [i mages . credit , None , 79 : [images . access_card , None , " Карта доступа " + PLAYER_NAМE + [images . access_ card , None , " Карта доступа " + FRI ENDl_NAME, [images . access_car d, None , " Карта доступа " + FRIEND2_NAME , " Острые " Монетка 80 : 8 1: ножницы . для Осторожно! " , торгового " острые автомата " , тупые , " тупые \ ножницы " ] , ножницы " ] , " монетка-кредит " ] , "а" , " карта доступа " ] , " карта доступа " ], " карта доступа " ] О i t ems_ player_ may_ carry = list ( range (53 , 82 )) # Числа ниже относятся к полу , нажимной плите , грунту , ядовитой жиже . 0 items_pl ayer_may_ stand_ on = it ems_p l ayer_may_ca rry + [0 , 39 , 2 , 48] ############### ## ПОМЕЩЕНИЕ ## #### # #### ##### # --п пу к: Л истинг 5-8. Код объектов для игры «Побег» Некоторые объекты списка з ан и мают больше одной стро­ ки О. Это нор м ально, ведь Python зна е т о том, что список не завершен до те х пор, пока не увиди т закрывающ у ю скобк у. Чтобы ра зделить строку (или любой д ругой фрагмент кода) 150 rЛАВА 5
на части, используй символ 5-8 \ в конце строки 6. В листинге строка разбивается для того, чтобы код гармонично С!\Ю­ трелся на страницах книги: но на экране он может прости­ раться сколь угодно далеко вправо. Объект это карта, на которой указано место круше­ 27 - ния посадочного модуля. Его подробное описание включает в себя переменные, которые ты задал(а) в листинге 5-5 для определения расположения посадочного модуля. Функция str () используется для преобразования числовых значений этих переменных в строки, чтобы их можно было объединить с другими строками для получения подробного описания @ . Также мы создали некоторые дополнительные списки, которые нам вскоре понадобятся: carry items _ player _ may _ хранит количество предметов, которые игрок может подобрать О . Это объекты с 53 по 81. Поскольку они сгруп­ пированы, мы можем настроить список items _ player _ с помощью диапазона. Диапазон may _ carry - это после­ довательность чисел от некоторого начального до некоторого последнего, из которого мы вычитаем едини­ цу. (В главе мы уже работали с диапазонами.) Мы можем 3 превратить этот диапазон в список при помощи кода list (range (53 to 82)). сок всех чисел от 53 до В результате у нас получится спи­ 81. Если хочешь добавить еще какие - нибудь предl\lеты, кото­ рые игрок сможет поднимать и переносить, добавля1':"'I их в ко­ нец этого списка. Например, чтобы добавить новые объекты под номерами 89 и 93, воспользуйся функцией player _ may _ carry 93]]. = list (range (54, items _ 82)) + [89, Кроме того, можно просто добавлять новые объекты в конец списка, расширяя диапазон, который используется для настройки списка items _ player _ may _ carry. Еще один новый список stand _ on, - это items _ player _ may _ который содержит указания, разрешено ли иг ­ року стоять на том или ином объекте 0. Игроки могут сто­ ять только на достаточно маленьких предметах, чтобы их можно было поднять, а также на полу разного типа. Чтобы составить этот перечень, мы добавляем в список player _ may _ carry i tems _ номера предметов, которые отно­ сятся к тому или иному виду напольного покрытия. подrОТОВКА О&ОРУДОВАНИll ДЛll КОСМИЧЕСКОЙ СТАНЦИИ 151
После того как ты закончишь ввод кода из листинга 5-8, считай, что раздел ОБЪЕКТЫ игры « Побег » полностью го­ тов! Однако наша игровая карта пока пустует. В главе 6 мы исправим этот недочет. YЧEliHAlll МИССИll No 4 Поэкспериментируй с некоторыми новыми объектами, которые мы только что добавили в игру. Попробуешь изменить код сле­ дующим образом? Заменить высокий книжный шкаф на почти новый бак из легкого пластика (объект 62)? Заменить космическую ягоду на небольшой, но тяжелый кусок марсианской породы (объект 29)? Заменить стул с мягким сиденьем на ядовитую жижу (объ­ ект 48)? Чтобы понять, с помощью какой инструкции размещать объект, ты можешь либо использовать координаты в уже существующем коде, либо посмотреть номера объектов в словаре objects (на эк­ ране или в списках, с которыми мы работали в этой главе). Запу­ сти программу и проверь результат. rотов ли ты к ПОЛЕТУ? Отметь следующие пункты, если чувствуешь, что материал этой главы тебе понятен. D Чтобы получить информацию из словаря, необходимо использовать ее ключ. Ключом может быть как слово, так и число. Также ключ может хра11ит~.ся в переме111юй. D Если ты попытаешься использовать ключ, которого нет в словаре, программа выдаст ошибку. D Чтобы избежать этой неприятной ситуации, проверь, есть ли ключ в словаре, прежде чем программа попытается к неr.1у обратиться. 152 rЛABAS
О Списки можно помещать в словари. Чтобы получить доступ к элементам такого списка, используй ключ словаря и индекс элемента в списке. Например: planets ["Земля"] [1]. О В игре «Побег» используется словарь objects, где хранит­ ся полная информация обо всех объектах игры. В этом сло­ варе каждый элемент представляет собой список. О Используй индекс элемента в списке, чтобы получить доступ к файлам с изображением объекта и его тени, а также к длинному и короткому описанию предмета. ОБСУЖДЕНИЕ МИССИИ Ниже представленьr ответы на учебные миссии из этой главы. УЧЕБНАЯ МИССИЯ № 1 Убедись, что ты поставил(а) запятую после записи данньrх о Нептуне и расставил(а) кавычки и двоеточия в правильных местах в строке инсрормации о Плутоне. = planets к {"Меркурий": "Самая маленькая планета, ближайшая Солнцу", "Венера": "День на Венере длится примерно 243 земных\ дня", "Земля": есть жизнь" "Единственная известная планета, на которой\ , --пропуск-- " Нептун". " Плутон" : "Самый дальний от Солнца ледяной "Крупнейшая карликовая планета :в гигант" , Солнечной\ системе" УЧЕБНАЯ МИССИЯ № 2 Измени код следующим образом. while True: query = input("Пpo какую планету if query in planets.keys(): print(planets[query] [О]) print("Ecть print else: ли у нее ( 'Сколько у планеты print("Heт кольца? информации! лун? ты " хочешь узнать? ") planets[query] [1]) " , planets [query] [2]) Извини!") подrОТОВКА О&ОРУДОВАНИll ДЛll КОСМИЧЕСКОЙ СТАНЦИИ 153
УЧЕБНАЯ МИССИЯ № Э Для выполнения миссии ты можешь создать помещение с любым интерьером. Мой совет : удали все инструкции, которые размещают объектьr в помещении, и вместо них используй указанные ниже. Запусти программу и посмотри, что изменилось! room_map[2] [6] room_map[l] [9] room_map[l] [1] room_map[l] [З] 12 10 7 1 УЧЕБНАЯ МИССИЯ № 4 Отредактируй код в разделе ОБЗОР следующим образом: room_map [2] room_map [2] room_map[l] room_map [1] room_map[l] room_map [1] [4] [6] [1] [2] [8] [9] 7 48 62 9 29 10
О&УСТРОЙСТВО КОСМИЧЕСКОЙ СТАНЦИИ В главе 5 мы добавили информацию обо всем оборудовании, которое будем использовать на протяжении нашей миссии. В этой главе мы займемся установкой части этого оборудования, а затем с помощью инструкций в разделе ОБЗОР пройдемся по разным помещениям станции, а также выйдем на поверхность планеты. Впервые у тебя появится возмож­ ность изучить внешний вид станции, кото­ рая вот-вот станет твоим домом. О&УСТРОЙСТВО КОСМИЧЕСКОЙ СТАНЦИИ 155
ИЗУЧЕНИЕ CЯOBAPJI ДАННЫХ ДЯJI ДЕКОРАЦИЙ На космической станции есть два типа объектов. • Декорации - это оборудование, которое остается на одном и том же месте на протяжении всей игры « Побег » . Например, мебель, трубы и электронные устройства. • Реквизит - это предметы, которые могут появляться, исче­ зать или перемещаться во время игры. Напри.мер, сюда относятся вещи, которые игрок может создавать и подби­ рать. К реквизиту также можно отнести двери, которые в открытом состоянии видны на экране, а в закрыто.l\1 исчезают. Данные, которые относятся к размещению предметов , и данные, которые относятся к реквизиту, хранятся отдель­ но и организованы различным образом. В этой гл аве мы до­ бавим только данные для декораций. К этому моменту наша программа уже «Знает», какие изображения и описания относятся к тому или иному объ­ екту, поскольку вся эта инфорJ1.1ация находится в словаре objects, который мы создали в главе 5. Теперь наJ\1 нужно сообщить программе, где мы хотим на нашей космической станции разместить объекты декораций. Для этого нам по­ надобится новый словарь - scenery. И вот как будет вы­ глядеть в этом словаре запись о комнате: номер_помещения : [ [номер_объекта , у, х] , [номер_объекта , у, х] ] Ключами здесь выступают номера помещений. Каждой комнате-номеру соответствует список, заключенный с двух сторон в квадратные скобки. Каждый элемент в этом спис­ ке - это еще один список, который сообщает программе, где именно в помещении должен разместиться тот или ино~':"I объект. Чтобы тебе было легче понять, где начинается и за­ канчивается код каждого из них, я выделил один объект красным цветом, а другой зеленым. 156 rЛАВА6
О каждом из объектов мы можем сказать следующее. Номер объекта совпадает с числом, который использует­ ся в качестве ключа в словаре 5 objects. Например, число обозначает стол. Положение объекта по оси у в помещении, то есть отно­ сительно его передне1':'1 и задней стены. Дальняя стена, как правило, находится на строке О, поэтому обычно мы начинаем раз1\!1ещать объекты с первой стро1п1. Макси­ мально возможное число равно высоте помещения минус 2: первая единица вычитается пото1\!1у, что нуr·лерация объектов на карте начинается с нуля, а вторая единица нам не нужна из-за пространства, занимаемого передней стеной. Я рекомендую оставлять еще немного больше ме­ ста в передней части помещения, так как передняя стена '1\•IОжет заслонять собой другие предi\1еты. Размер комнаты можно проверить с ПОJ\ЮЩЬЮ кода рым ты работал(а) в главе GAME - МАР, с кото­ 4. Положение объекта по оси х обозначает, на каком рас­ стоянии от левой до правой стены должен находиться объект. Аналогично стена, как правило, распо л ожена в столбце О. Максимально возможным для размещения числом здесь будет значение, равное ширине помещения минус 2. 1 о 1 1 1 2 1 3 .4 о у 1 р 2 3 и д 1 1 Рис. 6.1. Пример помещения на космической станции. Снимок помещения в игре (слева) и его карта (справа). У= унитаз, Р =раковина, И= игрок Чтобы лучше понять, почему используются именно та­ кие значения, обрати внимание на рисунок 6.1, на котором в виде снимка и карты изображено одно из помещений кос­ мической станции. На этом рисунке раковина (Р) О&УСТРОЙСТВО КОСМИЧЕСКОЙ СТАНЦИИ 157
находится во второй строке, поэто:му ее положение по оси у равно 1. Напомню, что стена, расположенная в первой строке, находится в положении по оси у, равном О. Положение ра­ ковины по оси х равно 3. Слева от нее есть две пустые ячей­ ки (плитки), таким образом, по оси х стена находится в по­ ложении, равном О. Давай пооютрим на данные этого помещения. Пока не вводи этот код. Чуть позже я предоставлю тебе все необ­ ходимые данные для декораций. scenery = { -- пропуск -- 3 О: [[3 4,1,1], (35,1,3]], --пропуск-- } Этот код хранит информацию об объектах, расположен­ ных в помещении 30. В пОJ1лещении 30 есть объект NO 34 - унитаз, расположенныi:'I в левом верхнем углу с координата­ ми по оси у № 35 - - 1 и по оси х тоже 1, и объект раковина, расположенная довольно близко к туале­ ту, с координатами по оси у, равными ми 1, и по оси х, равны­ 3. Ты можешь поместить в комнату один и тот же объект несколько раз, добавив список для каждой позиции и ис­ пользуя один и тот же номер объекта. Наприrv1ер, если хо­ чешь, ты можешь заполнить все поN1ещение унитазами, рас­ положив их в разных местах, хотя выглядеть это будет довольно странно . Включать стены в код декораций не нужно, поскольку, как ты уже наверняка за:метил(а), программа автоматически добавляет их в помещения, когда создает список room _ map. Несмотря на обилие скобок в коде каждого элемента в списке, разобраться здесь довольно легко. Скобки помогут тебе понять, сколько предметов находится в помещении, какие числа относятся к номерам объектов, и какие ординатам. 158 rЛАВА6 - к ко­
ДО&АВЛЕНИЕДЕКОРАЦИЙ Откройф айл дена в главе listing5 -8.py 5). (итоговая версия листинга приве­ Здесь находится карта игры и данные об объ­ ектах. Д а вай внесем в этот файл сведения для декораций. Эта информация содержится в листинге 6-1. Добавь но­ вый ра з дел с названием ДЕКОРАЦИИ перед разделом ПОМЕЩЕ ­ НИ Е. Убедись, что скобки и запятые расставлены правильно. Не забудь, что для каждого элемента декорации требуется список из трех чисел, а списки друг от друга отделяются за­ пяты .ми. Если ты не хочешь вручную набирать код, восполь­ зуйся фа1': '1лом data-chapteYб.py, который находится в папке listings. В нем ты найдешь словарь декораций, который можно просто скоп и ровать и вставить в свою программу. --ПР' )[]'У С К -- items_player_may stand_on items_player_may_carry t [О, 39, 2, 48] ############### ## ДЕКОРАЦИИ ## ############### # # Декорации - это объекты, между комнатами . #номер помещения : # ПОЗИЦИЯ ПО ОСИ [[номер которые объекта, невозможно позиция по перемещать оси у, Х] ... ] scenery = { 26 : [[ 39 , 8 , 2]] , 27 : [ [33 , 5 , 5 ], [33 ,1, 1] , [33 , 1 , 8 ], [ 4 7 , 5 , 2 ], [ 4 7, 3 , 10] , [ 4 7, 9 , 8] , [ 42 , 1 , 6] ], 28 : [ [27 , 0 , 3 ], [41 , 4 , 3] , [ 41,4, 7] ], 29 : [ [7 , 2 , 6] , [6 , 2 , 8] , [12 , 1 , 13 ], [44, 0 ,1 ] , [ 36 ,4,1 0 ], [ 10 , 1 ,1], [ 1 9 , 4 , 2] , [17 , 4 , 4]] , 30 : [ [34 ,1, 1] , [35 ,1, 3]] , 31 : [ [ 11,1, 1] , [19 ,1, 8] , [ 4 6 , 1 , 3]] , 32 : [[ 48 , 2 , 2 ], [48 , 2 , 3] , [ 4 8 , 2 , 4 ], [ 4 8 , 3 , 2 ], [ 48 , 3 , 3 ], [ 48 , 3 , 4] , [48 , 4 , 2] , [48 , 4 , 3 ], [4 8 , 4 , 4] ], 33 : [[1 3 ,1, 1 ], [ 13 ,1, 3] , [13 , 1 , 8 ], [13 , 1 , 10 ], [ 4 8 , 2 ,1], [ 48 , 2 , 7] ' [ 4 8 , 3 , 6] ' [ 4 8 , 3 , 3 ]]' 34 : [[ 3 7, 2 , 2 ], [ 32 , 6 ,7], [ 3 7, 10 ,4], [28 , 5 , 3 ]], 35 : [[1 6 , 2 , 9] , [16 , 2 , 2] , [16 , 3 , 3 ], [ 1 6 , 3 , 8 ], [1 6 , 8 , 9 ], [16, 8 , 2] ' [ 16, 1, 8] ' [ 1 6 , 1 , 3 ]' [ 1 2, 8 , 6] ' [ 1 2 , 9 , 4] ' [1 2 , 9 , 8 ]' [1 5 , 4 , 6] , [ 1 2 , 7 , 1] , [ 1 2 , 7 ,11 ] ], 36 : [ (4 , 3 , 1] , [9 , 1 , 7] , [8 , 1 , 8] , (8 , 1 , 9] , [5,5, 4] , [ 6 ,5, 7 ], [1 0, 1 , 1] , [12 , 1 , 2] ], 37 : [[ 48 , 3 , 1 ], [ 4 8 , 3 , 2] , [4 8 , 7 , 1 ], [ 4 8 , 5 , 2 ], [ 48 , 5 , 3 ], [ 48 ,7, 2] , ( 48 , 9 , 2] , (4 8 , 9 , 3 ], (4 8 , 1 1,1], ( 48 ,11, 2 ]], О&УСТРОЙСТВО КОСМИЧЕСКОЙ СТАНЦИИ 159
38: 39: 40: 41: 44: 45: 46: 47: 48: 49: 50: [[43,0,2], [6,2,2], [6,3,5], [6,4,7], [6,2,9], [45,1,10]], [[38,1,1], [7,3,4], [7,6,4], [5,3,6], [5,6,6], [6,3,9], [6,6,9], [45,1,11], [12,1,8], [12,1,4]], [[41,5,3], [41,5,7], [41,9,3], [41,9,7], [13,1,1], [13,1,3], [42,1,12]], [[4,3,1], [10,3,5], [4,5,1], [10,5,5], [4,7,1], [10,7,5], [12,1,1], [ 12, 1, 5]], [[46,4,3], [46,4,5], [ 18, 1, 1], [ 19, 1, 3], [19,1,5], [52,4,7], [ 14, 1, 8]], [[48,2,1], [48,2,2], [48,3,3], [48,3,4], [48,1,4], [ 48, 1, 1]], [[10,1,1], [4,1,2], [8,1,7], [9,1,8], [8,1,9], [5,4,3], [7,3,2]], [ [ 9, 1, 1], [ 9, 1, 2], [ 10, 1, 3], [ 12, 1, 7], [5, 4, 4], [ 6, 4, 7], [ 4, 1, 8]], [ [ 17,4,1], [ 1 7, 4, 2], [ 1 7, 4, 3], [ 17, 4, 4], [ 17, 4, 5], [ 1 7, 4, 6], [ 1 7, 4, 7], [ 17,8,1], [ 17, 8, 2], [ 17, 8, 3], [17,8,4], 17,8,5], [17,8,6], [17,8,7], [14,1,1]], [[14,2,2], [14,2,4], [7,5,1], [5,5,3], [48,3,3], [48,3,4]], [[45,4,8], [11,1,1], [13,1,8], [33,2,1], [46,4,6]] } checksum = О check_counter = О for key, room_scenery_list in scenery.items(): for scenery_item_list in room_scenery_list: О checksum += (scenery_item_list[OJ * key + scenery_item_list[l] * (key + 1) + scenery_item_list[2] * (key + 2)) check_counter += 1 print (check_counter, " (количество декораций) " ) 6 assert check_counter == 161, " Декораций должно быть 161 предмет " ~ assert checksum == 200095, " Ошибка в данных декораций " рrint ( " Контрольная сумма декораций : " + str (checksum)) # # # #### # ### # # ## ## ГО!v1Е'Ц:-'НИЕ ## ############### - пропуск Листинг 6-1. Код для декораций Сохрани свой список в файл с именем listingб - 1.py и запу­ сти его с помощью команды pgzrun listingб-1.py в обо­ лочке командно~':'~ строки. Поскольку мы просто добавили в программу данные, но не оставили никаких указаний, что с ними нужно делать, результат выполнения кода оста­ нется прежним. Однако если при ввводе данных была до­ пущена ошибка, программа прекратит работу и появится сообщение о проблеме. В этом случае вернись обратно к коду и дважды сверь его с тем, что указан в книге. 160 rЛАВА6
Прежде всего убедись, что ты правильно ввел(а) данные контрольной суммы! €) Вторая часть этого списка представляет собой некую предосторожность, которая называется контрольной сум­ мой. Здесь проверяется наличие и правильность всех дан­ ных, выполняются некоторые расчеты, а затем результат све ­ ряется с правильным ответом. Если во введенных тобой данных есть ошибка, этот фрагмент кода прервет работу програмJ\,tы, и игра не будет работать до тех пор, пока ты не исправишь проблему. Так твоя программа будет защище­ на от некоторых ошибок. (Но, конечно, не всех.) Для проверки данных программа использует инструк­ цию assert. Первая инструкция проверяет, есть ли в про­ грамJ\1е нужное количество элементов данных. Если нет, программа остановится и отобразит сообщение об ошиб­ ке 6 . П рограмма также проверяет, соответствует ли кон­ трольная сумма (результат вычисления) ожидаемому числу, и в случае, если это не так, останавливает программу €). Обрати внимание, что одна из инструкций в листинге занимает три строки О : 6-1 Python знает, что работа с инструк­ цией не закончена до тех пор, пока не появится закрываю­ щая скобка. СОВЕТ Если ты хочешь изменить данные декораций, перепроектиро­ вать помещения или добавить собственные комнаты, тебе при­ дется отключить проверку контрольной суммы. Это связано с тем, что вычисление контрольной суммы будет основано на других измененных тобой данных, поэтому результат вы­ числений не совпадет с ожидаемым, а программа не запустит­ ся. Чтобы не удалять этот фрагмент кода, просто поставь символ # перед строками с инструкциями их выключить. Как ты уже знаешь, символ asse r t 6,€), # чтобы используется для обозначения комментариев, поэтому Pythoп игнорирует все, что введено после него в той же строке. Запомни этот прием, он обязательно пригодится тебе во время создания и тестиро­ вания программы. О&УСТРОЙСТВО КОСМИЧЕСКОЙ СТАНЦИИ 161
ДО&АВЛЯЕМ оrРАЖДЕНИЕ ПО ПЕРИМЕТРУ ПОВЕРХНОСТИ ПЛАНЕТЫ Возможно, ты уже заl\·Iетил(а), что мы еще не добавили деко­ рации для «Комнат» с 1 по 25. с комнаты под номером Наши данные начинаются Напомню, что первые 26. 25 лока­ ций находятся снаружи, на поверхности планеты. Хотя у этих «помещений» и нет стен, для простоты мы будем называть эти пространс:гва Иl\Iенно так. На рис. показана карта «помещений» с 6.2 1 по 25. Снару­ жи эти «помещения» 01<ружены забороl\1, который на рис. 6.2 изображен пунктирной линией. Ограждение не позволяет игрокам выходить за пределы территории ком­ плекса и игровой карты. Нам необходимы заборы в следующих местах. • • • Левый край «помещений» 1, 6, 11, 16, 21. Верхний кра1':'1 «помещений» 1, Правый край «помещений» 2, 3, 4, 5. 5, 10, 15, 20, 25. В каждом «помещении», расположенном на поверхности планеты, присутствует одна декорация поверхности, кото­ рая случайным образом выбирается из небольшо1':'r группы подходящих картинок: камни, кустарники и кратеры. С точ­ ки зрения игрового процесса не столь важно, где располо­ жены эти предметы, поэтому их можно генерировать слу­ ча1':'1ным образом. В листинге показан код, который генерирует случай­ 6-2 ные декорации для поверхности планеты, а также добавляет ограждения. Добавь этот код в конец только что созданного ра1лела ДЕКОРАЦИИ и сохрани ре::зую,тат н файл с именем listingб-2.py. Как обычно, ты можешь воспользоваться 1<0мандой pgzrun listingб-2 .ру, чтобы проверить про­ грамму на отсутствие ошибо1<. --п]Jопуск-- рr int ("Кон грплы-1с~я сумма for room in range (l, # 162 в локациях rЛАВА6 планеты . 26): ,цек~)рс.L,ий: # "+ str(checksum)) Добавление случайных декораций
О if room != 13: # Пропуск помещения 13. scenery_item = random.choice([16, 28, 29, 30]) scenery[room] [[scenery_item, random.randint(2, 10), random.randint(2, 10) ]] 6 ~ # Используем # планеты. циклы , чтобы установить ограждение на поверхности О for room_coordinate in range (O, 13): for room_number in [1, 2, 3, 4, 5]: # Верхнее ограничение @ scenery[room_number] += [[31, О, room_coordinate]] б for room_number in [1, 6, 11, 16, 21]: # Ограничение слева О scenery[room_number] += [[31, room_ coordinate, 0]] for room_number in [5, 10, 15, 20, 25]: # Ограничение справа 0 scenery[room_number] += [[31, room_coordinate , 12]] 0 ID> del scenery[21] [-1] # из помещения 21 # Удаление последнего ограничения del scenery[25] [-1] # из помещения 25 # Удаление последнего ограничения ############### ## ПОМЕЩЕНИЕ ## ############### Листинг 6-2. Создание случайно выбранных декораций на поверхности планеты Конечно, чтобы получить удовольствие от прохождения игры, необязательно понимать, как она работает, однако если тебе интересно, как все устроено, я постараюсь как можно детальнее объяснить этот код. Перв ая часть листинга 6-2 случайным образом устанав­ ливает декорации. Это происходит благодаря функции random.choice() 6. Как функция вы­ random.randint() дает случайное число (например, при игре в кости), так и random. choice () выдает случайный предмет (например, переход хода или приз). Элемент выбирается из списка 28, 29, [16, 30]. Эти номера объектов обозначают космиче­ скую петрушку, большой кусок породы , маленький кусок породы и кратер соответственно. Также мы добавляем новую запись для «помещения» в словарь scenery €). В ней находится случайно выбранная декорация и случайные значения координат у их, в которых декорация размещается в пределах «помещения», но не слишком близко к его краям. О&УСТРОЙСТВО КОСМИЧЕСКОЙ СТАНЦИИ 163
Оператор ! = О означает «Не равно», поэтому декорации появятся только в том случае, если номер «поме щения » не равен 13. Кто знает, вдруг случится так, что в дальней­ шем при выполнении :миссии нам пригодится свободное ... 6-2 мы место на поверхности планеты Во второй части листинга устанавливаем огра­ ждения. Все участки поверхности планеты имеют высоту и ширину, равную 13 плиткам, поэтому здесь достаточно одного цикла О для построения верхних и боковых огра­ ждений. Значения переменноi:"'r цикла варьируются в диапазоне от О до 12, room _ coordinate и при каждом повторе­ нии цикла части ограждения устанавливаются вверху и по бокам соответствующих «помещений». Внутри щшла для room _ coordinate находятся три room _ number. Первый цикл room _ number 0 цикла вы­ страивает ограждение вдоль верхней строки верхних «поме ­ щений». Мы не будем использовать функцию range (), про­ сто переберем список с помощью цикла. Каждый раз при просмотре списка переменная одно из следующих значений: room _ number принимает [1, 2, 3, 4, 5]. Мы до­ бавляем декорацию в список декораций для «помещения», используя оператор+= 0. Это декорация 31 (забор), она рас­ положена в верхнем ряду «Помещения» (значение по оси у равно О). Переменная room _ coordinate отвечает за раз­ мещение по оси х. Таким образом, наш забор будет распола­ гаться в верхнем ряду «Помещений» с номерами 1-5. room _ coordinate есть еще два цикла room _ number. Первый выстраивает забор в левой части помещений 1, 6, 11, 16 и 21 б . На этот раз программа ис­ пользует значение переменно1':'1 room _ coordinate в каче­ Внутри цикла стве координаты у и значение О в качестве координаты х @. При этом декорации выстраиваются вдоль левого края этих помещений. Второй цикл выстраивает ограждение вдоль правого края помещений пользуется переменная 5, 10, 15, 20 и 25. Здесь также ис­ room _ coordinate для расположе­ ния части ограждения по оси у, а для координаты х исполь­ зуется значение 12. Таким образом, наш забор вытянется вдоль правого края помещений 164 rЛАВА6 0.
Мы не будем выставлять огра­ ждения там , где зона поверхности планеты соприкасается со стеной коолической станции. На рис. пш<азана карта « ПО!'.1ещен11я » 1"' 11 16 1 l 11 - 1 l 16 - 1 l 21 6.3 21. В левом нижнем углу « помеще­ ния» должна располагаться стена, 5 9 10 1 1 12 13 14 15 I 17 18 19 20 1 22 23 24 25 1- 1 поэтому забор здесь не нужен. "1- 4 - 1I 1 Р ис. 6.2. Создание о г ражде- Циклы , которые мы использова­ ли, установили здесь забор, поэто­ му мы воспользуемся инструкцией ния вокруг «помещений», расположенных на поверхности планеты @ для удаления последней декорации , установленной в этом « помещении » и в « помещен1rи » стороне комплекса (см. 25, которое находится на другой рис. 6.2). Гораздо проще установить эти два фрагмента забора , а затем удалить их, чем писать код , который предотвратит установку подобных фрагмен­ тов заборов. С помощью индекса -1 очень удобно обра­ щаться к последнем у элементу списка. о 2 3 4 5 6 а. о Поверхность планеты \D о rt) 7 8 9 12 Стена космической Стена космической станции станции Р ис. 6.3. Карта п оверх н ости планеты, здесь в идно соприкосновение забора со стеной космиче с кой станции О&УСТРОЙСТВО КОСМИЧЕСКОЙ СТАНЦИИ 165
Использование случайных декораций и циклов для раз­ мещения ограждений позволяет нам иметь большую терри­ торию без необходимости добавлять данные для более чеJ\1 200 фрагментов забора и декораций. СОВЕТ Если ты что-то меняешь в игре и не хочешь добавлять в нее случайно выбранные декорации или ограждения в «помеще­ ниях» с 1 по 25, просто удали разделы кода, указанные в ли­ стинге 6-2. ЗАrРУЗКА ДЕКОРАЦИЙ для КАЖдоrо ПОМЕЩЕНИЯ Теперь, когда мы добавили в игру данные о декорациях, я предлагаю написать код, 1-юторы~':'1 позволит нам воочию увидеть их на нашей космической станции! Ты, наверное, помнишь, что функция generate _ map () создает список room _ map для помещения, 1<0торое ты сейчас исследуешь. room _ map используется для отображения поме­ Список щения и навигации в нем. Сейчас функция generate _ map () просто вычисляет разi\1ер помещения и определяет местоположение дверей, а также пола и стен. Нам необходимо добавить код для по­ лучения декораций из нашего нового словаря и добавления их в room _ map. Но сначала мы должны внести в игру одну небольшую, но важную поправку. В разделе ПЕРЕМЕН­ НЫЕ, который расположен в начале программы, впиши но­ вую строку, указанную в листинге 6-3. грамму в файл с именем listingб-3.py. --пропус'К ################ ## г~Е r:м~ш ьт, ## ################ --;:1_r,опуск-- 166 rЛАВА6 Сохрани свою про­
LANDER_SECTOR - random.randint(l, LANDER_X ~ random.randint(2, 11) LANDER_ У= random.randint(2, 11) ТILE 24) SIZE = 30 Н#######Н#### КА.'':'А ## ## ############### --пропуск-- Листинг 6-3. На с тройка переменной T ILE _ SIZE В этой строке мы добавляем переменную, чтобы хранить размер плитки. Благодаря этому приему читать программу становится легче, ведь куда понятнее осмысленная фраза, нежели просто число 30, которое J\1ожет означать что угод­ но. Одного взгляда на фразу TILE SIZE будет достаточно, чтобы понять, зачем нужен этот код. Затем перейди в раздел ПОМЕЩЕНИЕ, который находится перед разделом ОБЗОР. Помести код из листинга 6-4 в конец раздела ПОМЕЩЕНИЕ, так ты разместишь декорации в теку­ щем помещении. Весь код в листинге ции generate _ map (), 6-4 относится к функ­ поэтому мы должны поставить от­ ступ в четыре пробела в первой строке, а затем сделать отступ в остальных строках так, как показано на рисунке. Сохрани свою программу в файл с именем listingб-4.py --nJ ...;пуск -- def generate_map(): --пропуск-- о 6 @) о 0 ~ 6 i f current room in scenery : for t his_scenery in scenery[current_room]: scenery_numЬer = this_ scenery[OJ scenery_y = this_ scenery[l] scenery_x = this_scenery[2] room_map[scenery_y] [scenery_x] = scenery_number 0 image_here = objects [ scenery_number] [О] image_width = image_here .get_width() image_width_in_tiles = int (image_width / TILE_SIZE) 1]) for Q in range (l, image_width_in_tiles) : room_map[scenery_y] [scenery_x + tile_number] =255 tile_numЬer О&УСТРОЙСТВО КОСМИЧЕСКОЙ СТАНЦИИ 167
############# ## ОБЗОР ## ############# пропуск Листинг 6-4. Дополнительный код для функции generat e _ map (), который добавляет декорации для текущего помещения в список r oom _ map Так, давай разберемся. Строка О проверяет, есть ли в сло­ варе scenery запись о текущем поr.лещении. Эта проверка необходима, потому что в некоторых помещениях наше1':'1 игры декораций может не быть, и, если мы попытаемся запросить несуществующий ключ словаря, Python выдаст ошибку. 6 , который перебирает элементы помещения и копирует их в список this _ первом прохождении цикла список this _ ЗатеJ1.1 мы создаем цикл декораций для scenery. При scenery будет содержать список для первой декорации. Во второй раз он будет содержать список для второй декора­ ции и так далее, пока не достигнет последнего предмета об­ становки текущего помещения. Каждой деI{Орации соответствует список, в котором со­ держится номер объекта, а также координаты у их предмета. Программа извлекает эти сведения из списка scenery, this _ используя индексы, и помещает их в переменные scenery _ number €), scenery _ у О и scenery _ х 0. Теперь у программы есть вся информация, необходимая для добавления декорации в список что список room _ map room _ map. Напомню, содержит номера объектов в каждой точке помещения. В качестве индексов списка здесь исполь­ зуются координаты у их помещения. Данная программа ис­ пользует значения переменных scenery _ у и scenery _ х в качестве индексов списка, чтобы поместить э лемент scenery _ number н с11исок room _ map 0. Если бы ширина всех наших объектов равнялась одной плитке , то на этом мы могли бы завершить нашу работу. В реальности некоторые объекты ш ире других и занимают площадь в несколько пл и ток. Например, широкий объект, расположенный на одной плитке, может охватывать еще две плитки справа от него, однако по1{а наша п рограмма « видит » его расположение в пределах лишь одной этой плитю1. 168 rЛАВА6
Что нам нужно добавить в список room _ map для этих дополнительных мест, чтобы программа понимала, что иг­ рок не может ходить по этим плиткам? Чтобы обозначить пространство, в котором нет объекта, но при этом по нему нельзя ходить, я использую число П очему 255? 255. Это довольно большое число, которое не помешает тебе добавить в игру еще немало объектов, ведь твой словарь объектов 1\ЮЖет содержать элемента. 254 Кроме того, это просто красивое число: именно такое мак­ симальное значение можно записать с помощью одного бай­ та данных (и знаешь, это действительно было важно в те времена, когда я только начинал писать игры тогда у компьютера было всего-то около 65 ООО - в 1980-х, байт памяти для хранения вообще всего, включая данные, графику и программный код). Итак, прежде всего нам нужно знать ширину изображе­ ния, чтобы понять, сколько плиток туда поместится. Чтобы получить информацию об объекте из словаря objects б, в качестве ключа мы используем значение переменной scenery _ number. Известно, что словарь objects воз­ вращает информации в виде списка, первы:м элементом ко­ торого является изображение. Поэтому для извлечения изо­ бражения мы используем индекс О и помещаем сведения о картинке в переменную image _ here. Затем, чтобы определить ширину изображения, мы мо­ жем воспользоваться get _ width () в переменную Pyga111e Zero, введя имя функции 0 . Это число мы помещаем image _ width. Поскольку мы хотим узнать, после его имени сколько плиток может вместить изображение, программа делит ширину изображения (в пикселах) на размер плитки (30) и округляет его (до целого числа) ~ - Этот результат нам понадобится для использования в функции range () tID, которая может принимать только целочисленные значения, поэтому мы должны перевести его в целое число. Если это­ го не сделать, значение ш ирины останется числом с плаваю­ щей запятой. Н аконец, мы настраиваем цикл, который добавляет зна­ чение 255 в пус т ые справа от декорации места, где бы ни была расположена плитка tID. О&УСТРОЙСТВО КОСМИЧЕСКОЙ СТАНЦИИ 169
Если ширина изображения составляет делим его на размер плитки (30) в качестве значения переменной ti l es. Затем цикл считает до 2 90 пикселов, мы и сохраняем результат (3) image _ width _ in _ с помощью функции range (). Здесь мы задаем ему диапазон от ременной image _ width _ in _ tiles 1 до значения пе­ @.Мы добавляем значения циклов к координате х объекта , помечая эти пози­ ции в room _ map числом 255. Таким образом, у крупных объектов, которые занимают три плитки, в пустых местах справа от них находится число 255. Теперь наша программа содержит все необходимые деко­ рации и умеет добавлять их на карту помещения. Все готово к выводу изображения. Давай еще немного поработае:м над кодом в разделе ОБ ЗО Р, чтобы наконец-то насладиться дол­ гожданной прогулкой по космической станции. НЕО&ХОДИМЫЕ ПРИrОТОВЯЕНИЯ ДЯЯ ЭКСКУРСИИ ПО КОСМИЧЕСКОЙ СТАНЦИИ Благодаряр азделу О БЗ ОР мы сJ1.южем ОСJ\ютреть все помеще­ ния станции и побродить по карте, пользуясь клавишаr·ли +--, j, l и ---+. Итак, давай еще немного поработаем над этим кодом, чтобы наконец-то полюбоваться всеми д оступными де1<0рациями. Если сейчас в твоем коде в разделе О БЗОР есть какой-ли­ бо вывод декораций на карту room _ map, пока удали его или закомJ1.1ентируй. Это отличное поле для экспериментов с дизайном помещения, но поскольку этот код во всех поме­ щениях устанавливает одни и те же объекты, в данном слу­ чае он нам будет только мешать. Конечно, если ты уже при­ думал(а) какую-то оригинальную обстановку, не удаляй эту часть программы, просто закомментируй. Щелкни мышью и выдели эти строки, а затем выбери команду меню Forшat ~ Соин11еnt Out Region (Форматирование ~ Закомменти­ ровать фрагмент кода) (или нажми сочетание клавиш Alt+3). Символы комментариев появятся в начале выделен­ ных строк, как показано в листинге 170 rЛАВА 6 6-5:
-П[/"JПуск-- ############# ## ОБЗОР ## ############# def draw (): global room_height, pyint(current r~om) generate __map () screen. clear () ## room_map[2] [4] ## room_ map[2] [6] ## room_ map[l] [1] ## room_map[l] [2] ## room_map[l] [8] ## room_map[l] [9] room_width, room_map 7 6 8 9 12 10 п Листинг 6-5. Комментирование кода в разделе ОБЗОР Теперь нам нужно немного изменить код так, чтобы про ­ грамма не выводила изображение на плитках помещения, которые отмечены числом 255. Изображение появится сле­ ва от этого пространства, поскольку в словаре информации об объекте В листинге 6-6 objects нет 255. появилась новая строка, которую тебе нужно добавить в указанное место в разделе О БЗ О Р. Опера­ тор if гарантирует, что инструкции, которые выводят на экран объект, выполнятся только в том случае, если но­ мер объекта не равен ( ! =) 255. После добавления строки отступи четыре пробела от кода , который следует за ней. Отступ помогает эти инструкции относятся к блоку Python понять, что if. Ты можешь либо доба­ вить четыре пробела в начале следующих двух строк, либо выделить их и выбрать команду iо.1еню Fo rш at ~ Indent Reg ioн (Фuрматирuиание ~ ДuGаиить uп: т у11). проr1уск - #########Н## ## Оi'ЗОР ## #########Н## проrтуск - О&УСТРОЙСТВО КОСМИЧЕСКОЙ СТАНЦИИ 171
for in range(room_height): for х in range(room_width): if room_ map[y] [х ] ! = 255 : image_to_draw = objects [ room_map[y] у [х ] ] [0] screen . Ыi t(i mage _to_draw , (top_ left_x + ( х*ЗО) , top_ left_ y + ( у*ЗО) i mage_ to_ draw . get_h eight ())) - пропуск Листин г - 6-6 . Редакт и ру ем ко д в р а зде л е ОБЗ ОР, что бы в местах, отмеченных числом 255, не появлялось изображение УЧЕliНАЯ МИССИЯ No 1 Попробуешь сделать собственный дизайн помещения, изменив декорации? Мы специально оставили помещение 43 пустым, что­ бы ты самостоятельно его украсил(а). Размер помещения состав­ ляет 9 х 9 плиток, поэтому ты можешь размещать объекты в позициях от 1 до 7 в любом из направлений (помни о стене!). При планировании дизайна ты можешь воспользоваться дизайном ком­ наты, которую мы создали ранее в разделе ОБЗОР в главе 5, или придумай любую другую планировку. Не забудь отключить ин­ струкцию assert . Тогда программа не «выбросит» ошибку, если контрольная сумма будет отличаться от данных в таблице. В нашей программе словарь objects (приводится в гла­ ве 5) хранит в себе номера всех объектов. Используй предме­ ты от 1 до 4 7, чтобы убедиться, что в программе нет каких-либо проблем, которые могут вылезти в будущем. Если же ошибки все-таки есть, обратись к моему коду, кото­ рый приведен в следующем разделе. Измени значение пере­ менной current _ room в разделе ПЕРЕМЕННЫЕ на 43, чтобы при первом запуске программы увидеть новую версию поме­ щения. По завершении работы не забудь вернуть переменной cu r rent room значение 31. Настала пора отправиться в п у тешествие по нашей кос­ мической станции. Сохрани програr.лм у в файл с именем listi11g6-6.py и з апусти ее , введя pg z ru n Исполь з уй клави ш и +-, 172 rЛАВА6 j, 1 и----+, чтобы listingб - 6. py. перемещаться
по карте и осмат р ивать комнаты станции. Как и раньше, благодаря коду в разделе О Б ЗОР ты можешь двигаться в лю­ бом направлении, даже если путь тебе преградит стена. Все декорации в помещениях должны находиться на своих местах. Теперь широкие объекты будут отображаться пра­ вильно, и ты снова с.можешь просматривать все пшлещения благодаря изменениям, внесенным в листинг 6-5. Под неко­ торыми объектаi\·tи по - прежнему будет виднеться черный квадрат, потому что Nrы нс установили там напольную плит­ ку, но в главе 8 мы исправим эту ошибку. Теперь у нас есть карта космической станции и декора ­ ции. П ришло время перебраться на нашу космическую базу. В следующей главе мы телепортируемся туда, чтобы, нако­ нец, ступить на п оверхность Марса. rотов пи ты к nonETY? Отметь следующие пункты, если чувствуешь, что материал главы тебе понятен. D Предметы , которые нельзя перемещать в игре, называются декорациями. D В словаре scenery в качестве ключа используется номер помещения, которому соответствует список статичных декораций, расположенных в помещении. D Объект декорации хранится в виде списка, содержащего номер декорации, а также положение по осям у их. D Контрольные суммы позволяют проверить изменение дан­ D Для добавления элементов в словарь ных или неверный их ввод. scenery можно использовать циклы. Часть декораций можно расположить случайным образом. D Функция generate _ map () берет из словаря scenery объек т ы , находя щ иеся в текущем помещении, и помещает их в список room _ map. После этого можно приступать к расстановке предметов в помещении. D Число 255 в room _ map обозначает пространство, которое занимает широкий объект, но сам он в этой точке отсутствует. О&УСТРОЙСТВО КОСМИЧЕСКОЙ СТАНЦИИ 173
ОБСУЖДЕНИЕ МИССИИ Ниже представлены ответы на учебные миссии из этой главы. УЧЕБНАЯ МИССИЯ № 1 Чтобы воспользоваться моим дизайном для помещения 43, добавь строку, указанную ниже, в словарь scenery. В результате должен получиться интерьер, схожий с показанным на рисунке 6.4. --пропуск-- 41: 43: 44: [[4,3,1], [10,3,5], [4,5,1], [10,5,5], [4,7,1], [10,7,5], [12,1,1], [12,1,5]], [[18,1,1], [18,1,4], [14,1,6], [52,4,5], [52,4,2]], [[46,4,3], [46,4,5], [18,1,1], [19,1,3], [19,1,5], [52,4,7], [14,1,8]], --пропуск-- Чтобы программа не останавливала работу при проверке кон­ трольной суммы, закомментируй инструкции assert в разделе ДЕКОРАЦИИ программы: --пpoпycк-- pr in t ( check_coun t e r, "scenery data iterns") ##assert check_counter == 161, " Декораций должно быть 161 предмет " ##assert checkswn == 200095, "Ошибка в данных декораций" print ("Контрольная сумма декораций: " + str ( checkswn) ) --пропуск-- IJ. _:.~.f.j ~ Рис. 6.4. Мой вариант дизайна помещения 43
ПУТЕWЕСТВИЕ НА КОСМИЧЕСКУЮ СТАНЦИЮ Теперь, когда мы оснастили космиче­ скую станцию декорациями, системами жизнеобеспечения и другим оборудованием, пришло время двигаться дальше. В этой главе ты впервые увидишь самого себя на кос­ мической станции и сможешь передвигаться и исследовать помещения. Возможно, поначалу ты почувствуешь себя неуверенно, но вскоре будешь с легкостью передвигаться по все{1 нашей космической базе. Ты у з наешь, как анимировать астронавта и управлять им с клавиатуры. Кроме того , мы напишем с тобой код , который позволит астронавту гулять по станции. На извечны1':'1 вопрос человечества « Есть ли жизнь на Марсе? » теперь есть ответ: д а. ПУТЕШЕСТВИЕ НА КОСМИЧЕСКУЮ СТАНЦИЮ 175
ПРИ&ЫТИЕ НА КОСМИЧЕСКУЮ СТАНЦИЮ Мы будем работать с листингом 6-6 из главы 6. Он станет нашей отправной точкой в этой главе, поэтому открой файл listingб-6.py. Скоро мы добавим в него код, которыi."'1 выведет на экран изображение твоего персонажа, облаченного в скафандр, внутри космической станции. А затем мы научимся пере­ двигаться с помощью клавиш t - , j, l и--+. ОТКЛЮЧЕНИЕ НАВИfАЦИИ ПО ПОМЕЩЕНИllМ В РАЗДЕЛЕ ОБЗОР Ранее, в разделе ОБЗОР, мы использовали клавиши t-, j, l и--+ для отображения различных помещений на карте. Теперь мы будем использовать эти кнопки, чтобы водить астронавта по комнатам станции. Для этого, во-первых, нам нужно отключить существующие элементы управления. Прокрути код вниз до раздела ОБЗОР и выдели инструкции, показанные в листинге Сопниеnt Out Region 7-1. Выбери команду меню Fопиаt ~ (Фор11латирование ~ ЗакомJ\1ентиро­ вать фрагмент кода), чтобы закомN1ентировать эти инструк­ ции, и тогда программа проигнорирует их. (Если хочешь, можешь их даже удалить.) Сохрани свою программу в файл с именем listingl-1.py. ПР' ##def ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## 176 movement( ): global current - room old_ room = current room if keyboard.left : current - room -= l i f keyboard . right : current room += if keyboard . up : current room МАР - WIDTH if keyboard . down : current room += МАР - WIDTH if current room > 50 : current room = 50 if current room < l : current room = 1 rЛ АВА 7
## ## ## if current room != old_room : print( " Tы вошел в комнату :" + str (current_room) ) ## ##clock . schedule_interval(movement , -- Р' у 0 . 08) к-- Листинг 7- 1. Отключение навигации с помощью кода в разделе ОБЗОР А теперь давай добавим код, с помощью которого мы и будем использовать клавиши t - , f, l и ---+для перемещения астронавта. ДО&АВЛЕНИЕ НОВЫХ ПЕРЕМЕННЫХ Предлагаю начать с настройки не1<0торых переменных. Самые важные из них - это начальные координаты, место , куда ты будешь телепортироваться. Как и всегда, \IЫ добав­ ляем переменные в разделе ПЕРЕМЕННЫЕ п рограммы, он находится в начале листинга. Добавь новые с т роки в листинг 7-2. Сохрани программу в файл с именем listingl-2.py. пропуr ТILE SIZE 30 О player_y, player_x 6 game_over False €) 2, 5 PLAYER = { " left " : [images .spacesui t_left , images.spacesuit_left_l, images.spacesuit_left_2, images.spacesuit_left_З, images.spacesuit_left_4 ] , " right " : [images.spacesuit_right, images.spacesuit_right_l, images.spacesuit_right_2, images.spacesuit_right_З, images.spacesuit_right_4 ] , " up " : ] [ images. spacesuit_back, images. spacesuit_back_l, images.spacesuit_back_2, images.spacesuit_back_З, images.spacesuit_back_4 , " down " : [images.spacesuit_fro nt, images.spacesuit_front_l, images.spacesuit_front_2, images.spacesuit_front_З, images.spacesuit_front_4 ПУТЕШЕСТВИЕ НА КОСМИЧЕСКУЮ СТАНЦИЮ 177
О player_direction = "down" 0 player_f rame = О ~ player_image = PLAYER[player_direction] [player_frame] player_offset_x, player_offset_y = О, О Л и стинг 7-2. Добавление переменных для движения игрока В разделе ПЕРЕМЕННЫЕ уже имеется переменная current _ room, значение которо~':'r определяет начальное рас­ положение игрока. (Если во время экспериr.лентов, которые мы проводили в главе room, 6, ты изменил(а) переменную убедись, что ты вернул(а) ей значение здаем новые пере1\1енные, player _ у и current _ 31.) Далее мы со­ player _ х О , в них хранятся координаты твоей начальной позиции в помещении. Как можно видеть, в одной строке мы настраиваем сразу две переменные. Числа помещаются в них в том же порядке, в ко­ тором они перечислены, поэто1\1у значение, равное сваивается переменной player к первой переменной), а 5- 2, при­ _у (первое число относится переменной player _ х. Эти значения будут J\Iеняться по мере твоего перемещения по кос­ мической станции и будут использоваться для проверки тво­ его местоположения, а также для вывода образа твоего игрока в нужном месте. П озиция персонажа измеряется с помощью тех же координат плитки, что и для декораций. Также здесь появляется переменная game _ over 6 , она сообщает о завершении игры. В начале программы мы при ­ сваиваеJ\1 этой переменной значение False False. Значение будет установлено до тех пор, пока игра не закончит­ ся, после чего сменится на True. Программа проверяет зна­ чение этой переменной, чтобы проверить, разрешено ли иг­ року перемещаться. Согласись, было бы странно, если бы игрок продолжал двигаться после своей смерти! Далее мы настроим изображения, чтобы анимировать ходьбуи грока. Анимаt(UЯ - это, по сути, обман зрения. Все начинается с серии почти одинаковых картинок, на ко­ торых нарисованы движения астронавта. Быстрое переклю­ чение изображений создает иллюзию, благодаря которой ты видишь движущуюся картию<у. В нашей игре мы будем ис­ пользовать серию рисунков передвижения астронавта, 178 rЛАВА 7
на которых меняется положение его ног. При быстром пере­ ключении картинок будет создаваться впечатление, что аст­ ронавт двигает ногами. СОВЕТ Ты можешь прямо сейчас увидеть, как двигается наш астро­ н авт. Просто листай страницы и смотри на правый нижний угол книги, где изображен персонаж. Каждое изображение, используемое в анимации, называ­ ется кадром или фреймом. В таблице 7.1 указаны кадры ани­ мации, J{Оторые мы будем использовать. Нумер а ц ию кадров мы начнем с О (положение покоя, астронавт стоит). Когда персонаж движется вверх по экрану, он отдаляется от нас, в этом случае мы будем видеть только его спину. Таблица 7.1. Кnавиwа Кадры для создания анимации астронавта Кадр О Кадр 1 Кадр2 Кадр3 Кадр4 left right up down ПУТЕШЕСТВИЕ НА КОСМИЧЕСКУЮ СТАНЦИЮ 179
В словаре PLAYER @ хранятся кадры анимации. Назва­ ния направлений - up, down, left и right - это ключи словаря. Каждая запись в нем представляет собой список, в котором находится изображение стоящего игрока, плюс четыре анимационных кадра для каждого из направлений ходьбы (см. таблицу 7.1). Словарь PLAYER будет использо­ ваться в соответствии с направлением, в которое смотрит игрок О , а также номером кадра анимации 0 для вывода правильного изображения в тех случаях, когда игрок идет или стоит на месте. Переменная player _ image @ хранит текущее изображение астронавта. СОВЕТ В конце книги в приложении Б приведены наиболее важные для игры «Побег» переменные, поэтому, если ты забыл(а), за что отвечает та или иная переменная, рекомендую обра­ титься к этому материалу. ТЕЛЕПОРТИРУЕМСЯ НА КОСМИЧЕСКУЮ СТАНЦИЮ Приготовься к телепортации! Теперь, когда мы задали начальные координаты, давай напишем код, который обес­ печит твое появление на космической станции. В листинге 7-3 приведены строки, которые необходимо добавить в разделе ОБЗОР. Как я уже говорил, добавлять нужно только новые строки. Весь остальной текст должен остаться прежним. Здесь он показан только для того, чтобы ты мог(ла) сориентироваться в программном коде. Перед первой новой строкой О сделан отступ в восемь пробелов, поскольку она находится и внутри функции, и внутри ци­ кла. Сохрани программу в файл с именеfl.1 listingl-3.py. --прLн1уск-- fоr in range(room_height): for х in range(room_width): f room__map[y] [х] != 255: image to_draw - objects[room_map[y] у screen.Ыit(image_to_draw, 180 rЛАВА 7 [х]] [О]
(top_left_x + (х*ЗО), top_left_y + (у*ЗО) - image_to_draw.get_ О б height())) if player_y == у: image_to_draw PLAYER[player_direction] [player_frame] screen.Ыit(image_to_draw, (top_left_x + (player_x*ЗO)+(player_offset_x*ЗO), top_left_y + (player_y*30)+(player_offset_y*30) - image_to_draw.get_height())) Р' к-- Листинг 7-3. Рисуем игрока в комнате Благодаря этим новым инструкциям персонаж появляет­ ся в помещении. Цикл у отрисовывает ПОI\,1ещение от даль­ ней стены к ближней. Цикл х построчно отрисовывает де­ корации слева направо. После вывода каждого ряда программа проверяет, нахо­ дится ли игрок в этой строке О . Эта инструкция должна располагаться на одном уровне со строкой range (room _ width), for х in без отступа, так как она не относит­ ся к циклу х. Функция запустится только один раз после за­ вершения циклах. Если игрок находится в только что нарисованной про­ граммой строке, в следующей строке кода игрока помещается в переменную 6 изображение image _ to _ draw. Изо­ бражение берется из словаря кадров анимации PLAYER, с учетом направления игрока и номера кадра ани:v1ации. Последняя из новых строк @ отображает игрока с помо­ щью только что настроенно1':'1 переменной draw, image _ to _ которая содержит в себе изображение астронавта. Помимо этого здесь используются переменные с координа­ тами игрока по осям х и у для определения места на экране, где появится изображение. В главе 3 я рассказывал, как рас­ считывается расположение того или иного изображения на экране (см. раздел «Узнаем, где необходиI\ю нарисовать каждый предмет» в главе offset _ ге 7-2. х и 3). Переменные player _ offset _ player _ у настраиваются в листин­ Они нужны нам для позиционирования игрока ме­ жду плитками, когда тот перемещается от одной плитки к другой. Чуть позже мы еще раз вернемся к этой теме. ПУТЕШЕСТВИЕ НА КОСМИЧЕСКУЮ СТАНЦИЮ 181
Приготовься к телепортации! 3... 2... 1... ! Сделай глубо­ кий вдох. Запусти программу с помощью команды listing7-3.py. pgzrun Если телепортация прошла успешно, наш астронавт должен появиться на космической станции (см. рис. 7.1). Если этого не произошло, проверь весь тот код, что ты менял(а) в этой главе. Обрати внимание: после телепортации ты не сможешь двигаться. Терпение! Вскоре мы добавим в нашу программу несколько новых строк кода, после чего этот неприятный эффект снимет как рукой. Рис. 7.1. Астронавт прибыл на станцию! ДО&АВЛЕНИЕ КОДА ДЛЯ ПЕРЕМЕЩЕНИЯ На этот раз мы добавим в программу совершенно новый раздел ЦИКЛ ИГРЫ. По сути, циклы граммы. Функция game _ loop () - это сердце про­ будет выполняться несколько раз в секунду, благодаря чему наш персонаж смо­ жет двигаться. Далее в книге мы добавим сюда дополнитель­ ные инструкции, которые позволят астронавту взаимодей­ ствовать с найденными объектаr.·ш. Добавь следующий код между разделами ПОМЕЩЕНИЕ и ОБЗОР. В листинге 7-4 показано, как это должно выгля­ деть. Сохрани программу в файл с именем listingl-4.py. --r1poпycк-- f or 182 rЛАВА 7 in range(l, irnage_wid~h_in_tiles): roorn_ rnap [ scPnery_y] [ scenery_x + ti le_ numЬer J - 255 tile_numЬer
############### ## ЦИКЛ ИГРЫ ## ############### О б def garne_loop (): global player_x, player_y, current_roorn global frorn_player_x, frorn_player_y global player_irnage, player_irnage_shadow global selected_itern, itern_carrying, energy global player_offset_x, player_offset_y global player_frarne, player_direction «) if garne_over: return О if player_frarne > О: player_f rarne += 1 tirne.sleep(0.05) if player_frarne == 5: player_frarne = О player_offset_x player_offset_y = О О 0 # сохраняем текущую позицию old_player_x player_x old_player_y = player_y игрока @ # двигаемся, если нажата клавиша if player_frarne == О: if keyboard.right: frorn_player_x player_x frorn_player_y = player_y player_x += 1 player_direction = "right " player_f rarne = 1 elif keyboard.left: #elif запрещает # по диагонали frorn_player_x player_x frorn_player_y player_y player_x -= 1 player_ direction "left" player_frarne = 1 elif keyboard.up: frorn_player_x player_x frorn_player_y player_y player_y -= 1 player_direction "up" player_frarne = 1 elif keyboard.down: frorn_player_x player_x frorn_player_y player_y player_y += 1 игроку двигаться ПУТЕШЕСТВИЕ НА КОСМИЧЕСКУЮ СТАНЦИЮ 183
player_direction player_frame = 1 б # # Если игрок очутился переместим его "down" в недопустимом для него месте , назад . # Не удаляй два комментария ниже - они понадобятся позже if room_map[player_y] [player_ x] not in items_player_may_stand_on: #\ or hazard_map [player_y] [player_x] != О: # p l ayer_x = old_player_x player_y = old_player_y player_f rame = О 0 0 if player_direction player_offset_x if player_direction player_offset_x if player_direction player_offset_y if player_direction player_offset_y "right" and player_frame > О: = -1 + (0.25 * player_frame) == "left" and player_frame > О: = 1 - (0.25 * player_frame) == "up" and player_frame > О: = 1 - (0.25 * player_frame) == "down" and player_frame > О : = -1 + (0.25 * player_frame) ############# ## О.'З()Р ## ############# проr,уск- Листинг 7-4. Учим астронавта двигаться В самом конце программы тебе нужно добавить новый раздел СТАРТ, в котором каждые скаться функция game _ loop (). 0,03 секунды будет запу­ В листинге 7-5 находится код, которым необходимо дополнить программу. Перед этой инструкциеI"::'1 не должно быть отступа, потому что она не относится к функции. Инструкции, которые находятся вне функции, Pytlюn выполняет в том порядке, в котором они указаны в программе, сверху вниз. Данная инструкция выполнится после того, как все пере1\,1енные, карта, декора­ ции и реквизит будут настроены, а функции определены. Сохрани программу в файл с именем ГIРО ############### ## СТАРТ ## ## # # # #### # # # ### 184 rЛАВА 7 listingl-5.py.
() clock.schedule_interval(garne_loop, geпerate_rnap 0.03) --пропуск-- Листинг 7-5. Нас т ройка функции g a me _ loop () для периодического запуска Запусти программу с помощью команды listing7-5 .ру. pgzrun В результате ты должен оказаться в поме­ щении станции (как показано на рис. 7.1), по которому мож­ j, l и ----+! Ты, навер­ но перемещаться с помощью клавиш +--, ное, заl'·летил(а), что, когда астронавт движется вверх по экрану, ноги персонажа перестают отображаться. Все эти огрехи исчезнут, когда мы усовершенствуем код для отображения помещений в главе 8. Сейчас, если наш герой выйдет за дверь, программа сра­ ботает не так, как ожидz~лось, однz~ко z~стронавт все же не дол­ жен проходить сквозь стены или J\·1ебель. Но если это все же происходит, перепроверь только что добавленную часть кода. Если проблема остается, присмотрись к той стрш<е, что на­ страивает список items _ player _тау _ stand _ оп, она рас п олагается в конце раздела ОБЪЕКТЫ. РАЗ&ИРАЕМСЯ В КОДЕ ПЕРЕДВИЖЕНИЯ Если ты хочешь просто сыграть в игру и позаниматься укра­ шением комнат, то в это1':'1 глz~ве можешь не углубляться в то, как работает код, просто замени изображения и данные для карт, декораций и реквизита. Что касается кода переl\11ещения персонажа внутри комнат и между ними, с чем мы немного позже будем работать в этой главе, то он должен работz~ть. Но если ты хочешь понять, как все это работает, и узнать, каким образом можно анимировать персонажей в играх, читай даль ш е. Начинается самая захватывающая часть нашей разра­ ботки, потому что этот код - настоящий игровой движок! Поскольку большую часть этого листинга ты уже ви­ дел(а), у тебя может возникнуть ощущение дежавю. В главе 2 для выхода в открытый космос мы работали с кодом для из­ менения положения игрока с помощью клавиш управления и функцией game _ loop () для управления движением ПУТЕШЕСТВИЕ НА КОСМИЧЕСКУЮ СТАНЦИЮ 185
персонажа. Предлагаю освежить воспоI\шнания и посмо­ треть, что нового появилось в листинге В листинге 7-4 в начало нового раздела. Функция interval (),которую (см. листинг дые 0,03 7-4. game _ loop () clock. schedule _ мы добавили функцию 7-5), О мы поместили в конец программы запускает функцию game _ loop () каж­ секунды. Каждый раз, когда выполняется эта функ­ ция, она проверяет, нажата ли клавиша+--, j, l или-----+, либо передвижения героя, и, если это так, программа обновляет позицию персонажа. В начале функции game _ loop () мы сообщаем какие переменные являю т ся глобальными 6 Python, (см. раздел « Разбираемся в коде выхода в открытый космос » главы 1, чтобы вспомнить, зачем они нужны). Некоторые из них мы еще не использовали, но чуть позже они нам понадобятся. Затем мы проверяем значение переменной Если ее значение равно game _ over. True С) , функция game _ loop () завершается без выполнения каких-либо других инструк­ ций, так как игра окончена. Эта переменная не дает игроку пере:мещаться после завершения игры. На данный момент в нашей программе игру закончить нельзя, ведь мы не зада­ вали для этого никаких условий. Функция game _ loop () проверяет, перемещается ли в данную секунду игрок О . Для прохождения одной плитки требуются четыре анимационных кадра. Если игрок дви­ жется, переменная player _ frame будет содержать одно из чисел в диапазоне от 1 до 4, которое обозначает номер используемого кадра анимации. Если игрок идет, програм­ ма увеличивает значение переменной player _ frame на 1, тем самым переходя к следующему кадру анимации. Это означает, что при следующем запуске функция draw () из раздела О Б ЗО Р покажет новую часть анима ц ии. Если значение переменной равным 5, player _ frame становится значит, все анимационные кадры были показаны. В таком случае программа сбрасывает значение переменной player _ f rame до О, тем самым завершая анимацию. Кро­ ме того, программа сбрасывает значение переменных player _ offset _ х и player _ offset _у. Вскоре я объ­ ясню, зачем они нужны. 186 rЛАВА 7
Далее мы смотрим, нажал ли игрок клавишу для запуска новой анимации передвижения персонажа. Прежде чем п о­ зволить игроку двигаться, мы сохраняем его текущую пози­ цию 0 , сохраняя позицию по оси х в качестве значения пере­ менной old _ player _ х и позицию по оси у- в качестве значения переменной old _ player _ у. Мы будем исполь ­ зовать эти переменные для перемещения игрока назад, если он попытается пройти туда, куда не следует, на п ример через колонну в стене. Если затем нажать клавишу со стрелкой @ , программа за ­ действует уже знакомую нам часть кода для изменения зна ­ чения переменных, отвечающих за положение игрока по осям х и у. Позицию игрока мы вычисляем в плитках, то есть в тех же единицах измерения, что используются при размещении декораций. Н е путай их с пикселами, которые мы использовали для измерения в главе 1. Когда игрок нажимает клавишу-----+, программа прибавля­ ет 1 к значению позиции по оси х. Если игрок нажимает клавишу+--, програм.ма вычитает 1. Аналогичный код мы используем для изменения значения по оси у, если игрок на­ жимает клавишу j или 1- Когда персонаж движется, глобальные переменные _ player _ х и from _ player from _у сохраняют позицию, с которой игрок начинал свое движение. Этими перемен­ ными мы воспользуемся позже, чтобы проверить, не полу­ чил ли игрок увечья от подстерегающих его в игре опасно­ стей. В переN1енной player _ direction хранится значение направления, в котором движется астронавт, а пе­ ременной ное 1, player _ frame присваивается значение, рав ­ т о есть первый кадр последовательности ани1\'1ации. Как и в главе 1, мы используем оператор elif для объ­ единения всех заданных ранее проверок на нажатие клавиш. Это гарантирует, что игрок не С!\южет одновременно изме­ нить позиции х и у, чтобы двигаться по диагонали . В нашем трехмерном помещении пере1\"1ещение игрока по диагонали позволило бы ему проходить через препятствия, протиски­ ваясь через слишком узкие для него пространства. После перемещения игрока программа проверяет б , можно ли ему переместиться в ту позицию, которую он ПУТЕШЕСТВИЕ НА КОСМИЧЕСКУЮ СТАНЦИЮ 187
выбрал. Для этого мы используем переменную rоот _ тар, тем самым просматривая объект, находящийся в том месте, в которое перемещается игрок, и сверяем его со списком iteтs _ player _тау staпd _ оп. В листинге есть _ фрагмент кода, который я пока закомментировал и который нам понадобится позже, чтобы игроки не проходили сквозь опасности. Чтобы проверить, присутствует ли тот или иной элемент в списке, мы используем ключевое слово iп. Если слева от него добавить ключевое слово поt, можно проверить от ­ сутс т вие того или иного элеI\,1ента в списке. Следующая строка означает «Если значение объекта в позиции на карте, где стоит игрок, отсутствует в списке предметов, игроку разрешается находиться на ... » if room_map[player_y] [player_x] not in items_player_may_stand_on: Если игрок стоит на чем-то, чего нет в списке iteтs player _тау_ staпd _ _ оп, мы сбрасываем значение пози­ ции игрока по осям х иу на исходное до перемещения значение. П роверка происходит так быстро, что игрок ничего даже не заметит. Если он попытается врезаться в стену, это будет выглядеть, как будто он даже не сдвинулся с точки! Таким способом проще помешать игроку проходить сквозь стены, и нам не придется проверять, допустимо ли выполнение того или иного движения . Программа присваивает переменной player _ f rате значение О, если значения положения игрока должны быть сброшены 0. В этом случае анимация игрока снова будет выключена. Ког[l,а ты нажимаешь клави111у ~,астронавт перемещается на одну плитку вправо. Для того чтобы сделать это изобра­ жение анимированным, нам потребуются четыре кадра, по­ этому астронавт отображается в позиции между двумя плит­ ками, когда воспроизводится эта анимация . Переменные player _ offset _ х и player _ offset _ у используются для хранения позиции астронавта. Эти переменные вычисля­ ются в конце функции gате 188 rЛАВА 7 _ loop () 0 . Функция draw ()
(см. листинг плитки (30 7-3) умножает значения сме щ ения на размер пикселон), поскольку картинки отображаются в пикселах. Например, если смещение составляет ки, астронавт оказывается примерно на 0,25 плит­ 7 пикселов в сторону от центра новой плитки. К ом п ьютер ОI<руглит число, по­ скольку мы не можем использовать половину пиксела. Взгляни на левую часть рис. 7.2. Для первого кадра ани­ мации, когда астронавт идет налево, нам необходимо доба­ вить три четверти плитки к новому положению игрока (0,75). Для второго кадра анимации мы добавляем половину плитки (0,5) к новому положению игрока, и только потом появляется его изображение. Для третьего кадра анимации мы добавляел.1 к новой позиции игрока четвер т ь плитки. =C±IJ-------------[IE I 1 Движение влево 1 1 1 Движение вправо 1 1 1 Исход н ая Смещение ____[lГ'"' К:др 0,75 = оо~:"' i """"о 1 ---+--0,75 0,5 2 - 0,5 0,25 3 -0,25 ~--+---------+-- [( [ r n --+---0 +-- ! ~л~~~~ 1 Смещение Исходная 4 о 1 1 Новая 1 плитка Рис. 7.2 Схема, гд е находится ас т ронавт на всех кадрах а н имации Мы 1\южем рассчитать эти значения смещений, используя номер кадра . Выполнить расчет при перемещении налево !\южно следующим образом: player_offset_x = 1 - (0.25 * player_frarne) ПУТЕШЕСТВИЕ НА КОСМИЧЕСКУЮ СТАНЦИЮ 189
Самостоятельно проверь правиль н ость этой формулы . Н а п р и мер, так будет выглядеть расчет, когда используется второй кад р анимации: 0,25 х 2 = 0,5 1 - 0,5 = 0,5 Н а рису н ке 7.2: 0,5 - это п равильное смещен и е для кадра 2. К огда игрок идет вправо, нам нужно вычесть часть плит ­ ки из позиции игрока, чтобы значение смещения было от ­ р ицательным. Вз гля н и на п равую часть р ис. м ы пр ибавляем значение - 0,75, 7.2 . Для кад р а 1 что п оме щ ает астро н ав т а на три четверти плитки левее его новой позиции. Мы 1\'южем вычислит ь смещение по оси х для шага вп р а ­ во, ис п ользуя номер кадра. В от эта фо р мула: player_offset_x = -1 + (0.25 * player_frarne) УЧЕ&НАЯ МИССИЯ No 1 Сможешь проверить, правильно ли работает эта формула? Воспользуйся ею, чтобы найти значения смещения для кадров 1 и 3 и проверить, соответствуют ли они значениям смещения на рис. 7.2. С1\1е щ ение по оси у происходит так же. Когда астронавт движе т ся вве р х, мы вычисляем сме щ ение п о оси у п о т ой же ф о р муле, что и смещение влево . Когда астронавт движется вниз, мы вычисляем смещение по оси у по той же формуле, что и сме щ е н ие в п раво . Так и м образом, функция • game _ loop () делает следующее. Если и г рок стоит на месте, анимация движения запускается пр и нажатии клав иш и . • Если игрок идет, то программа определяет следую щ ий кадр анимации и позицию перед в ижен и я между плитками , ис пользуя эту инфо р ма ц ию п ри выводе п ерсонажа на экран. 190 rЛАВА 7
• Если все кадры анимации перебраны, програ1\1ма обнуляет данные, чтобы движение персонажа не останавливалось. Движение происходит плавно, поэтому если ты будешь удерживать клавишу нажатой, то кадры анимации станут прокручиваться с 1 по 4 и ты не увидишь персонажа в поло­ жении стоя до тех пор, пока не остановишь его. ПЕРЕДВИЖЕНИЕ МЕЖДУ ПОМЕЩЕНИJIМИ Теперь, когда ты встал(а) на ноги, тебе наверняка захочется досконально исследовать космическую станцию. Давай добавим в функцию фрагмент кода, с поrvю­ game _ loop () щью которого астронавт сможет переходить из помещения в помещение. Добавь новый код из листинга 7-6, который запускается после того, как программа проверит нажатия клавиш, и до того, как она проверит, стоит ли игрок на дозволенной позиции. Не забудь добавить инструкции с символами комментария (#),расположенные в начале. Позже они нам понадобятся. Выделенные серым цветом строки в листинге 7-6 показы­ вают, куда необходимо добавить новый код. Сохрани про­ грамму в файл с именем команды listingl-6.py. pgzrun listing7-6.py, Запусти ее с по:мощью а затем прогуляйся по космической станции! Сейчас хороший МО1\1ент, чтобы осмотреться по сторонам, совсем скоро мы установим двери и заблокируем некоторые зоны станции. --пr.JТJуск-- def game_loop(): --пгопусI<.. -- player direct·on player_f rame = 1 О б ~ О 0 "down" # проверка на выход из помещения if player_x == room_width: # через #clock . unschedule(hazard_move) current room += 1 generate_map() p l ayer_x О # вход слева player_y = int (room_height / 2) дверь СПРАВА #вход в дверь ПУТЕШЕСТВИЕ НА КОСМИЧЕСКУЮ СТАНЦИЮ 191
0 player_f rarne = #start_roorn() return б Q О if player_x == -1: #через дверь СЛЕВА #clock.unschedule(hazard_rnove) current roorn - = 1 generate_rnap () player_x = roorn_width - 1 # вход справа player_y = int (roorn_height / 2) #вход в player_f rarne = О #start_roorn () return 0 W if player_y == roorn_height: # через дверь #clock . unschedule(hazard_rnove) current _ roorn += МAP_WIDTH generate_rnap () player_y = О # вход сверху player_x = int (roorn_width / 2) #вход player_frarne = О #start_roorn () return ВНИЗУ в if player_y == -1: #через дверь ВВЕРХУ #clock.unschedule(hazard_rnove) current_roorn -= МAP_WIDTH generate_rnap () player_ y = roorn_height - 1 # вход снизу player_x = int (roorn_width / 2) #вхо д в player_frarne = О #start_roorn () return Е ... ,rи # его иr рок О'- У' ипс я , hепопустимоw. J,-r>I 1- дверь дверь дверь "'r о месте, r <..:ре'l.1сстим Hd"!dд. if roorn_rnap[player_y] [player х] not in iterns_player_rnay_stand_on: #\ # or hazard_rnap[player_y] [player_xJ 1 = О: pldyer_x - old_player_x n,:_ пr,уск Листинг 7-6. Теперь персонаж может перемещаться между помеще н иями Чтобы понять, как работает этот код, давай посмотрим на карту помещения. На рис. ной и высотой в 9 7.3 изображена комната шири­ плиток с выхода!\н1, расположенными на каждой стене. Это изображение нам нужно, чтобы выяс­ нить позицию пою,rнувшего поl\1ещение игрока. Ты уже знаешь, что пози ц ии на карте пронумерованы, а нумерация начинается с О, с левого верхнего угла. 192 rЛАВА 7
Желтыми квадратами обозначены места, где мог бы нахо­ диться вы ш едший из ПО!\1ещения игрок. • Если позиция игрока по оси у равна -1, то это означает, что он вышел через выход вверху. • Если позиция игрока по оси х равна -1, он вышел через выход слева. • Если позиция игрока по оси у совпадает со значением пере­ менной room _ height, то игрок вышел через выход внизу. Нумерация плиток начинается с О, поэтому если игрок переходит на девятую строку в помещении с девятью стро ­ ками, то это будет означать, что он покинул помещение. • Аналогично если позиция игрока по оси х совпадает со зна­ чением переменной значит, астронавт поки ­ room _ width, нул комнату через выход справа. 4 ~ l * х = -1 u ~ Ll__U Р ис. 7.3. 1 Т уо9 1 ] х=9 1 н Т Ll Определяем, вышел ли и грок из помещен и я Н овые строки кода проверяют, вышел ли астронавт из помещения. Если позиция игрока по оси х совпадает ПУТЕШЕСТВИЕ НА КОСМИЧЕСКУЮ СТАНЦИЮ 193
со значением переменной room _ width О , то игрок нахо­ дится за дверью, расположенной справа, как показано на рис. 7.3. Когда игрок покидает помещение, нам нужно изменить номер поме щ ения, в котором герой находится . Номер ком­ наты хранится в переменной current _ room. Когда игрок проходит через выход справа, номер помещения увеличива­ ется на б. Взгляни еще раз на карту помещений (вернись к рис. в главе 1 4.1 4), чтобы убедиться, что все сделано верно: номера пш.-1еще н ий увеличиваются слева направо. Напри­ мер, если игрок находится в помещении 33 и проходит через выход справа, то он оказывается в помещении Затем программа генерирует новый список 34. room _ map @ для отображения и навигации по новой КОJ\шате. Игрок пе­ ремещается на противоположную сторону помещения О. П охоже, он прошел через дверной проеl\-1. Если игрок выхо ­ дит из помещения через выход справа, он входит в следую­ щую комнату через выход слева О . Помещения бывают разных размеров, поэтому нам по­ требуется изменить позицию игрока по оси у, чтобы поме­ стить его в середину дверного проема. Иначе наш персонаж может попросту пройти сквозь стену! Мы устанавливаем значение для положения игрока, равное половине высоты помещения 0 . Так мы добиваемся того, что игрок будет на­ ходиться прямо посередине дверного проема. Когда астро­ навт войдет в помещение, мы должны сбросить анимацию нашего героя @ . Я добавил пару функций, которые нам понадобятся позже, поэтому убедись, что ты добавил(а) следующие инструкции: clock.unschedule(hazard _ move) Функция О и start _ room() б. start _ room() отображает название помещения, когда игрок входит в него. Об этих инструкциях мы чуть позже поговорим более подробно. Наконец, инструкция ции game _ loop () 0. ret urn завершает работу функ­ Все дальне1':'1шие инструкции, что находятся в этой функ ц ии, в этот раз выполняться не будут. Когда функция снова запустится, последовательность ин­ струкций будет выполняться сверху вниз, как и всегда. 194 rЛАВА 7
Б следующем блоке кода CD осуществляется проверка, про ш ел ли игрок через дверь, расположенную слева. Чтобы астронавт прошел через дверь слева, программа выполняет следующее. • Проверяет, содержит ли переменная ние • player _ х значе­ -1 (см. рис. 7.3). Вычитает 1 из текущего номера помещения, чтобы перейти в помещение, расположенное слева. • Устанавливает положение по оси х игрока так, чтобы оно находилось по центру дверного проема, расположенного справа. Значение этой позиции равно ширине помещения минус 1. (Ты можешь убедиться в этом, взглянув на рис. В помещении с шириной, равной должна равняться • 9, 7.3. позиция игрока по оси х 8.) Устанавливает посередине положение игрока по оси у, используя высоту помещения. Здесь все работает точно так же, что и при выходе через дверь справа. Одна и та же структура кода используется при работе с выходами вверху и внизу ® . Тем не менее программа про­ веряет координату игрока по оси у, чтобы узнать, выхо­ дил ли он через одну из дверей, и устанавливает для него новую позицию при прохождении через верхний или ниж­ ний дверной проем. На этот раз мы изменим номер помещения на 5, посколь­ ку именно столько помещений раз1\,1ещено на игровой карте по ширине (см. рис. в помещении 37 4.1). Например, если ты находиmься и проходишь через выход, расположенный вверху, ты оказываешься в помещении ты находишься в помещении 37 32 (37 минус 5). Если 11 проходишь через выход, расположенный внизу, ты попадаешь в помещение 42 (37 5). Ранее мы задали переменной МАР _ WIDTH значе ­ 5, поэтому программа и использует его. плюс ние Теперь ты можешь с легкостью исследовать космическую станцию. В следующей главе мы исправим парочку остав­ шихся ошибок в отображении помещений. ПУТЕШЕСТВИЕ НА КОСМИЧЕСКУЮ СТАНЦИЮ 195
rотов ли ты к ПОЛЕТУ? Отметь следующие пункты, если чувствуешь, что материал этой главы тебе понятен. D Положение игрока в игре « Побег », как и декорации, изме­ ряются в плитках. D Функция game _ loop () управляет движением игрока и выполняется каждые D 0,03 секунды. Если игрок перемещается туда, где ему нельзя находиться, он возвращается в прежнее положение за доли секунды, причем так, что ты этого даже не заметишь. D Программа проверяет координаты игрока по осям х и у, чтобы узнать, покидал ли он помещение. Если это так, игрок окажется в середине противоположного выхода соседнего помещения. D Кадры анимации хранятся в словаре PLAYER и содержат список изображений для каждого направления. Ключами словаря являются названия направлений, а индексы позво­ ляют извлечь необходимый кадр. D Кадр О содержит неподвижное положение игрока. Кадры 2, 3 и 4 1, хранят движение астронавта. D Функция game _ loop () увеличивает номер кадра анима­ ции , которая используется для отображения ходьбы игрока. D П еременные player _ offset _ х и player _ offset _ у используются для правильного размещения астронавта при его переходе на новую плитку. ОБСУЖДЕНИЕ МИССИИ Ниже представлены ответы на учебные миссии из этой главы . УЧЕБНАЯ МИССИЯ № Для кадра 1: 0,25 х 1 = 0,25 -1 + 0,25 = -0,75 Для кадра 3: 0,25 х 3 = 0,75 -1 + 0,75 = -0,25 1
УЯУЧWАЕМ КОСМИЧЕСКУЮ СТАНЦИЮ Исследуя космическую станцию, ты наверняка заметил(а), что некоторые вещи выглядят не совсем п равильно . Чтобы побыстрее запустить и просмо ­ т р еть игру, мы воспользовались кодом в раз­ деле ОБЗОР. Но у этого фра г мента программы есть несколько недостатков. • Иногда под декорациями виднеется пустое пространство, поскольку там не установлена напольная плитка. • Когда персонаж подходит к нижней части помещения, передняя стена скрывает астронавта. • Изображение ног астронавта исчезает, когда он движется к задней части экрана. УЛУЧШАЕМ КОСМИЧЕСКУЮ СТАНЦИЮ 197
• Все помещения расположены в левом верхнеI\1 углу игрово­ го окна. Из-за этого игра выглядит криво и непоследова­ тельно, поскольку справа от помещений гораздо больше пространства, чем слева , а более широкие помещения, в отличие от узких, оставляют справа меньше места. • В игре отсутствуют тени, из-за чего сложно понять, где именно в комнате находится объект. В этой главе мы исправим все эти недостатки, а также добавим функцию, которая будет отображать сообщения в верхней части окна. Из них игроки будут получать информацию о космической станции и своем прогрессе в игре. Прочитав эту главу, ты узнаешь, как передавать данные функци­ ям Pytlюn и как рисовать прямоугольники с помощью Руgаше Zего. К концу главы наша космическая станция будет выглядеть просто великолепно! ПЕРЕДАЕМ В ФУНКЦИЮ ДАННЫЕ Прежде всего мы должны научиться передавать данные функциям. Ты уже знаешь, как передать текст функции print ().Мы просто помещаем его в круглые скобки и кавычки. Например: рrint ( " Пройди инструктаж по эвакуации в случае ЧС " ) Когда выполняется эта инструкция, функция print () получает заключенную в скобки фразу и отображает ее в окне командной строки или окне Python. Данные можно отправлять и в те функции , что мы созда­ ли сами. СОЗДАНИЕ ФУНКЦИИ, ПРИНИМАЮЩЕЙ ДАННЫЕ В качестве эксперимента мы создадим функцию, которая складывает два заданных нами числа. Открой новое окно, выбрав команду меню File ~ New File файл), а затеr-.1 добавь код из листинга 198 rЛABAS (Файл ~ Новый 8-1.
О б €) О def add (first_numЬer, second_numЬer): total = first numЬer + second number print (first _numЬer, " + " , second_ number, add (5, 7) add(2012, add(l234, total) 137) 4321) Листинг 8-1. Передаем функции некоторые данные Сохрани программу в файл с rн"1енем скольку здесь нам не нужен Pygame listing8-1.py. По­ Zего, то мы можем запу­ стить ее, просто выбрав команду меню Run ~ Run Module FS. (Выполнить ~ Выполнить модуль) или нажав клавишу (Если ты запускаешь программу с помощью Руgаше Zего, результат появится в окне командной строки, а игровое окно при этом останется пустым.) После запуска програ1\1мы на экране должно появиться следующее: 5 + 7 = 12 2012 + 137 = 2149 1234 + 4321 = 5555 Создаем функцию add () О . Теперь мы можем запустить ее, обратившись к ней по имени О , и передать нашей функ­ ции числовые значения, заключив их в круглые скобки и от­ делив друг от друга запятыми О . Затем функция сложит эти два числа. КАК ЭТО РА&ОТАЕТ Чтобы функция 1\Югла получить наши числа, мы создаем внутри нее две переменные для хранения этих чисел. Я при­ своил И!\! следующие имена: number first _ number и second _ О . Благодаря осмысленным названиям код про­ граммы становится читать гораздо проще. Помн11, что имена переменных могут быть любыми. В данном случае переменные локальные: они доступны только внутри этой функции. Когда мы работаем с функцией, первый переданный эле­ мент помещается в переменную элемент отправляется в first _ number, второй second _ number. УЛУЧШАЕМ КОСМИЧЕСКУЮ СТАНЦИЮ 199
В этом примере не имеет значения, в каком порядке ты указываешь эти две переменные, как и не имеет значения то, в каком порядке ты передаешь эти числа функции. Функции add(S, 7) и add(7, 5) вернут одинаковый результат. Но некоторые функции требуют передавать им данные в строго заданной очередности. Например, если бы функция вычитала одно число из другого, то ты получил(а) бы нужный результат только в случае, если бы передал(а) числа в правильном порядке. Единственны1":'r способ узнать, какие данные ожидает полу­ чить функция, - взглянуть на ее код. Тело функции выглядит довольно просто. В нем исполь­ зуется новая переменная, total, в которой хранится ре ­ зультат сложения двух чисел б . Затем программа выводит строку, содержащую первое число, знак плюс, второе число, знак равенства и итоговое число СЭ . В последних трех инструкциях мы передаем функции для сложения трех пар чисел О. На это!l.1 простом примере видно, как информация (иначе она называется аргумен.ты) передается в функцию. Ты можешь создавать функции, которые принимают боль ­ ше двух аргументов или даже принимать списки, словари или изображения. Функции упрощают повторное исполь­ зование набора инструкций, а наличие аргументов озна­ чает, что мы можем использовать эти инструкции с раз­ личными данными. Например , в листинге используется одна и та же инструкция 8- 1 трижды print () для ото­ бражения суммы трех разных пар чисел. Мы избежали по­ вторного ввода инструкции дающей переменную total. print () и инструкции, за­ Более сложные функции позволяют избежать многократного набора еще большего количества текста, поэтому программу становится легче читать и дополнять. А теперь давай добавим в код игры « Побег » еще несколь­ ко функций, чтобы дополнить космическую станцию новы­ ми объектами. 200 rЛАВА8
YЧE&HAll МИССИll No 1 Попробуй изменить программу так, чтобы было выполнено вы­ читание, а не сложение. Что произойдет, если ты изменишь по­ рядок чисел, которые ты задаешь новой функции? Может быть, ты даже захочешь внести в функцию какие-нибудь изменения помимо расчетов, чтобы она стала более удобной в использовании. ДО&АВЛЕНИЕ ПЕРЕМЕННЫХ ДЛJI РА&ОТЫ С TEHJIMИ, НЕВИДИМЫМИ ЧАСТJIМИ СТЕН И ЦВЕТОМ Чтобы наша косr..1ическая база стала еще краше, мы добавиl\·1 в код игры новые функции вывода, используя недавно полу­ ченные знания о функциях. Но сначала мы создади,..1 новые переменные, которые будут использовать эти функции. Открой файл lis6ng7-б.py, последний вариант игры, кото ­ рый ты сохранил(а) в главе 7. Найди раздел ПЕРЕМЕННЫЕ, расположенный в начале листинга, и добавь новые строки, указанные н листинге с именем 8-2. Сохрани программу н файл listingB-2.py. Рекомендую запустить програ1'v1му (с помощью команды pgzrun listing8-2 .ру), чтобы про­ верить ее на отсутствие каких-либо новых ошибок. --пгопуск-- ################ ## ПЕРЕМЕ:!НЫЕ ## ################ --прf)пуск-- player_image = PI,AYER [player_direct ion] [player_frame] player offset_x, player_offset_y ~ О, О 0 PLAYER_SHADOW = { " left " : [images.spacesuit_left_shadow, images.spacesuit_left_l_shadow, images.spacesuit_left_2_shadow, images.spacesuit_left_З_shadow, images.spacesuit_left_4_shadow ] ' " r ight " : [ images. spacesui t_r ight_shadow, images.spacesuit_right_l_shadow, УЛУЧШАЕМ КОСМИЧЕСКУЮ СТАНЦИЮ 201
irnages.spacesuit_right_2_shadow, irnages.spacesuit_right_З_shadow, irnages.spacesuit_right_4_shadow ] / " up " : [irnages. spacesuit_back_shadow, irnages.spacesuit_back_l_shadow, irnages.spacesuit_back_2_shadow, irnages.spacesuit_back_З_shadow, irnages.spacesuit_back_4_shadow ] / " down " : [irnages. spacesuit_front_shadow, irnages.spacesuit_front_l_shadow, irnages.spacesuit_front_2_shadow, irnages.spacesuit_front_З_shadow, irnages.spacesuit_front_4_shadow б player_image_shadow PLAYER_SHADOW [ " down" ] [О] €) Р ILLARS = [ irnages.pillar, irnages.pillar_95, irnages.pillar_80, irnages.pillar_50 irnages.pillar_бO, О wall_transparency_frarne О 0 BLACK = (0, О, 0) BLUE = (0, 155, 255) YELLOW = (255, 255, 0) WHITE = (255, 255, 255) GREEN = (0, 255, 0) RED = ( 12 8, О, О) ############### ## КА:'ТА ## ############### -проr,уск- Листинг 8-2. Добавление переменных, необходимых для работы новых функций вывода Мы добавили новый словарь, PLAYER _ SHADOW 0 , он похож на словарь PLAYER. В нем содержатся кадры анима­ ции тени астронавта, располагающейся на полу. По мере движения астронавта тень меняет свою форму. Переменная player _ image _ shadow б хранит текущий вид тени астронавта, так же как переменная player _ image хранит текущий кадр анимации астронавта (или его изображение в неподвижном состоянии). 202 rЛАВА 8
Чуть позже в этой главе мы добавим анимацию, которая затухает на передней стене, когда астронавт проходит прямо за ней, чтобы ты мог видеть своего персонажа. Далее мы на­ страиваем список кадров анимации С) и переменную wall _ t ransparency _ f rame, чтобы запомнить отобра­ жаемый на данный момент элемент О. Позже мы подробнее поговорим о том, как все это работает. Помимо этого мы добавили переменные, в которых бу­ дем хранить коды цвета 0. Цвета в Pygaine Zего хранятся в виде кортежей. Кортеж похож на список, содержи!\·юе ко­ торого ты не можешь изменить. В нем используются круг­ лые скобки вместо квадратных. Ты уже знаком(а) с этим по­ нятием, мы использовали кортежи при работе с координатами, когда рисовали на экране (см. главу 1). Цве ­ та хранятся в виде трех чисел, которые определяют количе­ ство красного, зеленого и синего пигментов (в этом поряд­ ке) в том или ином цвете. Количество каждого пиг!\-1ента измеряется по шкале от О до (255, О, 255. Этот цвет ярко-красный: 0) П о нашей шкале у красного цвета указано максимальное значение Таблица (255), 8.1. и в этом цвете нет зеленого (О) или синего (О). Примеры оттенков с кодами по модели RGB Красный Зеnеный Синий Описание 255 о о Ярко-красный о 255 о Ярко-зеленый о о о о Темно-темно-синий (почти черный!) 255 255 255 50 255 Ярко-синий Белый (все числовые значения пигментов максимальны) 255 255 150 Кремово-желтый (меньше голубого оттенка, чем в белом цвете) 230 230 230 200 255 150 100 200 Сиреневый о Оранжевый (максимум красного 255 105 180 Серебристый (слегка приглушенный белый) с примесью зеленого) Розовый Поскольку мы настроили переменные, отвечающие за цвета, мы можем использовать имя BLACK вместо кортежа УЛУЧШАЕМ КОСМИЧЕСКУЮ СТАНЦИЮ 203
(0, О, 0) для обозначения черного ц вета. Код программы будет гораздо проще читать, если цвета будут иметь назва­ ния . В таблице 8.1 показаны некоторые оттенки, которые ты, возможно, захочешь использовать в своих программах. Кро­ ме того, ты можешь сам(а) создавать новые оттенки, указы­ вая разные числа. УДАЯJIЕМ РА3ДЕЯ ОБЗОР Давай добавиr-.1 в нашу программу еще один раздел, ЭКРАН, в котором будут находиться некоторые новые функции, которые улучшат внешний вид игры. Благодаря разделу ОБЗОР мы смогли довольно быстро запустить игру, но в этой главе мы обновим функцию draw ().Чтобы раздел ОБЗОР не помешал в будущем работе игры, предлагаю его удалить. Количество строк в твоем разделе ОБЗОР может отличаться от моего, показанного на рис. 8.1, в зависимости от того, удалил(а) ли ты какие-либо строки при прочтении предыдущих глав. Чтобы полностью удалить раздел ОБЗОР, выполни сле­ дующие действия. 1. Найди в программе раздел ОБЗОР, расположенный в конце файла. 2. Нажав и удерживая кнопку мы ш и на первом символе # ком­ ментария-заголовка ОБЗОР, перемести мышь в нижнюю часть раздела (см. рис. 8.1). Раздел заканчивается чуть выше раздела СТАРТ. 3. Нажми на клавиатуре клавишу D el или Backs pace. Сохрани программу и запусти ее с помощью команды pgzrun. Если все сделано правильно, в окне командной строки не должно высветиться сооб щ ение об ошибке. Пока игровое окно показывает лишь чернильную тьму космоса, потому что в нашей программе еще нет никакого кода, с по ­ мощью которого мы могли бы что-то нарисовать. 204 fЛАВА8
' l1<t1n98-2.py • D:\P16oт1\)<eL10\210-MISS!ON PV1НON-A111<eJ>Щ>J\ptд1<Npo11нм1\кCJptllкt1n91\lкM98-2.py(l.bll) о х F1lt Рис. 8.1. Удаление раздела ОБЗОР ДО&АВЛЯЕМ РАЗДЕЛ ЭКРАН Давай добавим раздел ЭКРАН, которыl\I мы заменим раздел О БЗОР. Раздел ЭКРАН содержит огромное количество кода, задача которого - обновление изображений на экране. Сюда относится код, рисующий графику игры, отображаю ­ щий сообщения и меняющий п е реднюю стену так, чтобы она стала прозрачной. УЛУЧШАЕМ КОСМИЧЕСКУЮ СТАНЦИЮ 205
ДО&АВЛЕНИЕ ФУНКЦИЙ ДЛЯ РИСОВАНИЯ О&'ЬЕКТОВ Прежде всего мы создадим функции, с помощью которых будем рисовать объекты, тени и игрока на определенной позиции (плитке). Новый раздел кода в листинге 8-3, - ЭКРАН, указанный нужно добавить между разделами ЦИКЛ ИГРЫ и СТАРТ. Сохрани программу в файл с именем listing8-3.py и запусти ее с помощью команды pgzrun listing8-3.py. В окне игры все пока останется по-преж­ нему. Если в окне командной строки высветились ошибки , проверь код программы и при необходимости внеси в него необходимые иЗJ1ленения. Программ у лучше тестировать по мере добавления кода, это эффективнее, чем написать большой фрагмент и потом долго искать, где же в нем при­ таилась ошибка. проГiуск if player_direction =- "down" and player_frame > player offset_y = -1 + (0.25 * player_frame) ############### ## ЭКРАН ## ############### О def draw_image (image, х): screen.Ыit( 6 image, (top_left_x + top_left_y + ~ у, О: (х * TILE_SIZE), (у* TILE_SIZE) - image.get_height()) def draw_shadow (image, у, х): screen.Ыit ( image, (top_left_x + (х * TILE_SIZE), top_left_y + (у* TILE_SIZE)) def draw_player () : player_image = PLAYER[player_direction] [p layer_frame] 0 draw_image(player_image, player_y + player_offset_y, player_x + player_offset_x) С) player_image_shadow = PLAYER_SHADOW [playe r_direction] [player_frame] & draw_ shadow(player_image_shadow, О 206 rЛАВА8
pl aye r _ y + p l ay er_offset_y, pl a yer_ x + pl a yer_o ffset _ x) ############### ## СТАРТ ## ############### generate_rnap () clock.schedule_interval(garne loop, 0.03) Лист инг 8-3. П ер в ые функции раздела ЭК РА Н draw _ image () О - первая функция из числа новых. С ее помощью программа рисует на экране нужное нам изо­ бражение. П ри использовании этой функции мы задаем изображение, которое хотим нарисовать, и положение объ­ екта в помещении по осям у их. Основываясь на положении плитки в комнате, функция будет определять, где именно на экране следует отрисовать изображение (позици я пиксе­ ла). Например, мы можем использовать функцию следую­ щим образом: d r a w_irna ge (p l a ye r _ irnage , 5, 2 ) Вызов функции с такиJ\1и аргументами изобразит астро­ навта в точке поме щ ения с координа т ами у Функция image =5 их = 2. draw _ image () п рисваивает изображению имя и помещает значение координаты по оси у в перемен­ - в переменную х О. Н есмотря на то что функция draw _ image () занимает несколько строк, ее единственная инструкция это метод screen-Ыit (). Именно он отрисовывает изображение ную у, а значение координаты по оси х в заданной позиции б. Эта инструкция очень похожа на ту, которую мы использовали ранее в разд е ле О Б ЗО Р, поэтому, если нужно, ты можешь вернут ь ся к главе 3, чтобы вс п о­ мнить, как работает данная функция. Далее идет еще од н а новая функция - ~­ draw _ shadow() Она похожа на функ ц ию, выводящую изображение на эк­ ран, за исключением то г о, что высота изображения не вычи­ тается при расчете его позиции на эк р ане. Таким образом те н ь оказывается ниже основного изображения. Н а рис. 8.2 показаны астронавт и его тень, рас п оложенные на одной и тоi-:1' же плитке. Н е з абывай , что позиция по оси у, УЛУЧШАЕМ КОСМИЧЕСКУЮ СТАНЦИЮ 207
указанная для screen .Ыit (),отвечает за верхний край изображения. СОВЕТ Убедись, что все круглые скобки находятся в соответствующих им местах. Все аргументы функции screen .Ыit () должны быть заключены в круглые скобки, как и значения координат по осям у их, поскольку все это кортеж. Не забудь « завер­ нуть» в скобки часть умножения при вычислении позиции иг­ рока. Если программа не работает, проверь код на отсутствие ошибок, пересчитав открывающие и закрывающие скобки, чтобы убедиться, что расставил(а) их верно . top_left_y + (у * ТILE_SIZE) top_left_y + (у * ТI L E_SIZE) - image.get_height() Р ис. 8.2. О п ре д еление по зи ци и изображен и я и его те н и Третья новая функция, draw _ player (), отрисовывает изображение астронавта. Сначала нужный кадр анимации аст р онавта помещается в переменную player _ irnage О. Затем для отображения функция 0 этого кадра используется новая draw _ irnage ().Функции draw _ image ()для работы требуются следую щ ие данные. • П еременная player _ irnage, которая содержит изображе­ ние для отрисовки на экране. • Результат, получившийся после добавления глобальных переменных для player _ у и player _ offset _ у. Это позиция по оси у в плитках, значение которой может быть представлено десятичным числом (наприr.лер, • Результат после добавления переменных и player _ offset _ 5. 25). player _ х х для позиции по оси х (в плитках). (Дополнительную информацию о тorv1, как переменные, 208 rЛАВА8
отвечающие за смещение, используются для анимации, см. в разделе «Разбираемся в коде передвижения» главы 7.) Тень персонажа рисуется примерно так же, как и он сам: нужный кадр анимации из словаря PLAYER _ SHADOW поме­ щается в переменную player _ image _ shadow 0 . Затем для его отображения используется функция draw _ shadow () 8 . Функция draw _ shadow () работает с теми же ПОЗИЦИЯJ\НI плитки, что и функция draw - image (). РИСУЕМ ПОМЕЩЕНИЕ Теперь, когда мы добавили функции, позволяющие рисовать на экране объекты и игрока, мы можем заняться созданием кода для рисования помещения. Новая функция из листинга 8-4 draw() добавляет тени декорациям и игроку, а также исправляет проблемы с визуализацией, которые мы наблюдали ранее. Добавь новый код в конец раздела ЭКРАН, сохрани свою программу в файл с именем мощью команды listing8-4.py и запусти ее с по­ pgzrun listing8-4 .ру. Теперь у объек­ тов есть тени, как будто мы включили свет. Однако игра все еще выглядит не лучшим образом, поскольку все помеще­ ния отображаются в левом верхнеl\I углу окна, и периодиче­ ски, после того как игрок покидает помещение, объекты в нем не возвращаются в исходное положение, как должны. Совсем скоро мы это исправим. На данном этапе на экране не должно быть сообщений об ошибках. -npor," гк-- def draw_player(): player_irnage = PLAYER[play~r_d~Yection] [player_frarne] draw_irnage (player_irnage, player_y + player_offs(•t_y, player_x + player_offset_x) player_irnage shadow = PLAYER_SHADOW[player_direction] [player_frarne] draw_shadow(player_irnage_shadow, player_y + player_offset_y, player_x + player_offset_x) def draw (): if garne_over: return УЛУЧШАЕМ КОСМИЧЕСКУЮ СТАНЦИЮ 209
О # Очистка игровой арены . = Rect ( (0, 150), (800, 600)) screen.draw.filled_rect(box, RED) Ьох = Rect ( (0, 0), (800, top_left_y + (roorn_height Ьох 1) *30)) б screen.surface.set_clip(box) floor_type = get_floor_type() ~ for у in range (roorn_height): # Кладем на пол сначала плитку , # затем предметы . for х in range (roorn_width): draw_irnage(objects[floor_type] [О], у, х) # Следующая строка кода позволяет теням падать # на предметы на полу if roorn_rnap[y] [х] in iterns_player_rnay_stand_on: draw_irnage(objects[roorn_rnap[y] [х]] [0], у, х) О # Нажимная платформа в комнате 26 , # реквизита . нужна для размещения if current_roorn == 26: draw_irnage (objects [39] [0], 8, 2) image_on_pad = roorn_rnap[8] [2] if irnage_on_pad > О: draw_irnage(objects[irnage_on_ pad] [0], 0 for 8, 2) у in range (roorn_height): for х in range (roorn_width) itern_here = roorn_rnap[y] [х] # Игрок не допускается в 255 : это позиция для # широких объектов . if itern_here not in iterns_player_rnay_stand_on + [255]: irnage = objects[itern_here] [О] if (current_roorn in outdoor_roorns and у == roorn_height - 1 and roorn_rnap[y] [х] == 1) or \ (current_roorn not in outdoor_roorns and у == roorn_height - 1 and roorn_rnap[y] [х] == 1 and х > О and х < roorn_width - 1): # Прозрачность изображения стены # в первой строке. irnage = PILLARS[wall_transparency_frarne] draw_irnage(irnage, у, х) if objects[itern_here] [1] is not None : # # у объекта есть тень shadow_irnage = objects[itern_here] [1] # # 210 rЛABAS если тени «выкладка» понадобится Если горизонтальная
if shadow_image in [images.half_shadow, images.full_shadow]: shadow_width int (image.get_width() / ТILE_SIZE) # Используем тень по ширине объекта . for z in range (O, shadow_width): draw_shadow(shadow_image, у, x+z) else : draw_shadow(shadow_image, у, х) if (player_y == у) : draw_player () 4]) screen.surface.set_clip( None ) ############### ## СТАРТ ## #########Н#### generate_map () clock.schedule_interval(gam~ loop, 0.03) Листинг 8-4. Новая функция draw () Как и в случае с кодом, отвечающим за перемещения (в главе 7), совсем не обязательно углубляться в то, как рабо­ тает функция draw (),даже если ты хочешь самостоятельно настроить программу. В следующем разделе я объясню, как работает эта функция, поэтому, если ты пока не готов(а) углубиться в принципы ее работы, перейди к разделу «Распо­ ложение помещения на экране» далее в этой главе. РАЗ&ИРАЕМСЯ, КАК РА&ОТАЕТ НОВАЯ ФУНКЦИЯ DRAW() Счита1'::'1, что функция draw() - это более сложная версия кода, который ранее использовался в разделе ОБЗОР. Итак, давай разберем работу этой части программы. Очистка иrровой арены Программа начинается с очистки игровой арены О , J\·1еста, где будет отображаться космическая станция. Для этого мы pиcyeJ\t большой красны1':'1 прямоугольник, перекрывающий предыдущий экран. Области, которые содержат информа­ цию об игроке и расположены вверху и внизу, разделены, поэтому они остаются неиз:менными. УЛУЧШАЕМ КОСМИЧЕСКУЮ СТАНЦИЮ 211
Прямоугольник размещается на экране в два этапа. Вна­ чале создается фигура с помощью объекта Руgаше, называе­ J\юго Ьох Rect, выглядит это так (этот код вводить не надо): Rесt (( позиция_слева , позиция_сверху ), ( ширина , высота )) В качестве названия переменной выбери любое понра­ вившееся тебе имя, я же в своих программах использую имя Ьох. Позиция и разl\1ер представляют собой кортежи, по­ этому они заключены в круглые скобки. Далее мы отображаем Rect (прямоугольник), созданный ранее с помощью инструкции, подобной этой (опять же, этот код вводить не нужно): s cre en.dr aw. filled_rect (box , цвет ) Первый элемент в скобках - это созданный ранее пря­ моугольник Ьох. Второй элемент - цвет этого прямоуголь­ ника. Это может быть кортеж из чисел, представляющих значения красного, зеленого и синего цветов, чтобы в итоге получился некоторый оттенок. В листинге 8-4 я использо­ вал имя RED, по имени переменной, которую мы объявили ранее в разделе ПЕРЕМЕ ННЫ Е. Также объекты Rect можно использовать для создания области отсечення б . Представить ее себе можно как некое прозрачное окно, через которое ты можешь видеть содержи­ мое игры. Если программа рисует что-то за пределаl\IИ обла­ сти отсечения, увидеть это невозможно. Я установил значе­ ние отсечения, равное высоте помещения, чтобы тень игрока, находящегося в дверном проеме, не выходила за пределы нижнего края интерфейса игры. Рисование помещения Отрисовка комнаты происходит в два этапа. Сначала про­ грамма рисует плитки пола и все, по чему игрок может пере­ двигаться СЭ . Поскольку эти детали отрисовынаются в первую очередь, впоследствии мы можем рисовать поверх них обста­ новку, астронавта и тени. Так мы устраняем проблему с появ­ лением черных участков под декорациями, в этих местах будут сначала отрисованы напольные плитки и уже затем декорации. 212 rЛАВА8
Затем, используя новые циклы, програм/\1а добавляет в помещение декорации (включая тени 0 ). Поскольку мы рисуем эти детали после того, как был нарисован весь пол ком­ наты, тени окажутся поверх напольных плиток и предметов, расположенных на полу. Тени прозрачны, поэтому видны рас­ положенные под ними объекты. Циклы, с помощью которых отображаются декорации, также позволяют сделать стены не­ види/\1ыми 0 и поместить игрока поверх пола 0. Как и всегда, помещение мы начинаем отрисовьшать в на­ правлении от дальнеС'r стены к передней, чтобы убедиться, что объекты, расположенные в ближней части комнаты, на­ ходятся перед объектами, расположенными позади. Также я добавил небольшо1':'1 фрагмент кода для специаль­ ного объекта, которы1':'1 используется в игре только один раз. В комнате 26 в полу есть нажимная плита, н а которую во время игры можно ставить предметы (тяжелые сами по себе или же их можно сделать таковыми ... ). Этот фраг­ мент кода обеспечивает видимость как самой нажимной плиты, так и объекта, расположенного на ней. После того как все напольные плитки будут нарисованы, функция draw () проверит, является ли текущее помещение шлюзом (помещением 26). Если это так, то сначала будет на­ рисована нажимная плита, а затем предмет на неИ О . ljДфlf.jj Если ты используеzиь в игре собственную карту, удали этот фрагмент кода, чтобы убрать нажи.лmую плиту. Начни со строки с ко.ktментарием О и удали код вплоть до инструкции draw _ image (objects[image _оп_ pad [0], 8, 2) (включительно). Скрываем переднюю стену Когда программа отрисовывает первый ряд комнаты (цикл у равен roorn _ height - 1), код проверяет, нужно ли рисо­ вать невидимую стену вместо окрашенного объекта (стены), взятого из карты помещения 0. Полупрозрачная стена используется в том случае, когда игрок стоит прямо за нei:'I (см. рис. 8.3). Если стена расположена на поверхности планеты, то про­ грамма делает стену полностью невидимой. Внутри УЛУЧШАЕМ КОСМИЧЕ С КУЮ СТАНЦИЮ 213
космической станции невиди:мая стена используется толь­ ко в том случае, если та не находится в одном из нижних уг­ ловых положений (см. рис. 8.3). В углах всегда используется сплошная закра ш енная стена. Было бы странно, если бы сте­ новая панель начиналась во второй строке снизу. Позже мы добавим код для анимации полупрозрачности стены, изменив значение переменной transparency _ frarne. wall _ Но пока в игре этого нет. Рис. 8.3. Полупрозрачная стена, расположенная в передней части помещения, как она выглядит в финальной версии игры Добавnяем тени Если у объекта есть тень, то она берется из словаря и помещается в переменную objects shadow _ irnage 8. Затем про­ грамма проверяет, следует ли использовать инструкции half _ shadow или full _ shadow, благодаря которым заполняется половина плитки или целая плитка соответ­ ственно. Эти две стандартные тени используются с предме­ тами, состоящими из блоков (такими как электроприборы и стены), тень которых не имеет четких очертаний. Про­ грамма проверяет, н аходи тся ли изображение shadow _ irnage в списке, содержащем эти два стандартных изобра­ жения Е3. Это просто~'.'1 и понятный способ проверить, относится ли shadow _ irnage к одному из двух значений. Если тебе нужно проверить три значения и более, этот метод зна­ чительно упростит код программы, в отличие от множества условий 214 rnABAS if с использованием операторов== и or.
Если тень относится к одному из стандартных изображений, про­ грамма определяет, какой ширины в плитках она должна быть. Для этого мы берем ширину объекта, отбрасы­ о вающего тень, и делим ее на ширину плитки (30 пикселов). Например, изо­ бражение шириной 90 Рис. 8.4. Объект шири ­ пикселов будет иметь ширину, равную 3 ной 3 nлитки отбрасы­ плиткам. вает стандартную тень, Затем програ1\1ма создает цикл, что­ которая исnользуется трижды бы отрисовать стандартное изображе ние тени, используя переменную z. Отсчет начинается с О и продолжается до тех пор, пока значение ширины тени не станет равным -1, потому, что в функции сутствует последний элемент: числа О, 1 и 2. Значения z range (0, 3) range () от­ сгенерирует добавляются к значению позиции по оси х из основного цикла и используются для отрисовки плит, на которые падает тень. На рис. шириной в 3 плитки. Цикл z 8.4 показан объект принимает значения О, 1 и 2, благодаря чему тень появляется в нужном месте. Когда после укладки пола мы помещаем астронавта в нужной точке комнаты, то делаем это так, чтобы ноги аст­ ронавта не исчезали при перемещении по экрану Функция 0. draw () завершает свою работу отключением области отсечения, которая не позволяла теням выходить за пределы нижней части игровой области @ . РАСПОЛОЖЕНИЕ ПОМЕЩЕНИЯ НА ЭКРАНЕ Давай теперь устраним проблему, из-за которой помещение отображается в левом верхнем углу экрана. Программа использует для позиционирования помещения на экране две переменные: top _ left _ х и ный момент они имеют значения top _ left 100 и 150 _у . На дан­ соответственно. Это означает, что помещение всегда отображается в левом верхнем углу экрана. Мы добавим код, который будет менять значения этих переменных в зависимости от размера помещения, чтобы оно располагалось по центру окна УЛУЧШАЕМ КОСМИЧЕСКУЮ СТАНЦИЮ 215
(см. рис. 8.5). Внешне игра будет выглядеть лучше, что упростит ее прохождение. Рис. 8.5. Помещение расположено в центре экрана Добавь новые строки, указанные в листинге функции generate _ map (), 8-5, в конец которая находится в разделе ПОМЕЩЕНИЕ программы. Поскольку эти строки относятся к функции, перед ними должен быть отступ в четыре пробе­ ла. Сохрани программу в файл с именем listing8-5.py и запу­ сти ее с помощью команды pgzrun listing8-5.py. Теперь все помещения должны быть расположены по центру экра­ на, как показано на рис. 8.5. --npor,ycf<'-- def generate_map(): --пропуск-- fоr 216 rnABAS in range(l, image_width_in_tiles): roorn_map[scenery_y] [scenery_x + t"le_numЬer] = 255 tile_numЬer
О б ~ О 0 Ф center_y = int (HEIGHT / 2) #Центр игрового окна center_x = int (WIDT H / 2 ) room_pixel_width = room_width * TILE SIZE # Размер # помещения в пикселях room_pixel_height = r oom_height * TILE SIZE top_left_x cente r_x - 0.5 * room_pixel_width top_left_y = (center_y - 0.5 * room_pixel_height) + 110 Л истинг 8-5. Добавление переменных для р а с п оложе н ия помещения в це нт ре игрового окна Эти инструкции находятся внутри функции generate _ map (), map которая наст р аивает список room _ для каждого помещения, когда игрок попадает в него. Функция generate _ map () настраивает пере .11ленные top _ left _ х и top _ left _ у, которые запоминают, в каком месте на экране должно появиться помещение. Н овый код в листинге 8-5 начинается с определения се ­ редины окна. П еременные HE I GHT и WIDTH храня т размер окна (в пикселах). Разделив их значения на 2, мы получим координаты центра окна. Храниться они будут в перемен­ ных center _у О и center _ х б. Затем п рограмма определяет ширину изображения поме­ щения (в пикселах) ~. Результатом будет ширина помеще ­ ния в плитках, умноженная на размер одной плитки, запи ­ шем его в переменную room _ pixel _ width. Аналогичный расчет выполняется для вычисления высоты помещения О . Чтобы поместить изображение помещения в его центр, нужно, чтобы одна половина помещения находилась слева о т центральной ли н ии, а другая - справа, так что мы вычи­ таем половину ширины помещения в пикселах из значения центральной линии 0 и начинаем рисовать помещение с этой точки. Аналогичный расчет выполняется и для переменной top _ left к результату _у, за исключением того, что мы прибавляем 0 число 110. Так приходится делать для того, чтобы в нашем окончательном макете область в верхней ча­ сти экрана использовалась в качестве информационной па­ нели, поэтому наl\1 нужно немного сдвинуть изображение помещения, чтобы освободить место для табло. УЛУЧШАЕМ КОСМИЧЕСКУЮ СТАНЦИЮ 217
ДЕЛАЕМ ТАК, ЧТО&Ы ПЕРЕДНЯЯ СТЕНА ПОСТЕПЕННО ПОЯВЛЯЛАСЬ И ИСЧЕЗАЛА На данный J\ЮJ1.1ент в игре есть несколько слепых зон, где игрока не видно. В центре помещения мы можем избежать этого эффекта. Для этого мы должны убедиться, что пред­ меты не загораживают игрока. Однако в передней части помещения у нас располагается высокая стена. Это может привести к массе разнообразных проблеivI. Если ты уронишь какой-нибудь предмет, то уже не сможешь его найти, или же, если что-то будет наносить урон твоему здоровью, ты просто не сможешь понять, что происходит! Решение заключается в том, чтобы стена исчезала , когда иг­ рок приближается к ней. Функция draw () отрисовывает колонны передней стены с помощью кадров анимации. Анимация стены состоит из пяти кадров (пронумерованных от О до 4) в списке PILLARS. Первы1':'1 кадр представляет собой сплошную сте­ ну, а последний показывает максимально прозрачную стену (см. таблицу 8.2). По мере смены номера кадра анимации стена становится все прозрачнее. Текущий кадр хранится в переменной wall _ transparency _ frame. Благодаря тому, что в графическом файле можно задать прозрачность, мы можем видеть нашего астронавта сквозь прозрачную стену, расположенную перед ним. Таблица 8.2. Кадры анимации для передней стены Номер кадра о 2 3 4 Изображение ----В листинге 8-6 приведена функция adjust _ wall _ transparency (),которая будет управлять появлением сте­ ны и ее исчезновением. Размести ее в конце раздела ЭКРАН после функции 218 rЛABAS draw (), которую мы только что закончили,
и до раздела СТАРТ. Кроме того, в конец программы нужно добавить строку, которая обеспечит регулярный запуск на­ шей новой функции. Эта строка также приведена в листин­ ге 8-6. Сохрани обновленную програN1му в файл с именем listing8-б.py и запусти ее с поJ\ющью команды pgzrun listing8-6.py. Если наш герой подш':'щет вплотную к перед­ ней стене, она станет прозрачной, и мы сможем через нее уви­ деть персонажа (см. рис. 8.3 ранее в э той главе). Как только аст­ ронавт отойдет от стены, она снова станет непрозрачной. --nror1ycк-- ############### ## Э.t<:РАН ## ############### screen.surface.set clip(None) def adjust_wall_transparency (): global wall_transparency_frame О б ~ О 0 Ф б if (player_y == room_height - 2 and room_map[room_height - 1] [player_x] == 1 and wall_transparency_frame < 4): wall_transparency_frame += 1 # Исчезновение стены . if ((player_y < room_height - 2 or room_map[room_height - 1] [player_x] ! = 1) and wall_transparency_frame > 0): wall_transparency_frame -= 1 # Появление стены . ############### ## СТАРТ ## ############### Ф generate_map () cloc k.schedul e_interval(game_loop , 0.03) clock.schedule_interval(adjust_wall_transparency, 0.05) Листинг 8-6. Когда игрок подходит к стене, расположенной но переднем плане, оно становится прозрачной После дняя строка кода, которую мы добавили в листин­ ге 8-6, указывает функции adjust _ wall _ transparency () запускаться каждые 0,05 секунды 0. УЛУЧШАЕМ КОСМИЧЕСКУЮ СТАНЦИЮ 219
Благодаря этому стена исчезает или вновь появляется в за­ висимости от движения астронавта по комнате. Давай посмотрим, как это работает. Если игрок стоит за стеной, то следующие два утверждения будут верны. • Их позиция по оси у будет равна Как показано на рис. room _ height - 8.6, room _ height - 2 О. нижняя строка карты равна 1. Поэтому мы проверяем, находится ли игрок в строке выше. • В нижней строке помещения расположена часть стены, которая соответствует позиции игрока по оси х б . На рис. 8.6 красным квадратом обозначена позиция , в кото­ рой астронавт не будет виден. Значение стены в нижней строке равно 1. Зеленый квадрат показывает, где астронавт будет виден. В данном случае это дверной проем. Здесь зна­ чение нижней строки карты помещения равняется О. о 4 о 4 room_ height - б room_ height room_height Рис. о 8 8.6. Выясняем, находится ли игрок за стеной Если игрок находится за стеной Об , и ее прозрачность не максимальная €) ,то значение прозрачности стены увели­ чивается на 220 rЛABAS 1 О.
Если одно из следующих значений истинно, это означа­ ет, что игрок не скрыт стеной. • Их значение по оси у меньше, чем высота помещения, room _ height - 2 0 . Хотя - бы частично игрока 1\ЮЖНО увидеть , если он находится чуть дальше стены. • В нижней строке помещения нет ни одной части стены, соответствующей их значению по оси х @. Если в обоих случаях значение прозрачности стены пре­ вышает минимальное б , оно уменьшается на единицу Е3 . Функция draw () использует значение переменноi:'1 wall _ transparency _ frame, чтобы определить, какое изображение из кадров анимации, находящихся в списке PILLARS, следует использовать в первой строке. В результате стена будет постепенно исчезать, а затем по­ являться, в зависимости от расположения игрока. Затухание происходит достаточно быстро, чтобы игрокам не приходи­ лось ожидать его появления, но при этом эффект срабатыва­ ет плавно, чтобы затухание не исчезало мгновенно, мелька­ ние будет отвлекать игрока. ОТО&РАЖЕНИЕ ПОДСКАЗОК, СОВЕТОВ И ПРЕДУПРЕЖДЕНИЙ Иногда в процессе игры требуется выводить на экран неко­ торый текст, который рассказывает игроку о том, что про­ исходит. Например, текст }..ЮЖет использоваться, чтобы сообщить о результате взаимодействия с предметом или же чтобы объяснить игроку, что за объект перед ним. Последняя функция в разделе ЭКРАН предназначена для выведения сообщения в верхней части окна игры. Есть два вида строки для вывода такого текста. • Первая строка, расположенная на расстоянии 15 пикселов от верхней части окна, здесь игроки могут узнать о резуль­ тате своих действий. Например, здесь отображается описа­ ние предr-летов и сообщается игроку, что происходит, когда тот использует те или иные предметы. УЛУЧШАЕМ КОСМИЧЕСКУЮ СТАНЦИЮ 221
• Вторая строка расположена на расстоянии 50 пикселов от верхней части окна, здесь будут отображаться важные сообщения. Строки разделены для того, чтобы более важные сообще­ ния не были скрыты менее важными. Если игрок должен знать об опасной для жи з ни ситуации, вряд ли ему понра­ юпся, если это сообщение будет з а менено тексто.м, в кото­ ром говорится о в х оде в новое помещение! После того как ты добавил(а) в листинг 8-6 код для управления прозрачностью стены, добавь в конец раздела ЭКРАН новый фраг м ент программы и з листинга ни файл под именем listi11g8-7.py. 8-7. Сохра­ Ты 1\южешь протестиро­ вать этот код, запустив программу с помощью ко.манды pgzrun listing8-7.py, хотя никаких изменений ты пока не увидишь. Вскоре мы добавим в программу инструкции по использованию новой функции show _ text (). if ((playt>r_y < room_height 2 or room map[room_height 1] [player_x] ! = 1) and wall transparency frame > 0): wall. transparency frame -= # П r>"юаие сгсн~1. О def show_text (text_t o_sho w, l ine_nu mЬ er) : if ga me_over : return 6 t e xt_lines = [1 5 , 5 0 ) С) Ь о х = Rect ((O, t ext_lines [ line_numЬer ]), ( 8 0 0 , 35) ) О screen . draw . fil l ed_rect (box , BLACK) 0 screen . draw . t e x t(t e x t_t o_sh ow, (2 0, t ex t_lines[l in e _ number] ), color=GREEN ) ############### ## СТАГТ ## ############### --n.г.)r1yc к-- Листинг 8-7. Добавление функции, которая выводит текст на экран Мы будем использовать функцию show _ text () О сле­ дующим образом (этот код добавлять в программу не нужно): show_t e xt 222 rЛАВА8 ( " текст " , номер_ строки )
Вместо номера строки должен быть либо О, если это _ верхняя строка, либо 1, если это вторая строка, предназна­ ченная для важных сообщений. В начале функции текст помещается в переменную строки- в переменную text _ to _ show, line _ number Мы используем список text _ lines, а номер _ О. чтобы запомнить положение (в пикселах) двух строк текста по вертикали б. Далее 1\IЫ определяем поле ~ и заполняем его черным цветом О , чтобы очистить строку для вывода нового сообщения. И, наконец, мы используем функцию screen. draw. text () из Руgаше Zего, чтобы вывести текст на экран 0. Эта функция в качестве аргументов принимает сам текст, его положение по осям х и у, а также цвет текста. Значения координат заключены в круглые скобки (это кортеж). В листинге слева 0,а из списка 8-7 позиция по оси х находится в 20 пикселах координаты положения по вертикали взяты text _ lines, используя число в line _ number в качестве индекса списка. ОТО&РАЖЕНИЕ НАЗВАНИR ПОМЕЩЕНИR ПРИ ВХОДЕ В HErO Чтобы протестировать функцию вим функцию start _ room(), show _ text (),давай доба­ которая отображает название помещения, когда игрок входит в него. Помести эту функцию в раздел ЦИКЛ ИГРЫ перед функцией показано в листинге listing8-8.py. 8-8. game _ loop (), как Сохрани программу в файл с именем Вывод программы остается прежним. -пропуск ############### ## ЦV"СЛ ИГ?Ы ## ############### def start_roorn (): show_text( " Tвoe местонахождение : " + roorn_narne, 0) def garne_loop(): --пропуск-- Листинг 8-8. Новая функция start _ room() УЛУЧШАЕМ КОСМИЧЕСКУЮ СТАНЦИЮ 223
Эта функция принимает значение переменной room _ name, которую мы настроили в функции generate _ map (). В ней содержится название текущего помещения, взятое из списка GAME _ МАР. Название помещения объеди­ няется с текстом Твое функции местонахождение и передается show _ text (). Теперь нам нужно сделать так, чтобы функция room ()запускалась start _ каждый раз, когда игрок входит в новое помещение. Если помнишь, в главе 7, в листинг 7-6 J\:IЫ доба­ вили фрагмент кода, который тут же закоr...1r...,1ентировали. Настала пора его включить! В этом коде все строки #start _ room() нужно заменить на start _ room(). # работает как «выключатель», сообщая Python Символ о том , что данную инструкцию нужно игнорировать. Что­ бы инструкция выполнялась, нужно удалить знак lffi. Replace Dialog Find: #. о l=start_roomQ х close Replace with: l ~tart_roomQ Find Options О Regular expression О Match case О Whole word G2J Wrap around Direction О Up@Down [ Replace ] 1Replace+ Find 1 [ Replace All Рис. 8.7. Раскомментирование функции start _ room(), которая срабатывает, когда игрок входит в помещение Нет никакой необходимости вручную искать и менять все эти строки. За нас это сделает IDLE. Выполни 8.7. следую­ щие действия, а также взгляни на рис. 1. Выбери команду меню Edit ~ (или нажми сочетание клавиш Replace (Правка ~ Заменить) Ctrl+H) в IDLE, чтобы открыть диалоговое окно поиска и замены текста. 2. Введи #start_rooш() в поле Find (Найти). 3. Введи staгt_rooш() в поле Replace With (Заменить на). 4. Нажми кнопку IDLE Replace All (Заменить все). самостоятельно заl\1енит инструкции в четырех ме­ стах и перейдет к последней в списке, как показано в ли­ стинге 224 rЛАВА8 8-9 (этот код вводить не нужно).
Сохрани файл под именем грамму с пш.ющью команды listing8-9.py и запусти про­ pgzrun listingB-9.py. Те­ перь, когда наш астронавт входит в помещение, должно вы­ свечиваться сообщение с названием этой комнаты. Функция срабатывает только в случае прохождения игро­ ком дверей, поэтому в начальном помещении название не отобразится. -пгоr1vгк if player у== -1: # FХОД CF!Ppxy #clock.unschedule(hazard_move) cuyrent_room -= МАР WIDTH generate_map () player_y = room_height - 1 # вход сни~у player х = int ( rоощ width / 2) !f вхuц F playe f rame - О start_room () return дF\''ОЬ проr1уск Л ист и нг 8-9. Функция start _ room() в ы пол няется, когда игрок пок и дает помещение Раздел ЭКРАН игры «ПобеР) завершен! Позже мы внесем в него еще несколько небольших изменений, чтобы показать врагов астронавта, но в общем и целом основа для большей части игры готова. В следующей главе мы начнем распаковывать личные вещи, чтобы добавить в игру реквизит. rотов ли ты к полЕТУ? Отметь следующие пункты, если чувствуешь, что тебе понятен материал этой главы. О Данные, передаваемые в функцию, называются ее аргу.лtентом. О Чтобы передать данные в функцию, необходимо указать их в круглых скобках после имени функции. Можно указать несколько аргументов, в этом случае их нужно разделить запятыми. Например: add(S, 7). О Чтобы функция могла принимать данные, необходимо настроить локальные переменные для получения аргумен­ тов при определении функции. УЛУЧШАЕМ КОСМИЧЕСКУЮ СТАНЦИЮ 225
D Код в разделе программы ЭКРАН позволяет рисовать поме­ щение, анимировать невидимую стену и отображать тексто­ вые сообщения. D Функция show _ text () принимает два аргумента: стро1<у текста, которую необходимо отобразить, и тип сообщения (О или 1). Строка 1 предназначена для важных сообщений. D Чтобы задать расположение объекта ка), нужно предоставить Rect (прямоугольни­ Python кортежи, которые содержат его расположение и размер. D Функция screen.draw.filled _ rect () позволяет нарисо­ вать закрашенный прямоугольник. D Цвета в Руgаше Zero представлены в формате ный, зеленый, синий). Например, (255, 100, RGB 0) - (крас­ это оранжевый цвет: максимум красного, капелька зеленого и отсутствие синего. D Если тебе нужно сделать какую-либо замену в тексте всей программы, можно воспользоваться командой (Заменить все) в Replace All IDLE. ОБСУЖДЕНИЕ МИССИИ Ниже представлены ответы на учебные миссии из этой главы. УЧЕБНАЯ МИССИЯ № 1 Если ты меняешь тип арисрметической операции, не забудь изме­ нить имя срункции и заменить знак «плюс» на знак «минус» внутри срункции print (). Если имя срункции оставить прежним, программа, конечно, будет работать, но, согласись, было бы странно для вычита­ ния чисел использовать срункцию с названием add (). Если ты поменяешь порядок следования чисел, срункция все равно будет вычитать второе число из первого, однако результат вычитания будет совсем не тем, что ты ожидал(а). Поэтому важно, чтобы инсрормация передавалась срункции в нужном порядке. В случае некоторых срункций Python может выдать ошибку, если ты отправишь аргументы в неправильной очередности. def subtract (first_numЬer, total second_numЬer): = f irst_numЬer - second_numЬer print(first_numЬer, "-" , second_numЬer, suЬtract (S, 7) subtract (2012, 137) suЬtract (1234, 4321) total)
РАСПАКОВКА ЯИЧНЫХ ВЕЩЕЙ Теперь, когда космическая ста н ция готова к эксплуатации, пришло время распаковать личные вещи и разнооб ­ разные инст рументы и оборудование, кото­ рые нам понадобятся в работе. В этой главе мы напишем код для объектов , которые можно переr..1ещать между комнатами (так называемый ре­ квизит). Во время игры ты сможешь находить предметы, подбирать их и переносить, а также использовать найден­ ные вещи для решения головоломок. ДО&АВЛЕНИЕ КОДА ДЛЯ РЕКВИЗИТА В главе 5 мы уже добавляли некоторые сведения о рекви ­ зите, когда работали с именами файлов изображений РАСПАКОВКА ЛИЧНЫХ ВЕЩЕЙ 227
и описаниями в словаре objects. Словарь objects содер­ жит информацию о том, что такое предмет. В этой главе мы добавим информацию, где в игре находятся пред.\1еты. Возможно, тебя удивляет, что мы работаем с реквизитом и декорациями по отдельности. Причина этого заключается в том, что информация о них используется по-разному: сло­ варь scenery использует в качестве ключей номера комнат. И это логично, ведь программе необходимо одновременно получать информацию обо всех декорациях в пш..1ещении. После добавления на карту помещения сведений о статич ­ ных декорациях, словарь scenery не понадобится до тех пор, пока игрок не войдет в новую комнату. Реквизит же, напротив, 1\южно перемещать, поэтому ин ­ формация о нем может понадобиться в любое время и в лю­ бом ПО1\1ещении. Если эта информация будет находиться в списке декораций, ее будет трудно найти и изменить. Поэтому мы создадим новый словарь, props, в который и будем добавлять сведения о реквизите. В качестве ключа выступит номер предмета, а каждая запись будет представ­ лять собой список со следующим содержимым . • Номер комнаты, в которой находится предмет из числа реквизита. • • Положение реквизита в помещении по оси у (в плитках). Положение реквизита в помещении по оси х (в плитках). Например, так будет выглядеть запись предмета под но­ мером 65: [50 , 1, 65 - молотка: 7] Объект находится в комнате равно 1, 50, его положение по оси у а положение по оси х равно 7. Предметы, которых нет в игровом мире или которые пе­ реносит игрок, будут «находиться)) в помещении О, что обо­ значает отсутствие реального местоположения в игре. Н а­ пример, некоторые объекты не появятся в игровом мире до тех пор, пока они не будут созданы или же они должны будут исчезнуть после того, как их уничтожат. Все они бу­ дут храниться в помещении О. 228 rЛАВА9
СОВЕТ В словарях props и objects используются одни и те же клю­ чи. Если тебе интересно, что в словаре пунктом props скрывается под 65, обратись к подробной информации о словаре objects. В листинге 9-1 указан код, который добавляет в игру ре­ квизит. Открой файл listillg8-9.py, последнюю версию игры из предыдущей главы. Добавь новый раздел РЕКВИЗИТ по­ сле функции show _ text () в разделе ЭКРАН, но до раздела СТАРТ. Добавь новые строки кода и сохрани новую про­ грамму в файл с именем listing9-1.py. Если тебе не хочется самостоятельно вводить код, просто скопируй и вставь его из файла data-cl'Japter9.py. Как и прежде, программу можно запустить с помощью команды pgzrun listing9-1.py. Вывод программы пока останется прежним, но ты хотя бы сможешь проверить, нет ли каких-либо ошибок в набранном тобой коде. --пpnпycк- s creen. draw. text ( text_to_ show, (20, text_lines[l ne_numЬer]), color~GREEN) ############### ## РЕКВИЗИТ ## ############### # # Реквизит #Любой # # - комнатами , игры номер объекты, они реквизит в можно появляться должен отправляется объекта : которые могут перемещать или настраиваться помещение [помещение , у, между исчезать. здесь. Реквизит вне О . х] О props = { 20: [31, О, 24: [ 45, о, 6 25: [32, О, 40: [0, 8, 56: [О, О, [ 31, 1, 60: [О, О, 64: [27, 8, 68: [О, О, 4], 21: [26, 2]' 2], 26: [27, 6], 53: [45, О], 57: [35, 7] / О], 61: [36, 3], 65: [50, О], 69: [30, О, 1], 22: [41, О, 2], 12, 5], # две стороны 1, 5], 54: [О, О, О], 4, 6], 58: [О, О, О], 1, 1]' 1, 7] / 3, 3]' 62: 66: 70: 23: [39, О, 5], одной двери 55: 59: [О, [ 36, 1, 6] / 63: [О, [39, 5, 6]' 67: [ 46, [ 4 7' 1, 3] / О, о, 0], О]' 1, 1]' РАСПАКОВКА ЛИЧНЫХ ВЕЩЕЙ 229
С) 71: 74: 78: LANDER_Y, LANDER_X], 72: [О, 0, 0], 73: [27, 4, 6], [28, 1, 11], 75: [0, О, 0], 76: [41, 3, 5], 77: [0, О, 0], [35, 9, 11], 79: [26, 3, 2], 80: [41, 7, 5], 81: [29, 1, 1] [0, checksum = О for key, prop in props.items(): if key != 71: # номер 7 1 пропускаем , О в checksum += == 37, assert len (props) рrint ( " Контрольная сумма assert checksum == 61414, f) in_my_pockets selected_item item_carrying игре он разный . (prop[O] * key + prop[l] * (key + 1) + prop[2] * (key + 2)) " (количество реквизита) " ) 0 print ( len (props), ~ потому что каждой "Ожидается 37 реквизита :" , " Ошибка предметов реквизита " checksum) в данных реквизита " [55] # первый элемент in_my_pockets[selected_item] О ############### ## CIAH ## ############### --пр~>пуск-- Листинг 9-1. Добавление в игру кода для реквизита Работу с новым разделом РЕКВИЗИТ мы начинаем с со­ здания словаря для хранения инфор1\1ации о реквизите О . В это.м словаре перечислены места расположения всего ре­ квизита, начиная с некоторых дверей тельную шлюпку с номера (40) (20-24), включая спаса­ и переносимые предметы, начиная 53. Здесь есть одна странность, на которую я хотел бы обра­ тить твое внимание. Мы считаем двери реквизитом, а не де­ корациями, поскольку они не всегда находятся на одноl\I и том же месте: когда они открыты, программа удаляет их из помещения. Большинство дверей после их открытия так и остаются в э т ом положении до тех пор, п ока игра не за­ кончится. Однако дверь, соединяющая помещения 27 и 32, способна закрываться, что означает, что игроки могут ви­ деть ее с двух сторон. В итоге нам понадобятся два реквизи­ та для отображения этой двери б. Она будет доступна в верхней части помещения ния 230 rЛАВА9 32. Две эти двери - 27 и в нижней части помеще­ объекты под номерами 25 и 26.
Реквизит 71 - это посадочны1':'1 модуль, который совершил аварийную посадку на поверхности планеты еще до начала игры. В качестве координат посадочного модуля мы использу­ ем значения переменных LANDER _У и LANDER _ Х из раздела П ЕРЕМЕННЫЕ программы ~ ,поскольку 1\Iестоположение моду­ ля каждый раз при запуске игры будет другим. Приземляясь, модуль двигался с такой скоростью, что мог бы до самых краев погрязнуть в марсианском грунте. Аппарат будет расположен в помещении О до тех пор, пока игрок не откопает его. Как и при работе с декорациями (см. главу 6), я восполь­ зовался проверкой контрольной суммы , чтобы тебе было проще определить, была ли допущена ошибка при вводе данных. Если это так, то, возможно, тебе не удастся пройти игру. Единственным реквизитом, который не учитывается при вычислении контрольной су .ммы, является число по­ 71, скольку для этого предмета в качестве позиций в каждой игре будут использоваться новые случайные числа О. Чтобы из;1.1енить код реквизита , просто закомментируй две инструкции по проверке контрольной су_r..н.лы @ . От­ ключить их можно следующим образом: #assert len(props) == 37 , " Ожидается 37 предметов реквизита " #assert checksum == 61414 , "Ошибка в данных реквизита " В окне командной строки 0 программа выводит итого­ вую контрольную сумму и количество элементов данных, поэтому если ты изменишь код реквизита, то сможешь ис­ пользовать эту информацию для изменения чисел в обеих инструкциях assert, чтобы они соответствовали твоим настройкам реквизита. Если ты так сделаешь, то можешь про­ должать использовать эти строки и не комментировать их. Также программа определяет две новые переменные и список, они понадобятся нам позже в этой главе. В списке in _ rny _ pocket s б хранятся все предметы, которые на­ шел игрок. Все вместе они составляют инвенпи~рь игрока. Один из этих предметов всегда выбран, поэтому игрок мо­ жет с ним что-нибудь сделать. Переменная в списке selected _ itern хранит индекс предмета in _ rny _ pockets. Переменная itern _ carrying хранит номер выбранного предмета. Иначе говоря, переменная РАСПАКОВКА ЛИЧНЫХ ВЕЩЕЙ 231
itern _ carrying обозначает номер предмета, который держит в руках астронавт. Чуть позже в этой главе мы пого­ ворим об этих переменных более подробно. ДО&АВЛЯЕМ РЕКВИЗИТ НА КАРТУ ПОМЕЩЕНИЯ Поскольку мы теперь знаем, где находится реквизит, пред­ лагаю вывести изображения предметов на экран. Работать это будет так: когда игрок входит в комнату, реквизит, кото­ рый там находится, по_r...1сщастся в список функция roorn _ rnap. Зате11л draw() использует этот список при отрисовке помещения. Инструкции, отвечающие за перенос реквизита на карту помещения, мы добавим в раздел ПОМЕЩЕНИЕ, внутрь функ­ ции generate _ rnap (). Инструкции должны располагаться после строк, которые мы ввели в главе с переr...1енными top _ left _ разделом ЦИКЛ ИГРЫ. х и 8, когда работали top _ left _ у, перед Поскольку все новые инструкции относятся к функции generate _ rnap (), перед ними должен быть отступ в четы­ ре и более пробела. Добавь в свою программу новые инструкции, указанные в листинге 9-2, и сохрани ее в файл listing9-2.py. Запусти его pgzrun listing9-2 .ру. Теперь в не­ с помощью команды которых помещениях должны появиться новые предметы, как это показано на рис. 9.1. Mi 1• •1 ,j'- Рис. 9.1. Минуту назад этой двери здесь не было! Ммм ... а этот баллон с воздухом может пригодиться 232 rЛАВА9
--г1p:·шycл-- top_left_x top_left_y О 6 ~ О 0 0 ~ center_x - 0.5 * room_pixel_width (center_y - 0.5 * room_pixel_height) + 110 for prop_numЬer , prop_info in props.items(): prop_room = prop_info[O] prop_ y = prop_info[l] prop_ x = prop_info[2] if (prop_room == current_room and room_map[prop_y] [prop_x] in [О, 39, 2]): room_map[prop_y] [prop_x] = prop_number image_here = objects[prop_numЬer] [О] image_width = image_here.get_width() image_width_in_tiles = int (image_width / TILE_SIZE) for tile_numЬer in range (l, image_width_in_ti l es): room_map[prop_y] [prop_x + tile_number] = 255 ############### ## ЦИ.{Л ~I ::>!:: ## ############### Листинг 9-2. Добавляем реквизит на карту текущего помещения Новый код мы начинаем с настройки цикла, с помощью которого будем перебирать элементы в словаре props О. Для каждого предмета ключ из словаря попадает в перемен­ ную сок prop _ number, а список координат передается в спи­ prop _ info. Чтобы сделать код более аккуратным, я добавил несколь­ ко переменных для хранения данных из списка info prop _ б. Программа извлекает информацию о номере ком­ наты (и ПОJ\1ещает ее в переменную prop _ room), а также о положении по оси у их (которые передаются переменным prop _ у и prop _ х соответственно). Далее мы выполняем проверку на соответствие значения переменной prop _ room помещению, в котором находится игрок €),а также проверяем, есть ли на полу О реквизит. При проверке напольного покрытия в список по1\1ещаются три разных типа пола (О для пола в помещениях внутри кос­ мической станции, 2 для грунта и ложенной в помещении 26). 39 для платформы, распо­ Программа проверяет располо­ жение реквизита, чтобы определить, что находится в этом месте на карте помещения. Если выпадает один РАСПАКОВКА ЛИЧНЫХ ВЕЩЕЙ 233
из перечисленных типов пола, то на полу отображается объ­ ект, который будет виден игроку. В противном случае ре­ квизит будет спрятан внутри декорации и игрок не СJ\южет его увидеть. Например, если на месте реквизита находится шкаф, предметы, что находятся внутри него, игроку видны не будут. Но если игрок заглянет в шкаф, то предметы по­ явятся на экране. Если реквизит находится в помещении и просто лежит на полу, карта помещения обновляется в соответствии с но­ мером этого реквизита 0. Неко т орый реквизит, такой как двери, оказывается шире одной плитки. Поэтому мы добавляем число 255 всем плит ­ кам, которые покрывают этот реквизит, кроме первой б . Это похоже на код, который мы использовали ранее в функ­ ции generate _ map ()для ций (см. листинг 6- 4 определения широких декора­ в главе 6). ПОЛУЧАЕМ ДАННЫЕ ОТ ФУНКЦИИ: &РОСАЕМ КОСТИ В главе 8 ты узнал(а), как передавать данные (т. е. аргументы) функции. Но мы можем не только передавать данные, но и по­ лучать их, давай узнаем, как это делается. Данный навык не­ обходим для того, чтобы создать функцию, которая сообщает, на каком объекте стоит игрок. В листинге 9-3 показана простая программа, которая по­ лучает число из функции и помещает его в переменную. Сей­ час мы будем работать не с игрой «Побег», а с отдельным проектом, поэтому создай новый файл, выбрав команду меню File ~ New File (Файл ~ Новый файл). Сохрани программу в файл с именем listing9-3.py. Эта про­ грамма работает без Pygame Zего, поэтому ты можешь запу­ стить ее с помощью команды меню Run ~ Run Module (Выполнить ~ Выполнить модуль) в окне сценария. Програм­ ма имитирует 1О-сторонний игральный кубик. 234 rЛАВА9 О import random 6 de get_numЬer () : die number = random . randint ( l , 10)
о rerur: die_numЬer get_numЬer 0 randorn_number = (!) print (random_numЬer) () Листинг 9-3 . Симулятор 1О-стороннего игрального кубика демонстрирует, как передать число из функции В начале этой программы мы подключаем модуль random О, который предоставляет Pythoп набор функций для случай­ ного выбора значений. Затем мы создаем функцию number () get _ б, которая генерирует случайное число в диапазоне от 1 до 1О ~ и помещает результат в переменную dice number. Обычно при запуске функции (на языке программистов вызов функции) указывается ее имя, например так: get_number () На этот раз мы не просто вызываем функцию, мы говорим Pythoп поместить результат ее работы в переменную random _ number 0. Когда функция отправляет свой резуль­ тат с помощью команды в переменную ret urn О, это значение сохраняется ra n dom _ number. Затем команда print про­ граммы выводит это значение 0. Этот код показывает, что способ получения информации от функции заключается в использовании переменной для хранения результата запуска функции 0 и применении ин­ струкции return для передачи результата в конце работы функции О. Ты можешь возвращать не только числа, но и стро­ ки и списки. Таким образом мы можем использовать результа­ ты работы функции в других частях программы, там, где это возможно. Этот метод позволяет основной части программы получать информацию из локальной переменной функции (в данном случае dice _ number ), которая обычно доступна только внутри самой этой функции. Эта программа нам больше не понадобится, поэтому ты можешь закрыть ее, когда закончишь с ней экспериментиро­ вать. РАСПАКОВКА ЛИЧНЫХ ВЕЩЕЙ 235
ПОИСК О&ЪЕКТА НА КАРТЕ ПОМЕЩЕНИЯ Вскоре мы добавим в нашу игру код, который позволит игроку собирать предметы на косr.1ической станции. Давай подумаем, как можно выяснить, какш'::'r Иl\Iенно предмет под­ нял игрок. Когда игрок взаимодействует с декорациями или рекви­ зитом, программе нужно знать номер объекта, который тот использует. Как правило, сделать это очень просто. Если кар­ та помещения «говорит>>, что номер реквизита в конкретном расположении игрока равен мы понимаем, что это моло­ 65, ток. Программа может отобразить на экране описание мо­ лотка и позволить игроку поднять его или использовать. В случаях, когда игрок взаимодействует с широкими объектами, теми, что занимают сразу несколько плиток, определение номера объекта усложняется. Как ты помнишь, мы используем число 255 для обозначения плиток, которые занимает широкий объект, но при этом само число не отно­ сится к какому-либо реквизиту. Поэтому нашей программе необходиl\ю определить реальный номер объекта, переме­ щаясь влево по карте помещения до тех пор, пока не будет найдено число, отличное от 255. Например, если игрок касается крайней правой трети двери, программа увидит, что эта позиция содержит чис­ ло 255, поэтому она проверит значение позиции слева. Эта позиция также содержит число 255, поэтому программа проверит позицию еще левее. Если эта плитка отмечена числом, отличным от 255, программа будет знать, что опре­ делила реальный номер объекта, например, рей). Используя объект номер 20, 20 (одна из две­ программа позволит иг­ року осмотреть или открыть дверь. Мы создадим две функции, которые будут вычислять но­ мер объекта, как показано в листинге их в код игры, выбери команду меню 9-4 . Чтобы добавить File ~ Open (Файл ~ Открыть) и открой файл Сейчас мы создадим lz:sting9-2.py. новый раздел программы, ВЗАИМОДЕЙСТВИЕ С РЕКВИЗИ­ ТОМ. Размести его после раздела РЕКВИЗИТ. В этот новый раздел мы добавим код, позволяющий персонажу поднимать и выбрасывать реквизит. 236 rЛАВА9
Сохрани обновленную программу в файл с и:-..1енем listing9-4.py. Результат выполнения программы пока не со­ держит ничего нового, но ты можешь запустить програl\1- му с помощью команды pgzrun listing9-4 . ру, чтобы убеди т ься, что в коде нет ошибок. Проверь окно команд­ ной строки на отсутствие сообщений о каких -либо про ­ блемах. -пгоr1ус:к in_my_pockets selected_item item_carrying [55] О # первый эгеме1-1т in_my_pockets[selected_item] ################################# ## ВЗАИМОДЕЙСТВИЕ С РЕКВИЗИТОМ ## ################################# О def find_object_start_x () : checker_x = player_x @) while room_map[player_y] [checker_x] О checker_x -= 1 0 return checker_x 6 255: ~ def get_item_under_player (): item_x = find_object_start_x() Q item_player_is_on = room_map[player_y] [item_x] 0 return item_player_is_on б --пропуск-- Листинг 9-4 . Поиск реального номера объ е кта П режде чем мы перейдем к изучению того, как работает этот код, я хочу объяснить, как игровой цикл позволяет иг­ рокам взаимодействовать с реквизитом и декорациями. 1. Когда игрок нажимает одну из клавиш для перемещения, програ!-.1ма изменяет положение игрока (даже если тот в результате движения окажется в невозможном для нахо­ ждения месте, на п риl\·tер внутри стены). 2. Любые действия, которые запрашивает игрок, программа выполняет, используя данные объекта в местоположении игрока. Это означает, что игрок и объект на данный момент находятся в одной и той же точке помещения. РАСПАКОВКА ЛИЧНЫХ ВЕЩЕЙ 237
3. Если игрок стоит где-то, где ему находиться запрещено (например, внутри стены), программа перемещает его на исходную позицию. Весь этот процесс происходит так быстро, что ты не смо­ жешь заметить то, как наш астронавт входит в стену или иную декорацию. Следовательно, игрок может использовать клави ш у переr·лещения плюс клавишу действия, чтобы осмо­ треть или использовать декорацию. Например, ты можешь войти в стену и нажать клавишу Пр обел, чтобы изучить стену и увидеть ее о п исание. Точно так же это работает с объектом, на котором стоит игрок, например, с реквизи­ том , расположенным на полу. Первая новая функция, которую мы добавили в листин­ ге 9-4, - это find _ object _ start _ положение обозначено числом 255, х () О . Если место­ то эта функция находит начальную позицию ближайшего объекта в позиции игро­ ка, двигаясь справа налево. Для этого функция присваивает переменной checker _ х значение, соответствующее позиции игрока по оси х б . Мы используе.1\1 цикл, который будет вы п олняться до тех пор, пока на карте помещения в позиции по оси х переr·ленной checker _ число 255 х и в позиции игрока по оси у будет указано ~ . Внутри этого цикла находится инструкция, которая уменьшает значение переменной на checker _ х 1 О , перемещаясь на 1 плитку влево. Когда цикл заканчи­ вается, переменная checker _ х содержит крайнюю левую позицию, в которой начинается объект. Далее это значение отправляется обратно 0 в инструкцию, которая вызвала данную функцию. Вторая новая функция player () Ф - - get _ item _ under _ определяет, какой именно объект находится в позиции игрока. Она использует предыдущую функцию, чтобы понять, где начинается объект, и сохраняет позицию по оси х в качестве значения переменно1":'1 item _ х б . Затем на карте помещения просматриваются данные для этой по­ зиции, чтобы понять, какой объект там расположен 238 rЛАВА9 0.
После чего полученный ноr-.1ер будет отправлен обратно в инструкцию, которая запустила эту функцию 0. ПОД&ИРАЕМ ПРЕДМЕТЫ Теперь, когда все необходимые для данного этапа функции готовы, мы можем перейти к созданию еще одной парочки функций, которые позволят игроку поднимать предметы и отправлять их в инвентарь. Затем мы займемся настрой­ кой управления с клавиатуры. ПОД&ИРАЕМ РЕКВИЗИТ Добавь две функции, показанные в листинге раздела ВЗАИМОДЕЙСТВИЕ С сразу после кода из листинга 9-5, в конец РЕКВИЗИТ ОМ программы, 9-4. Сохрани програ:мму в файл с именем listing9-5.py. Про­ верь код на отсутствие ошибок, запустив файл с помощью команды pgzrun listing9-5.py. Вывод программы оста­ нется прежним. Здесь мы просто добавляем новые функции, но использовать их пока нельзя, потому что управление с клавиатуры еще не готово. --nг.Jr1ycк-- tem_player is on room_map [player return item_player_is on 1 у] [item. _х] def pick_up_object (): global room_map О item_player_is_on = get_item_under_player() 6 if item_player_is_on in items_player_may_carry: ~ room_map[player_y] [player_x] = get_floor_type() О add_object(item_player_is_on) show_text ( " Tы взял : " + objects[item_player_is_on] [3 ] ' 0 0) sounds.pickup.play() time.sleep(0.5) else : show_text( " Tы не можешь взять это! " , @ def add_object (item): # Добавление предмета global selected_item, item_carrying б in_my_pockets.append(item) (;) item_carrying item 0 selected_item = len (in_my_pockets) - 1 0) в инвентарь . РАСПАКОВКА ЛИЧНЫХ ВЕЩЕЙ 239
d i splay_inventory() props[itern ] [О] = О # Перенесенные предметы # в комнату О (за пределами карты) . W попадают def display_inventory (): print (in _ту_ p o ckets) -- пропуск -- Листинг 9-5. Добавление функций, с помощью которых астронавт сможет поднимать предметы Функция pick _ up _ object () запускается, когда иг­ рок нажимает клавишу G, чтобы поднять предмет. Все начи­ нается с передачи номера объекта, который находится в по­ зиции игрока, в переменную item _ player _ is _ on О . Если объект можно переносить с места на 1\•1есто 6, в дело вступает остальная часть функции. Чтобы удалить предмет с пола, программа заменяет изо ­ бражение объекта в позиции игрока изображением пола - грунта или напольной плитки (с помощью номера объек­ та) С). Функция get _ floor _ type () используется для определения типа пола в этом по1\1ещении. Когда помеще­ ние перерисовывается, предмет исчезает с пола. Выглядит это весьма правдоподобно, как буд то игрок и впрямь подо­ брал предмет. Затем поднятый объект попадает в список п редметов, которыми владеет игрок, с помощью функции add _ object () О. Далее на экран выводится сообщение, что игрок поднял предмет, которое сопровождается звуковым эффектом. На случай, если игрок слишком долго будет удерживать клавишу, и чтобы сообщение не было перезаписано, уста ­ навливаем короткую задержку в полсекунды с пш.ющью ин­ струкции time.sleep(O.S). Если объект нельзя перемещ а ть, программ а выводит со­ общение о том, что игрок не может его переместить 0. Н а­ пример, декорации переносить нельзя, поэтому нам следует сообщить об этом игроку. В противном случае он может по­ думать, что нажимает не ту клавишу или программа попро­ сту не работает. С помощью функции п редмет в список 240 rЛАВА9 add _ object () мы добавляем in _ my _ pockets, который хранит
предметы, подобранные игроком (инвентарь игрока). В на­ чале функции номер объекта, который получает эта функ­ ция, помещается в позицию (item) локальной переменной @ . Элемент помещается в конец списка pockets _ list с помощью функции in _ my _ append () fJ. Для хранения номера объекта, который находится вру­ ках игрока, мы используем глобальную переменную item _ carrying, поэтому ей присваивается но.r"1ер объек­ та, находящегося в этой позиции @ . Переменная selected _ item обозначает последний элемент в списке. Таким образом объект, который только что взял игрок, бу­ дет выбран 0. Эти пере1\1енные понадобятся нам при даль­ нейшем использовании объектов, а также в тех случаях, ко­ гда функция display _ inventory () будет выводить на экран список предметов. Пока эта функция выводит спи ­ сок в окне командной строки. Наконец, мы присваиваем элементу в словаре props номер помещения, равный О @. Это означает, что только что подобранный предмет не отображается на игровой карте. Если бы мы этого не сделали, то, когда игрок во1':'1дет в это помещение в следующий раз, предмет появится снова. ДО&АВЛllЕМ УПРАВЛЕНИЕ С КЛАВИАТУРЫ Чтобы новые функции работали, нам необходимо настро­ ить управление с клавиатуры. Мы будем использовать кла­ вишу G для получения игроком предмета. П омести новые инструкции, указанные в листинге в функцию game _ loop (), 9-6, которая находится в разделе ЦИКЛ ИГРЫ программы. Новая инструкция должна выпол­ няться после того, как програ.мма проверит выходы, но до того, как игрок будет перемещен назад из-за того, что находится в неподходящем для него месте. --пpoпycк-- player_f rame start_room () return О 6 О if keyboard.g: pick_up_object() РАСПАКОВКА ЛИЧНЫХ ВЕЩЕЙ 241
# Е~ли иг~ок очутился в недог.устимом для него месте, переместим # ee'G на ~ад. if room_map[player_y] [player_x] not in i:ems_player_may_stand_on: #\ # or hazard_map[player_y] [player_x] != О: пporrvcк - Листинг 9-6. Добавляем управ л ение с клавиа ту ры Пере д первой новой инструкцией О тебе нужно доба­ вить отступ в четыре пробела, поскольку она относится к функции garne _ loop (). Перед второй инструкцией сде­ лай отступ в восеl\1ь пробелов 6, поскольку она относится к приведенной выше инструкции клавишу G О, if. Когда игрок нажимает эти инструкции вызывают функцию pick _ up _ object () 6 . Сохрани код в файл listing9 - б.py. Теперь, после запуска игры командой pgzrun listing9-6.py, твой персонаж сможет поднимать предметы. Чтобы проверить, так ли это, попробуй поднять кисло­ родный баллон, который находится в первом помещении. Для этого подойди к нему и нажми клавишу G. Ты услы­ шишь звук и увидишь сообщение, после чего баллон исчез­ нет из помещения. Теперь каждый раз , как ты будешь подбирать предмет, в окне командной строки (где ты вводил(а) инструкцию pgzrun) будет отображаться список инвентаря, например так: [ 55 , 59] Каждый раз ты будешь видеть, что в конце списка появ­ ляется новый элемент. Предмет под номером 55, йо - йо, на­ ходится в твоем кармане с самого начала игры. НАСТРАИВАЕМ ИНВЕНТАРЬ Итак, теперь ты можешь подбирать любой реквизит, кото­ рый находится на космической станции. Давай попробуем упростить просмотр инвентаря для случаев, когда нам нужно узнать, какой именно предмет несет наш астронавт, а также сделаем так , чтобы игрок мог выбирать предметы, 242 rЛАВА9
чтобы ими воспользоваться. Для этого мы создадим функ­ цию display _ inventory (),отображающая в верхней части игрового окна предметы, которые несет игрок. Затем мы усовершенствуем управ­ " ление с клавиатуры, чтобы игрок мог нажать клавишу ТаЬ для выбора сле­ дующего предмета в инвентаре. Вы ­ бранный предмет будет выделен рам- ~ Боnьшая стоповая nо • ка Рис. 9.2. В верхней части игрового окна отображается инвентарь кой, а под ним появится его описание. На рис. 9.2 показано, как это должно выглядеть. ОТО&РАЖЕНИЕ ИНВЕНТАРЯ Листинг 9-7 содержит код, который необходимо добавить в программу. В листинге 9-5 функция display _ inventory ()уже содержит некоторые строки. Замени их новым кодом. Сохрани изменения в файл listing9-7.py. При запуске программы с помощью команды listing9-7.py pgzrun ты увидишь предметы, которые по мере их нахождения добавляются в твой инвентарь, его можно уви­ деть в верхней части экрана. -пропуск selected_ i tem - len ( in_my _pockets) - 1 display_inventory() props[iterr][O] =О# ГЕ·ре1-·есеюые r,,p,_~мe'I<r # (з~ пределами карт~) . О rorciднo" if len (in_my_pockets) return ~ start_display = (selected_i tem // 16) * 16 list_to_show = in_my_pockets[start_display: start_display + 16] selected_marker = selected_item % 16 for item_counter in range ( len (list_to_show)): item_number = list_to_show[item_counter] image = objects[item_number] [О] screen.Ыit (image, (25 + (46 * item_counter) , 0 0 «;) комнату О def display inventory(): Ьох = Rect ( (0, 45) , (800, 105)) screen.draw.filled_rect(box, BLACK) 6 О в == О: 90)) box_left = (selected_marker * 46) - 3 = Rect ( (22 + box_left, 85), (40, 40)) Ьох РАСПАКОВКА ЛИЧНЫХ ВЕЩЕЙ 243
screen.draw.rect(box, WHITE) item_highlighted = in_my_pockets[selected_item] description = objects [ i tem_highlighted] [2] screen.draw.text(description, (20, 130), color= "white" ) '1> ############### ## СТАРТ ## ############### generate_map () clock.schedule ~n~erval(game_loop, 0.03) clock.schedule interva (adjust wall transparency, W clock.schedule_unique(display_inventory, 1) Л истинг 9-7. 0.05) Ото б ражение инвентаря Новая функция display _ inventory () начинает свою работу с того, что перекрывает область инвентаря черным прямоугольником, тем самым «сбрасывая» предыдущее изо ­ бражение О . Если игрок ничего не несет с собой, функция просто возвращает управление в програмN1у без каких-либо дальнейших действий, так как предметов для показа нет 6. На экране есть пространство для отображения только 16 предметов, однако наш герой в силах та щ ить гораздо боль­ ше вещей. Чтобы выбрать любой из отображаемых на экра­ не предметов, игрок может нажать клавишу ТаЬ для переме­ щения слева направо. Если список in _ my _ pockets становится слишком длинным и его содержимое не поме­ щается на экране, программа отобразит только 16 предме ­ тов. Если выбран последний предмет (из показанных 16) на экране и игрок нажимает клавишу ТаЬ, отобразится сле­ дующий предмет (17 и так далее). Если игрок нажимает кла­ вишу ТаЬ, находясь на последнем предмете, то инвентарь будет отображаться, начиная с первого предмета из списка. Часть списка in _ my _ pockets, отображаемого в дан­ ный момент на экране, мы сохраняем в другом списке, list _ to _ show, и используем цикл, чтобы вывести его на экран @.Ц икл помещает числа в переменную counter, item _ которая используется для извлечения нужного изображения предмета, а также для определения, в какой п озиции экрана его следует нарисовать б. Самая сложная задача здесь - должны присутствовать в списке 244 rЛАВА9 понять, какие элементы list _ to _ show.
В переменной start _ display вого элемента из списка in мы сохраняем индекс пер­ _ ту _ который (эле­ pockets, мент) программа должна отобразить @ . Оператор полученный номер объекта на 16, // делит округляя результат в меньшую сторону. Затем результат умножается на 16, что­ бы получить индекс для первого элемента группы. Напри­ мер, если выбранный элемент имеет номер димо разделить на 16 9 на 16 (0,5625), 9, тебе необхо­ округлить (О) и умножить (все равно О), получив результат О. Это начало списка. Это логично, поскольку мы знаем, что на экране есть место для 16 предметов, а число зить группу с предметом (1,375), тат 16. меньше 9 22, 16. Если нужно отобра ­ мы разделили бы округлили О и умножили на 16, 22 на 16 получив резуль­ Это начало следующей группы, поскольку индексы первой группы находятся в диапазоне от О до Список list _ to _ show 15. мы создаем, используя J\1етод среза списка, благодаря которому можно получить доступ к части списка. Если указать два индекса списка, разделив их двоеточием, программа вырежет эту часть списка. Часть, кото­ рую мы используем, начинается с индекса и заканчивается через 15 start _ display элементов после О . Срез списка про­ пускает последний элемент, поэтому в качестве конечной точ­ ки мы используем значение start _ display + 16. Чтобы понять, какой предмет следует выделить (то есть показать на экране, что он выбран) в новом списке 0, нам понадобится произвести некоторые вычисления. Индекс элемента будет находиться в диапазоне от О до ним мы его в переменной 15, selected _ marker. и сохра­ Значение этой переменной равно остатку от деления выбранного но­ мера объекта на 16. Например, если выбран объе1~т 18, то при отображении второй группы элементов индекс будет равен 2. вен О?) В (Помнишь, что индекс первого элемента всегда ра­ Python доступен оператор деления по модулю, %, который ты можешь использовать для получения оста т ка от деления. Чтобы выделить на экране выбранный предмет, мы рису­ ем вокруг него прямоугольник, используя объект Rect, и располагаем его в левой части окна игры @ . В отличие от закрашенных прямоугольников, с которыми мы работали РАСПАКОВКА ЛИЧНЫХ ВЕЩЕЙ 245
ранее (например, О), благодаря этой инструкции мы можем нарисовать прозрачный прямоугольник с белой рамкой. Описание выбранного предмета отображается под окном инвентаря 0, поэтому игроки могут «п роюrстывать» свои предметы и читать их описания. И, наконец, J\IЫ должны учесть, что, когда программа за­ пускается впервые, на экране должен отобразиться инвен­ тарь. Для этого мы создаем небольшую временную задерж­ ку@, чтобы избежать проблем, связанных с попыткой использовать инструкцию screen .Ыit () до запуска Pyga111e Zero. В то время как метод interval () используется для запуска функции по расписа­ нию, метод clock. schedule _ clock. schedule _ unique ()запускает выпол­ нение функции лишь раз, после того, как выйдет время задержки. НАСТРАИВАЕМ КЛАВИШУ ТАВ для УПРАВЛЕНИЯ ИНВЕНТАРЕМ Сейчас, когда ты запускаешь игру, инвентарь отображается на экране, но мы пока не можем переходить по очереди от одного предмета к другому, поэтому у нас всегда выбира­ ется последний наi::'щенный предмет. Давай настроим управ­ ление с клавиатуры (с помощью клавиши ТаЬ), чтобы из инвентаря можно было выбрать конкретный предмет. Добавь новые инструкции из листинга game _ loop () 9-8 в функцию сразу после инструкций, отвечающих за сбор предметов с помощью клавиши G, из листинга 9-6. Поскольку эти строки кода находятся внутри функции game _ loop (), необходимо добавлять к ним отступ в четы­ ре и более пробела. Сохрани изменения в файл с названием listing9-8.py. Те­ перь после запуска программы с помощью команды listing9-8.py pgzrun ты сможешь выбрать тот или иной предмет из своего инвентаря, нажав клавишу ТаЬ. (Клавиша ТаЬ обычно находится в левой части клавиатуры, на не1"::'r часто изображены две стрелки.) Если есть желание, ты можешь попробовать подобрать несколько предметов, чтобы проверить управление с кла­ виатуры или же просто переходи к следующему разделу, 246 rЛАВА9
в котором мы пополним наш инвентарь дополнительными предметами, чтобы провести тестирование программы уже на них. --пропуск-- f 1 yi.- ci.• J. р 1 с -;:_ 1f О 6 ~ О 0 0 б Q ~ h iЕ С t /J if keyboard.tab and len (in_my_pockets) > О: selected_item += 1 if selected_item > len (in_my_pockets) - 1: selected_ item = О item_carrying = in_my_ pockets[selected_ item] display_inventory() time.sleep(0.2) if keyboard.d and item_carrying: drop_object(old_player_y, old_player_x) if keyboard.space: examine_object() -- пр о пу ск -- Л истин г 9-8. Испо л ьзование клавиш и ТаЬ для выбора п редмета из инвентаря П ервый блок инструкций запускается в том случае, когда игрок нажимает клавишу Та Ь , но только если в списке in _ ту _ pocket s что-то есть (поэтому его длина должна быть больше О) О . Чтобы выбрать следующий предмет в инвентаре, мы уве­ личиваем значение переменной selected _ iteт на 1 6 с каждым последующим нажатием клавиши ТаЬ. В этой пе­ ременной хранится индекс (начинается с О), поэтому про­ грамма вычитает из длины списка число 1, чтобы прове­ рить, не находится ли выбранный элемент в конце списка @. Если это так, то выбранный элемент сбрасывается, и мы пе­ реходим к первому элементу с индексом О. П еременной iteт _ carrying мы присваиваем значе­ ние в виде номера выбранного объекта (который берется из списка in _ту_ in _ту_ pockets pockets) О . Напри.\1ер, если список содержит объекты с номерами 55 и 65, а выбранный элемент имеет индекс О, то переменной iteт _ carrying элемент в списке будет присвоено значение in _ту_ pockets). 55 (первый Наконец, мы РАСПАКОВКА ЛИЧНЫХ ВЕЩЕЙ 247
отображаем наш инвентарь с помощью функции display _ inventory(), созданной ранее 0. Пока 1\IЫ работаем с этой частью программы, давай вспо­ МНИJ\I, что мы недавно добавили код для управления с клавиа­ туры, чтобы наш астронавт мог выкладывать или изучать предметы. Функция drop _ object () запускается, когда иг­ рок нажимает клавишу О , при этом значение переменной item _ carrying не должно быть равно False. Эта функ­ ция получает предыдущие координаты игрока по осям у их, чтобы обозначить место, где следует выложить предмет 6. Помни, что текущее местоположение игрока может оказаться даже внутри стены, в зависимости от того, в каком моменте игрового цикла мы находимся. Как я уже говорил:, предмет из инвентаря нужно выкладывать в той позиции персонажа, где он находился до момента совершения движения. Также мы добавили инструкции для запуска функции examine _ object () 0 после нажатия клавиши Пр обел 0 . П ока что не нажимай в игре клавиши О и Пр обел, иначе произойдет сбой программы, поскольку мы еще не добави­ ли функции, которые обрабатывают нажатие этих клавиш. Но вскоре мы решим эту проблему. ТЕСТИРУЕМ ИНВЕНТАРЬ Было бы неплохо сейчас протестировать нашу программу, однако на данный момент в нашем инвентаре слишком мало предметов. Чтобы сэкономить время, мы настроим код так, чтобы игрок мог добавлять в инвентарь несколько предме­ тов за раз. Так мы СJ\южем протестировать отображение инвентаря и управление им с помощью клавиши Та Ь . К огда игра начнется, мы заполним список pockets in _ту _ различными предметами. Быстрее всего это мож­ но сделать, изменив инструкцию настройки это г о списка в разделе РЕКВИЗИТ следующим образом (но пока что не де­ лай этого!): in_my_pockets = items_player_may_carry Теперь, после запуска игры, твой персонаж сразу полу­ чит все предметы, сколько он может нести. Правда, в этом 248 rЛАВА9
случае ты получишь куда меньше удовольствия от игры, по­ ТОJ1лу что игрок уже на старте будет обладать всеми ве щ ами , включая те, которые можно обнаружить лишь по ходу игры. Из-за этого некоторые головоломки просто потеряют свой смысл. Н о программу протестировать все же как- то надо, поэто ­ му я предлагаю тебе создать список предметов, подобный следующему: i n_my_ p oc k ets = [5 5, 5 9, 61 , 64 , 65 , 66, 67 ] * 3 В этой строке мы создаем список, в котором содержатся три указанные последовательности элементов. В итоге у тебя получится инвентарь , содержащий п о три одинако­ вых предмета (что невозможно в реальной игре), так что ты сможешь проверить, правильно ли отображается инвентарь, в котором находится более 16 предметов. Завершив тестирование, верни коду его предыдущий вид. Иначе во время игры ты 1\IОЖешь столкнуться с неожи­ данностями. Вот как должна выглядеть эта строка: in_my_p oc ket s = [ 55] ВЫКЛАДЫВАНИЕ ПРЕДМЕТОВ Собирать ве щ и, разбросанные по всей космической стан­ ции, - это очень увлекательно, но иногда тебе по т ребуется выкладывать предметы, чтобы ты мог(ла) либо применить их , либо попросту выбросить. Для этого нам понадобятся две новые функции, которые п озволят выкладывать пред­ меты, и принцип работы которых схож с фу н кциями для сбора объектов, только наоборот. Функция ци и drop _ object () (противоположность функ­ pick _ up _ object ()) позволяет выложить предмет на пол в том месте, где находится игровой персонаж. В ли­ стинге 9-8 мы добавили обработку нажатия клавиши для за­ пуска этой функции. Функция remove _ object () похожа на функцию add _ object (),но работает она в обратном порядке: сначала предметы извлекаются из инвентаря, а затем он обновляется. РАСПАКОВКА ЛИЧНЫХ ВЕЩЕЙ 249
Добавь новые функции, указанные в листинге нец программы в раздел ВЗАИМОДЕЙСТВИЕ С ТОМ. Сохрани новую программу в файл 9-9, в ко­ РЕКВИЗИ­ listing9-9.py. Теперь после запуска программы с помощью команды pgzrun listing9-9.py ты. Это касается как ты сможешь выкладывать предме­ i':ro-r':'ro, которое с саrvюго начала игры лежит у тебя в кармане, так и всех остальных предметов, что были собраны во время исследования космической станции. --пт::,у;уск - description ~ obJects[item_highlighted] [2] screen. draw. text (description, (20, 130), co~or-"white") О def drop_object (old_y, old_x): global room_map, props 6 if room_map[old_y] [old_x] in [О, 2, 39]: # куда можно # положить предметы ~ props[item_carrying] [0] current room props[item_carrying] [1] old_y props[item_carrying] [2] old_x О room_map[old_y] [old_x] = item_carrying show_text( " Tы положил " + objects[item_carrying] [3], 0) sounds.drop.play() 0 remove_object(item_carrying) time.sleep(0.5) @ else : # Этот код выполняется только в том случае , если # здесь уже есть реквизит show_text( " Tы не можешь положить это тут ." , 0) time.sleep(0.5) def remove_object (item): #Извлечение предмета из инвентаря global selected_item, in_my_pockets, item_carrying б in_my_pockets.remove(item) Q selected_item = selected_item - 1 0 if selected_item < О: selected_item = О '1i> if len (in_my_pockets) == О: # Если инвентарь пуст item_carrying = False # Присваиваем переменной # item_ car r ying значение False else : # В противном случае присваиваем выбранный предмет item_carrying = in_my_pockets[selected_item] display_inventory() ############### ## rTAPT ## ############### - r,т::-опускЛистинг 9-9. Добавление функций для выкладывания предметов 250 rЛАВА9
Функция drop _ object ()ожидает два значения: коор­ динаты по осям у их предыдущей позиции игрока. Если иг­ рок переместился с помощью функции game _ loop (), то это будет позиция, в которой он находился до того, как начал перемещение. Иначе эти значения будут соответство­ вать координатам того места, в котором игрок находится се1":'1час. Так 1\1Ы можем быть полностью уверены в том, что именно это место является подходящим для того, чтобы вы­ ложить предмет, и он не окажется, например, внутри стены. Значения предыдущих координат игрока присваиваются пе­ ременным old _ у и old _ х в этой функции О . Программа проверяет, является ли поверхность на карте помещения в предыдущей позиции игрока полом какого­ либо типа. Если да, то здесь можно выложить реквизит, и в этом случае будут использоваться инструкции, отвечаю­ щие за выкладывание предметов из инвентаря. Если нет @ , игрок получает сообщение о том , что здесь нельзя выбро­ сить предмет. Такая ситуация может произойти, например , в том случае, если в этой позиции уже находится реквизит. Если игрок выложил предмет, то нам нужно обновить словарь props. Переменная item _ carrying содержит номер предмета, который несет игрок. Соответствующая запись в словаре props представляет собой список. Первый элемент списка (индекс О) - это помещение, в котором на­ ходится реквизит, второй элемент (индекс ние по оси у, а третий элемент (индекс 1) - его положе­ положение 2) - по оси х. Эти значения представляют собой номер текущего помещения и коорлинаты предыдущей позиции игрока @ . Кроме того, нам необходимо обновить карту текущего помещения, поскольку теперь в нем должен появиться выло­ женный предмет О. Игра выведет на экран сообщение и воспроизведет звук, который оповестит игрока, что он успешно выложил тот или иной предмет, а затем эта вещь удаляется из инвентаря с помощью функции remove _ object () 0 . Функция remove _ object () берет предмет из инвента­ ря игрока и обновляет значение переменной item. selected _ Номер объекта, переданный этой функции, сохраня­ ется в переменной item, а затем функция remove () б РАСПАКОВКА ЛИЧНЫХ ВЕЩЕЙ 251
удал я ет его из с п иска in _ my _ pockets. Далее, когда вы ­ б р анный элемент удален, коли ч ество выбран н ых элементов уменьшается на 1 @ .Теперь выбран будет предыдущий эле­ мент из списка . Если значение выбранного элемента ока­ жется меньше О, то з н ачение в ы бранного элемента сбрасы­ вается на О 0. Это происходит в том случае, когда иг р ок выкладывает предмет, который идет первым в списке инвен­ таря. Если руки и г рока свободны, то пе р еменной carrying присваивается значение False item _ @ .В п ротивном случае зна ч ение устанавливается равным номеру выбра н но­ го предмета. Функция display _ inventory () перерисо ­ вьшает изображение инвентаря так, чтобы было видно, что предме т удален. YЧE&HASll МИССИSI No 1 Пришло время для инструктажа по технике безопасности. Сможешь взять кислородный баллон и доставить его в лаза­ рет? Поставь баллон возле кровати, расположенной по цен­ тру. Чтобы проверить работу программы, покинь помещение, а затем снова войди, чтобы убедиться, что баллон все еще на месте. ИЗУЧАЕМ О&'ЬЕКТЫ Исследуя космическую станцию, ты наверняка захочешь повнимательнее изучить предметы, чтобы понять, смо - гут ли они пригодиться тебе в выполнении твоей миссии. Инструкция examine выводит подробное описание пр ед­ мета и работает как с декорацияJ1.1и, так и с реквизитоJ1.1. Исследуя один объект, ты иногда можешь найти что-нибудь еще. Н апример, осматривая шкаф, може ш ь найти что-то и внутри него. Нажатие клави ш и Проб ел запускает функцию examine _ object (). (Этот элемент управления мы доба­ вили в листинге 9- 8.) в листинге после функции 9-10, Добавь новую функцию, указанную рая появилась в листинге 252 rЛАВА9 9-9. remove _ object (), кото­
Сохрани программу в файл с именем программу с помощью команды listing9-10.py. Запусти pgzrun listing9-10.py. Теперь ты можешь исследовать объекты, подойдя к ним и нажав клавишу Пробел. Если ты, например, нажмешь кла­ вишу j совместно с клавишей Пробел , находясь у стены в дальней части помещения, то сможешь осмотреть стену. item_carrying display_inventory() -пропуск О 6 ~ О 0 @ б ~ 0 @ in_my_pockets[selected_item] def examine_object (): item_player_is_on = get_item_under_player() left_tile_of item = find_object_start_x() if item_player_is_on in [0, 2]: # не учитываем пол return description = " Ты видишь : " + objects[item_player_is_on] [2] for prop_number , details in props.items() : # реквизит = номер объекта : [номер помещения , у , х] if details[O] == current_room: #если реквизит # в помещении # Если реквизит скрыт (= в позиции игрока, # но не на карте) if (details[l] == player_y and details[2] == left_tile_of_item and room_map[details[l]] [details[2]] != prop_number) : add_object(prop_number) description = " Ты нашел " + objects[prop_numЬer] [3] sounds.combine.play() show_text(description, 0) time.sleep(0.5) ##########Н### ## С ГАРТ ## ############### --пгоп;у L'к-- Листинг 9-1 О. Добавляем код, позволяющий изучать предметы Код в листинге 9-10 использует различные функции, с которыми мы работали в этой главе. Мы начинаем с полу­ чения номера объекта, который хочет изучить игрок, и со­ храняем его в переменной В этот момент в функции item _ player _ is _ on game _ loop ()позиция О. игрока будет находиться на предмете, который он хочет осмотреть, или, возможно, внутри него, если это декорация. Мы РАСПАКОВКА ЛИЧНЫХ ВЕЩЕЙ 253
помещаем начальную позицию элемента по оси х в перемен­ ную left _ tile _ of _ item 6 . Если в 11лесте, где нахо­ дится игрок, нет объекта для изучения , функция завершает­ ся без каких-либо дальнейших действий €). Будет разумнее игнорировать пустое пространство, чем описывать пустой пол, особенно если ты попросту ошибся или ошиблась, на­ жав не ту клавишу управления. Если в месте, где располага­ ется игрок, все же есть предмет, то описание объекта, взятое из подробного описания из словаря переменной objects, передается description О . Затем программа проверяет объект, который исследует игрок, на наличие предмета, скрытого внутри. Для перебо­ ра всех элементов в словаре props 0 мы используем цикл. Если предмет находится в текущем помещении в месте по­ ложения игрока, но карта помещения в этой позиции не со­ держит номер реквизита 0, это означает, что предмет скрыт. Поэтому мы добавляем скрытый предмет в инвентарь игро­ ка fJ и отправляем игроку сообщение, в котором сообщает­ ся, что он что-то нашел. В этом сообщении приводится краткое описание предмета, чтобы игрок все-таки смог по­ нять, что же ему попалось @ . После завершения работы функции отображается описа­ ние предмета 0 . Здесь установлена короткая пауза, чтобы описание не было мгновенно перезаписано в том случае, если игрок будет удерживать клавишу нажатой ®. Если при разработке своей версии игры ты захочешь спрятать реквизит внутри декораций, обязательно дай игро­ кам толковую подсказку о том, где находится спрятанный предмет. В игре «Побег» ты будешь находить предметы в шкафах. Если ты заметишь что-то необычное, советую тебе изучить этот объект повнимательнее, кто знает, что ты еще сможешь там найти ... Но не нужно обыскивать каждый стул, кровать или стеновую панель! Если ты решишь скрыть реквизит в широких декорациях (напри:v~ер, в кровати), убедись, что он спрятан в том месте, где по оси х находится местоположение декорации, а не в пространстве, которое на карте помещения обозначе­ но числом 254 rЛАВА9 255.
YЧE&HASll МИССИSI № 2 А сможешь ли ты найти МРЗ-плеер? Он находится в спальне того из астронавтов, которого в главе 4 ты назвал FRIEND2. Если ты использовал мой код, то плеер должен быть в спальне Лео. Теперь, когда все вещи разложены по своим местам, ты наконец-то можешь расслабиться и п оиграть с йо-йо, а так­ же осмотреться вокруг и попробовать найти что-нибудь еще . В следую щ ей главе мы добавим в про г рамму но вый раздел, код ко т орого позволит нам ис п ользовать найденн ы е п р едметы. rотов ли ты к ПОЛЕТУ? Отме т ь следующие пунк ты , если чувс т вуе ш ь, ч то ма т е р иал этой главы тебе п оня т ен. D Информация о расположении реквизита хранится в слова р е props. D Н омер р еквизита - это ключ словаря, и каждая запись содержит с п исок с номером помещения и координатами реквизита по осям у их. D Ч тобы получить число из ф ункции, создай переменную, в кото р ой будет храниться п рисла н ная и нф орма ц ия. Например, имя_ переменной = D воспользуйся инст р укцией D function _ name () . Ч тобы верну ть число (и ли что-нибудь д р угое) из ф у н кци и , О перат о р // ret urn . используется для деления чисел и округляет результат в мен ьш ую сторону, удаляя все деся т ич н ые знаки. D Оп ератор % вычисляет ос т а т ок от деления двух чисел. D Значения переменных и списков можно менять, чтобы Н априме р , 5 % 2 р авно 1. у п рости т ь т ес т ирование п ро г раммы, на пр имер, можно заполнить инвентарь всеми п р едме т ами уже в самом начале иг р ы. Главное не забыть потом вернуть все ! D Реквизит можно прятать внутри предметов обстановки, только сначала следует убеди т ься, что он находится в том РАСПАКОВКА ЛИЧНЫХ ВЕЩЕЙ 255
л1есте, где начинается декорация, а кроме того, игроки дол­ жны хотя бы примерно понимать, где искать спрятанные предметы. ОБСУЖДЕНИЕ МИССИИ Ниже представлены ответы на учебные миссии из этой главы. УЧЕБНАЯ МИССИЯ № 1 Баллон находите.я в помещении, откуда стартует игрок (31). Лазарет находите.я в помещении 41. Выйди из комнаты, в которой ты находишься в самом начале игры, и иди так: направо, вниз до конца, налево и вверх. УЧЕБНАЯ МИССИЯ № 2 Он лежит в шкасру в помещении и пройди вниз по карте. 47. Выйди из начальной комнаты (31)
ДЕЙСТВИЯ С ПРЕДМЕТАМИ Итак, теперь в игре есть реквизит, и логично, что нашим следующим шагом станет написание кода, кото­ рый позволяет использовать найденные объекты и комбинировать их, чтобы созда­ вать новые предметы. Без этих навыков мис­ сию тебе не пройти. У тебя будет возмож­ ность отточить их так, что никакая ситуация не застанет тебя врасплох. Код в этой главе будет проще, чем в тех листингах , с ко­ торыми мы работали ранее. Кро1\1е того, в нем содержатся ответы на многие головоломки игры « Побег ». Так что я по­ стараюсь не раскрывать все секреты и не буду объяснять каждый шаг и каждое решение. Например, ты можешь встретить в коде номер предмета, но что это такое, я не скажу. ДЕЙСТВИll С ПРЕДМЕТАМИ 257
Если ты застрянешь где-нибудь в игре, ты можешь обра­ титься к этоJ\1у коду и воспользоваться им как подсказкой, чтобы понять, чем являются те или иные объекты: для этого тебе понадобится словарь в главе 5). objects (см. листинги 5-6 и 5-8 Однако я рекомендую пользоваться этим методом лишь в самых крайних случаях. Просто представь себя на месте астронавта, и ты наверняка сможешь разгадать все загадки. Спроси себя: к чему у тебя есть доступ и что здесь может оказаться полезным? Или, может быть, ты каким-то образом можешь улучшить имеющиеся предметы? ДЕЙСТВИJI С ПРЕДМЕТАМИ С ПОМОЩЬЮ КЛАВИАТУРЫ Мы начнем с добавления кода, отвечающего за управление с клавиатуры, в функцию listing9-10.py, game _ loop (). Открой файл последний листинг в главе 9. Именно он ста­ нет основой для нашего нового кода. В листинге 10-1 приведены новые инструкции, которые тебе нужно добавить в функцию game _ loop (). Помести их после кода, отвечающего за управление с клавиатуры действиями выкладывания и изучения предметов, которые ты добавил(а) в предыдущеi:'r главе. Эти инструкции запу­ скают функцию use _ object ()в нажимает клавишу listingl0-1.py. U. том случае, если игрок Сохрани программу в файл с именем Пока нет смысла ее запускать, так как ничего нового не произойдет. Если ты нажмешь клавишу грамма перестанет работать. пpt)r,yc к- if keyboa~d.space: examine_object() if keyboard.u: use_ob ject() --пропуск-- Листинг 10-1. Добавление кода, отвечающего за действия с объектами 258 rЛАВА 10 U, про­
ДО&АВЛЯЕМ СТАНДАРТНЫЕ СОО&ЩЕНИЯ ПРИ ИСПОЛЬЗОВАНИИ О&'ЬЕКТОВ Функция, которая отвечает за использование объектов, довольно длинная, поэтому я посвятил ей в программе отдельный раздел. Создай раздел ВЗАИМОДЕЙСТВИЕ С ОБЪ­ ЕКТАМИ после раздела ВЗАИМОДЕЙСТВИЕ С добавленного в главе 9. В листинге 10-2 РЕКВИЗИТОМ, указано начало этого нового раздела. Введи приведенный ниже код после функции examine _ object (),но до начала раздела СТАРТ. --'1pOПYL'K-- show text(description, time.sleep(0.5) 0) #################################### ## ВЗАИМОДЕЙСТВИЕ С ОБЪЕКТАМИ ## #################################### def use_object (): global room_map , props, item_carrying, air, selected_item, energy global in_my_pockets, suit_stitched, air_fixed, game_over О use_message = " Ты попытался не 6 что-то сделать , но ничего вышло ." standard_responses = { 4: " Воздух на исходе ! Поторопись! " , 6: " Не время сидеть сложа руки !" , 7: " Не время сидеть сложа руки !" , 32: " Все затряслось и загрохотало , но ничего \ не произошло . " , 34: " Ага! Так лучше . А теперь вымой руки ." , 35: " Ты моешь руки и стряхиваешь воду. " , 37: " Из пробирок идет дымок, когда ты их трясешь 54: " Ты жуешь жвачку . Она липкая , как клей .", 55: " Йо-йо подпрыгивает вверх и вниз, немного \ медленнее , 56: " Это с 59: Земле " , Можешь ли ты соединить ее \ чем-нибудь? " , " Прежде чем использовать емкость, необходимо \ течь '' , " Ты подаешь " Не выбрасывай сигнал добавь светом , ничего. " Чтобы насладиться просто 75: на слишком неудобно . устранить 61: 62 : 67: чем ." , А но вдруг вкусной никто его не пригодится ... " космической видит ." , , едой , \ воды !" , " Ты здесь : " + str (current_room) + " // Х : " \ + str (player_x) + " // У: " + str (player_y) ДЕЙСТВИSI С ПРЕДМЕТАМИ 259
# Получение номера объекта в месте нахождения игрока item_player_is_on = get_item_under_player() for this_i tem in [item_p l ayer_is_on, i tem_ carry ing ] : if this_item in standard_responses: use_message = standard_responses[this_item] ~ О 0 @ б . show_ text(use_message, 0) time .sleep(0.5) ############### СТА.Н ## ## ##Н#Н######## - проr,уск Листинг 10-2. Добавление первых инструкций по использованию объектов В листинге object ().Мы 10-2 приведена первая часть функции use _ разберем ее более подробно при помощи других листингов, приведенных в этой главе. В конце функ­ ции программа выводит сообщение о том, что произошло, когда игрок попытался использовать объект ние будет находиться в переменной &. Это сообще­ use _ message. В нача­ ле этой функции мы записываем в нее соответствующий текст О . Позднее в ней будет храниться сообщение об успешном выполнении действия, если игроку удастся ис­ пользовать тот или иной предмет. Некоторые предметы в игре не имеют какого-либо пред­ назначения, но если игрок попытается их использовать, программа вознаградит его за это сообщением. Эти сооб­ щения могут содержать как подсказки, так и просто допол­ нять сюжет игры. В словаре standard _ responses содер­ жатся сообщения, которые может увидеть игрок при использовании определенных объектов 6. Ключом словаря является номер объекта. Например, если игрок захочет сесть на стул (лентяй!), то есть повзаимодействует с объек­ том под номером 6, он увидит сообщение, в ко т ором гово­ рится: «Не время сидеть сложа руки!». В переменной item _ the _ player _ is _ on хранится номер объекта, который находится в позиции игрока в по­ мещении €) _ Игроки могут использовать как те предметы, которые они несут, так и те, на которых они стоят. Далее мы создаем цикл, который перебирает список из двух элемен­ тов: номер объекта, на котором стоит астронавт, и ноI>.1ер 260 rЛАВА 10
объекта, который астронавт несет в руках О . Если один из них является ключом для словаря standard _ responses 0 , то значение переменной use _ message ме­ няется на сообщение из этого словаря 0 , которое относится к этому объекту. В случае, если и объект, который несет иг­ рок, и объект, на котором стоит игрок, имеют стандартные сообщения, программа отдаст приоритет первому из них. Сохрани свой файл под именем с помощью команды listingJ0-2.py. pgzrun listingl0-2 .ру. верки работы программы нажми клавишу U, Запусти его Для про­ чтобы вос­ пользоваться йо-йо, которое ты держишь. ДО&АВЛllЕМ ПЕРЕМЕННЫЕ, ОТВЕ'IАЮЩИЕ ЗА ПРОf РЕСС иrРЫ Расскажу о некоторых новых переменных, которые нам необходимо будет добавить в программу. В них будут хра­ ниться важные данные о прогрессе игрока в игре. • Air хранит значение (в процентах), которое показывает, сколько воздуха у тебя осталось в запасе. • energy хранит значение (в процентах), отражающее здоро­ вье игрока. Если игрок получает травму, оно у1\1еньшается. • suit _ stitched хранит значение true или false в зави­ симости от того, починил ли игрок свой костюм. • air _ fixed хранит значение true или false в зависимо­ сти от того, был ли найден и установлен кислородный баллон. Добавь эти переменные в конец раздела ПЕРЕМЕННЫЕ так, как показано в листинге грамму в файл 10-3. listingl0 -3.py. Сохрани обновленную про­ Пока запуск программы не вы­ ведет ничего нового, поскольку мы всего лишь добавили пе­ ременные, но еще никак их не обрабатываем. --пpor1ycк-- GREEN = (0, RED = (128, 255, 0) О, 0) air, = e nergy 100, 100 ДЕЙСТВИll С ПРЕДМЕТАМИ 261
suit_stitched, air fixed launch f rame = О False , False # # # # # # ##### ## # # ## i<A.'TA ## ############### --пг туск-- Листинг 10-3. Добавление переменных, отвечающих за прогресс в игре ЗАДАЕМ ДЕЙСТВИЯ РАЗЛИ'IНЫМ О&'ЬЕКТАМ Следующим этапо:м работы функции use _ object () явля­ ется проверка конкретных объектов на наличие действий, которые можно над ни1\1и совершать. Благодаря этим про­ веркам все стандартные сообщения, которые мы задали ранее и указали в листинге 10-4, будут проигнорированы. Так как эти инструкции находятся внутри функции use _ object (), все они имеют отступ в четыре и более пробелов. Сохрани программу в файл с именем ее с помощью команды listingl0-4.py. Запусти pgzrun listingl0-4 .ру. --прnпуск-- i f th1s item in standard_responses: use message standard_responses[this item] О if item_carrying == 70 or item_player_is_on == 70: use_message = " Играет музыка !" sounds.steelmusic.play(2) б elif item_player_is_on == 11: use_message = " ВОЗДУХ : " + str (air) + \ " % / ЭНЕРГИЯ " + str (energy) + " % / " if not suit stitched: use_message += "! ! ! СКАФАНДР ПОРВАН !! ! / " if not air fixed: use_message += "! ! ! НЕТ БАЛЛОНА С ВОЗДУХОМ !!!" if suit stitched and air fixed: use_message += " СКАФАНДР В ПОРЯДКЕ " show_text(use_message, 0) sounds.say_status_report.play() time.sleep(0.5) # Если компьютер " включен ", игрок явно намерен # обновить статус. # Команда return, чтобы прекратить использование # объекта , который случайно переопределил этот . return €) о 262 rЛАВА 10
0 elif itern_carrying == 60 or itern_player_is_on == 60 : use_rnessage = " Ты вставил " + objects [ 60] [ 3] + \ " в скафандр " air_f ixed = True air = 90 air_ countdown () rernove_object(60) elif (itern_carrying == 58 or itern_player_is_on == 58) \ and not suit stitched : use_rnessage = " Ты использовал " + objects [56] [ 3] + \ " для ремонта ткани скафандра " suit_ stitched = True rernove_object(58) elif itern_carrying == 72 or itern_player_is_on == 72 : use_rnessage = " Ты запросил помощь по радио . \ Спасательная шлюпка прибывает . \ Отправляйся в сектор 13 , снаружи ." props [40] [0] = 13 elif (itern_carrying == 66 or itern_player_is_on 66) and current roorn in outdoor roorns: use_rnessage = " Ты ищешь ... " if (current_roorn LANDER_SECTOR and p l ayer_ x == LANDER_ X and player_y == LANDER_Y) add_object(71) use_rnessage = " Ты нашел посадочный модуль !" elif itern_player_is_on == 40 : clock.unschedule(air_countdown) show_text ( " Поздравляем тебя , " + show_text( " Mиccия Ты в пройдена PLAYER_NAМE успешно! +" !" , \ 0) \ 1) безопасности. " , garne_over = True sounds . take_off . play() garne_cornpletion_sequence() elif itern_ player_ is _ on == 16 : energy += 1 if energy > 100 : energy = 100 use_rnessage = " Ты жуешь петрушку немного и восстанавливаешь\ энергии " draw_ energy_ air() elif itern_carrying == 68 or itern_player_is_ on == 68 : energy = 1 00 use_rnessage = " Ты ешь , чтобы восстановить энергию " rernove_object(68) draw_energy_air() ДЕЙСТВИll С ПРЕДМЕТАМИ 263
if suit_stitched and air_fixed: # доступ в шлюз if current_room == 31 and props[20] [0] == 31: open_door(20) # включая удаление двери sounds.say_airlock_open.play() shоw_tехt( " Компьютер сообщае т, что elif props[20] [О] == 31: props[20] [0] =О # удаление двери sounds.say_ airlock_open.play() shоw_tехt( " Компьютер show_text(use_message, time.sleep(0.5) сообщает , шлюз с что открыт ." , 1) карты шлюз открыт ." , 1) О) ############### ## СТАРТ ## ############### - пропу<..к-Листин г 10-4. Н астраиваем использование определенных объектов В листинге 10-4 содержится ряд инструкций, которые проверяют, имеет ли используемый объект определенный номер. Если это так, то будут выполнены инструкции, предназначенные для этого объекта. Например, если игрок несет предмет 70 (МРЗ-плеер) или стоит на нем О , будет выведено сообщение «Играет музы­ ка!», и зазвучит музыка. Если игрок использует компью- тер 6, сообщение будет п редставлять собой результат объ­ единения информации, полученной из переменных и energy, air и предупреждения о том, что костюм или кисло ­ родный баллон повреждены. В этом случае тоже воспроиз­ водится звуковой эффект «Отчет системы». В конце данного набора инструкций О я добавил опера­ тор ret urn. С его помощью мы 1\южем предотвратить си­ туацию, когда игрок, намереваясь поработать с компьюте ­ ро11л, случайно воспользуется другим объектом. Если не использовать инструкцию ret urn, игрок вместо ком­ пьютера мог бы использовать другой реквизит, который хранится в его инвентаре. П р ограмме не всегда понятно, чем игрок хочет воспользоваться в тот или иной момент, од­ нако игра настроена таким образом, чтобы уде 11ять перво­ степенное внимание тем действиям, которые помогут игро­ ку в его миссии. 264 rЛАВА 10
В нескольких местах я не стал добавлять в строку 0 на­ звание пред!\1ета, а воспользовался его кратким описанием из словаря objects. Это сделано для того, чтобы ты не уви­ дел спойлеры в коде! Символ \ в ~юнце строки €) указывает Pytl:юn на то, что код продолжается и на следующей строке. Некоторые стро ­ ки получились довольно ДЛИННЫJ\1И, поэтому я разбил ИХ, чтобы они уместились на странице книги. Протестируй работу новых инструкций. Для этого по­ дойди к одному из компьютерных терJ\1ию1лов и нажми кла­ вишу U. На экране появится сообщение. Если ты сможешь найти МР3-плеер, то сможешь послушать на нем музыку. ОД:J.101 Будь как можно внимателы-tее при вводе кода из листин­ га 10-4) особенно это касается номеров объектов. Если ты допустиutь здесь оиtибку, то, возможно, тебе не удастся реиtюпь головоломки в игре! КОМ&ИНИРОВАНИЕ О&'ЬЕКТОВ Для решения некоторых загадок в игре тебе потребуется использовать сразу несколько предметов вместе. Например, один объект можно использовать в качестве инструмента, с помощью которого ты совершишь действие над другим объектом, или же ты просто можешь соединить вместе какие-либо два объекта. Например, для решения одной из головоломок понадобится вставить GРS-модуль в нера­ ботающую навигационную систему. Когда ты найдешь оба предмета, тебе нужно будет объединить их, чтобы получи­ лась работающая навигационная система. Для совместного использования двух объектов выбери один из тех, что нахо­ дится в твоем инвентаре и встань на другой объект или подойди к нему. Возможно, тебе придется положить на пол предмет из инвентаря, чтобы ты смог воздействовать на него другим предметом, который держишь в руках. В движке игры «Побег» подобные комбинации носят на­ звание рецеппzов. Внутри каждого рецепта находится спи­ сок из трех номеров объектов. Первые два - это ДЕЙСТВИll С ПРЕДМЕТАМИ 265
объединяемые предметы, а третий - это ноl\1ер предмета, который получается после их объединения. Например: [73, 74, 75] Предмет 73 (GРS-модуль), вставленный в предмет 74 (не­ работающая навигационная система), составляют вместе предмет 75 (рабочую навигационную систему). Результат объединения объектов, новый предмет, окажется в твоем инвентаре. Предметы из числа реквизита, использо­ ванные при объединении, удаляются из игры. Однако если один из них является декорацией, то он останется на месте. В листинге 10-5 приведен список рецептов. Помести его в конец раздела РЕКВИЗИТ программы. Сохрани свой файл под именем listingJ0-5.py. После запуска программы ничего нового не произойдет, но мы хотя бы проверим, верно ли введен код. --nf:/;rrycк-­ in_my pockets selected_itcm item_carrying RECIPES [62, [59, [88, = [ 35, 63], 54, 60], 58, 89], [55] # п~~вы~ эг~ме~т in_my_pockets[selected_item] О [76, 28, 77], [77, 55, 56], [89, 60, 90]' [78, 38, 54]' [56, 57, 58], [67, 35, 68] [73, 74, 75]' [71, 65, 72]' checksum = О check counter = 1 for recipe in RECIPES: checksum += (recipe[O] * check_counter + recipe[l] * (check_counte r + 1) + recipe[2] * (check_counter + 2)) check counter += 3 print ( len (RECIPES), "( количество рецептов ) " ) assert len (RECIPES) == 11, "Ожидается 11 рецептов " assert checksum == 37296, " Ошибка в данных рецептов " рrint ( " Контрольная сумма рецептов : ~ , checksum) ################################# ## В'~АИМОДЕ.11С'IЬЛF С Р" t<ЬУ1ЗИГОМ ## ################################# --пг'lпуск-- Л и стинг 10-5. Добав л яем ре ц еп т ы в игру «Побег» 266 rЛАВА 10
Теперь нам нужно добавить код, с помощью которого J\1Ы будем пользоваться рецептами. Вставь его в конец функции use _ object (),как показано в листинге 10-6, и сохрани программу в файл listinglO-б.py. Запусти программу с помо­ щью команды pgzrun listingl0-6.py. Теперь наш астро­ навт умеет комбинировать пред меты. - пропуск sounds.say_airlock_open.play() shоw_tехt("Компьютер О сообщает , что шлюз открыт .", 1) for recipe in RECIPES: ingredientl recipe[O] ingredient2 = recipe[l] combination = recipe[2] if (item_carrying == ingredientl and item_p layer_is_on == ingredient2) \ or (item_carrying == ingredient2 and item_playe r _ is _on == ingredientl) : use_message = " Ты соединяешь " \ + objects[ingredientl] [3 ] \ + " и " + objects [ingredient2] [3] \ + ", чтобы получилось : " \ + objects[combination] [3] if item_player_is_on in props.keys(): props[item_player_is_on] [О] =О room_map[player_y] [player_x] = get_floor_type() in_my_pockets.remove(item_carrying) add_object(combination) б ~ о 0 sounds.comЬine.play() show_text(use_message, 0) time.sleep(0.5) - проr!',ск- Листинг l 0-6. Объединение предметов в игре Возможно, ты уже понял(а), как работает этот код: боль­ шая часть приемов в нем тебе должна быть з11а!{ома . Мы пе­ ребираем в цикле все элементы, которые находятся в списке RECIPES О, и при каждом прохождении добавляем в список рецептов новый предмет. Номера «инг ред11ентов» и номера комбинаций мы помещаем в переменные. Это упрощает чтение кода функций 6. Программа проверяет, несет ли игрок первый «ингреди­ ент» и стоит ли на втором €), или наоборот О. Если это так, ДЕЙСТВИll С ПРЕДМЕТАМИ 267
то на экране появится сообщение, в котором говорится о том, какие игрок объединил предметы и что из этого по­ лучилось 0. Когда мы создаем комбинированный предмет, он обычно заменяет предметы-«ингредиенты». Однако если один из объектов является декорацией, а не реквизитшл, то он останется в игре. Таки:r·л образом, программа проверяет, яв­ ляется ли предмет, на котором находится игрок, реквизи­ том 0, и, если да, то предмет переносится в помещение О, то есть удаляется из игры &. Кроме того, он удаляется и с карты текущего помещения 0. Предмет, который игрок нес, удаляется из инвентаря а только что созданный предмет туда добавляется УЧЕ&НАЯ МИССИЯ 0, ®. No 1 Давай сделаем простой тест для проверки кода рецепта. Для этого нам придется слегка подправить код. В разделе РЕКВИЗИТ строку, которая отвечает за значение переменной in _ my _ pockets, измени так, чтобы персонаж нес объек­ ты 73 и 74: in_my_pockets = [55, 73, 74] Теперь запусти программу: в твоем инвентаре окажутся GРS-модуль и неработающая навигационная система. Выложи один из предметов и встань на него. Выбери другой предмет из своего инвентаря и нажми клавишу U. Предметы должны объединиться в рабочую навигационную систему! Ты можешь использовать ее для определения своего местоположения в игре. Чтобы убедиться, что код действительно работает, по­ пробуй выложить другой предмет и соединить его с первым. Не забудь после эксперимента вернуть коду первоначаль­ ный вид: in_my_pockets 268 rЛАВА 10 [55]
ЗАДАЕМ ФИНАЛЬНЫЙ эпизод иrРЫ Вр азделе ВЗАИМОДЕЙСТВИЕ С ОБЪЕКТАМИ есть еще одна функция, она отвечает за короткую анимацию, которая вос­ производится в финале игры: астронавт взлетает на спаса­ тельной шлюпке. Помести приведенную ниже функцию в конец раздела ВЗАИМОДЕЙСТВИЕ С ОБЪЕКТАМИ, как пока ­ зано в листинге - 10-7: show_text(use_message, time.sleep(0.5) пропуск 0) def game_completion_sequence (): global launch_frame # ( начальное значение О , # в раз~еле ПЕРЕМЕННЫЕ ) Ьох = Rect ( (0, 150), (800, 600)) screen.draw.filled_rect(box, (128, О, 0)) Ьох = Rect ( (0, top_left_y - 30), (800, 390)) screen.surface.set_clip(box) for in range (O, 13): for х in range (O, 13): draw_image(images.soil, настраивается у у, х) launch frame += 1 if launch_frame < 9: draw_image(images.rescue_ship, 8 - launch_frame, draw_shadow(images.rescue_ship_shadow, 8 \ + launch_ frame, 6) clock.schedule(game_completion_sequence, 0.25) else : screen.surface.set_clip( None ) screen.draw.text( " MИCCИЯ " , (200, 380), color = "white " , fontsize = 89, shadow = (1, 1), scolor = " Ьlack " ) screen.draw.text( " ЗABEPШEHA " , (145, 480), color = " white " , fontsize = 89, shadow = (1, 1), scolor = " Ыасk " ) sounds.completion.play() sounds.say_mission_complete.play() 6) ############### ## С ГАРТ ## ############### --пропуск-- Листинг l 0-7. Пол е тели! ДЕЙСТВИll С ПРЕДМЕТАМИ 269
ИССЯЕДУЕМ О&'ЬЕКТЫ Итак, теперь наш астронавт умеет многое: исследовать объ­ екты, которые находятся на станции, и использовать их, чтобы понять, зачем нужен тот или иной предмет. Но прежде чем ты найдешь весь реквизит и приступишь к работе на космической станции, тебе придется решить загадку, как открыть заблоки­ рованные двери, преграждающие путь в некоторые помещения космической станции. В следующей главе мы завершим настройку космической станции, добавив код, который будет открывать двери при помощи карт доступа. Кроме того, теперь ты и сам(а) можешь добавлять в игру головоломки, ты уже знаешь все, что для этого нужно. Са­ мый простой способ ния (листинг стинг 10-2) - использовать стандартные сообще­ для вывода подсказок, и рецепты (ли­ для объединения предметов. Кроме того, ты 10-5) J\южешь добавить несколько простых инструкций (ли­ стинг 10-4), чтобы узнать, несет ли игрок тот или иной предмет, увеличить или уменьшить значение переменной air или energy, вывести какое-нибудь сообщение, или сделать что-нибудь еще. Счастливого пути! rотов яи ты к nояЕТУ? Отметь следующие пункты, если чувствуешь, что материал этой главы тебе понятен. D Инструкции по использованию объектов содержатся в функции use _ object (). D В словаре standard _ responses содержатся сообщения, которые выводятся, когда игрок использует определенный объект. D Для многих объектов существуют специальные инструкции по обновлению различных списков или переменных на слу­ чай использования игроком этих объектов. D В списке RECIPES хранятся подробные сведения о том, как именно игрок может к011лбинировать объекты. D Первые два пункта рецепта пункт - - это «ингредиенты», а третий результат их ко11лбинирования.
&ЛОКИРОВКА ДВЕРЕЙ В ПОМЕЩЕНИЯ На нашей космической станции доступ в некоторые зоны ограни­ чен специальными дверьми, которые гарантируют, что астронавты смогут попасть за них только в том случае, если у них есть соответствующий доступ. Неко­ торые двери требуют личные пропуска, а попасть в инженерный отсек можно при помощи нажатия кнопки, расположенной в центре управления полетами. Помимо этого двери инженерного отсека оснащены таймером, по истечении которого они будут автоматически закрыты, тем самым повышая уровень безопасности. &ЛОКИРОВКА ДВЕРЕЙ В ПОМЕЩЕНИll 271
Благодаря наличию дверей также обеспечивается со­ блюдение правил техники безопасности. Согласно этим требованияl\1 астронавты должны иметь исправный ска­ фандр, чтобы войти в шлюз, и привести с собой товарища, чтобы открыть дверь, ведущую на поверхность планеты. Кадры с камеры видеонаблюдения свидетельствуют о том, что некоторые астронавты нашли способ обойти требова­ ние о присутствии с ними компаньона, чтобы наслаждать­ ся безмятежностью одинокой прогулки по поверхности планеты. Как ты помни ш ь, мы установили дверь на космической станции еще в процессе создания реквизита. В этой главе мы добавим код, который позволит открывать и закрывать двери, а также придумаем несколько новых трюков и голо­ воломок, чтобы игра стала еще интереснее. ВЫ&ИРАЕМ МЕСТА РАЗМЕЩЕНИЯ ЗАЩИТНЫХ ДВЕРЕЙ Безусловно, двери являются жизненно важной частью дизайна космической станции, но они также важны и для дизайна игры. Также очевидно, что каждая дверь представ­ ляет собой сложную головоломку: игрокам нужно найти способ открыть запертые двери. Двери также помогают нам рассказать игроку сюжетную историю, в которой герою п ридется преодолевать препят­ ствия, используя свои навыки выживания и логическое мышление. Загадки, представленные в игре, могут понра­ виться игроку только в том случае, если ему придется поло­ мать над ними голову. Вот почему важно контролировать моменты, в которых игроки видят различные элементы го­ ловоломки. Представь, что ты входишь в помещение, а вы­ ход преграждает пылающий огонь. Если у тебя есть огнету ­ шитель, ты просто им воспользуешься. Здесь нет ничего сложного. Но ситуация становится куда интереснее, если, увидев угрозу (или загадку), тебе приходится поразмыслить о том, как ее преодолеть. Блокируя некоторые поJ\.1ещения станции, мы обращаем внимание игроков на проблему, что­ бы он и начали думать над ее решением. Конечно, мы 272 rЛАВА 11
не можем заранее знать, что и гроки з а метят все оставленные на их пути предметы, но мы можем дать им возможность по­ чувствовать игру во всех ее проявлениях. Благодаря дверям карта становится для нас еще более по­ лезным инструментом. Хотя в случае на ш ей игры это не так очевидно, карта станции не очень большая. Однако мы мо ­ жем обеспечить нашим игрокам более богатый игровый опыт и большую продолжительность игры, если вынудим их проходить через наиболее сложные помещения больше одного раза. Например, если мы поместим ключ в конце ко ­ ридора, то можно заставить игрока повторить путь по кори­ дору, чтобы добраться до ключа и использовать его в двери, мимо которой игрок здесь прошел. Н а рисунке 11.1 показано рас - положение дверей в игре. Не буду 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 пока не отправятся в верхнюю 36 37 38 39 40 правую секцию космической 41 42 43 44 45 46 47 48 49 50 говорить слишком много, но от­ мечу, что игроки не смогут по­ пасть в птлещение 36 до тех пор, станции (через помещение 34). Кроме того, они не смогут до­ браться до помещения 27, пока 40. Если не попадут в по м ещение мы будем стратегически верно раз­ Р ис. 11 .1. Карта с д верьми, отмечен н ыми крас н ым цве т ом мещать предметы, включая карты доступа, в запертых помещениях, то сможем направлять игрока на протяжении всей игры. Разрабатывая свою игру, тщательно выбирай места, в ко­ торых будет находиться реквизит. Это один из наиболее важных аспектов, если r-.1ы хотим, чтобы игроки получили удовольствие от решения наших головоломок. РАЗМЕЩАЕМ ДВЕРИ Все д вери в игре «Побег » расположены у верхнего или у нижнего выходов из комнаты, потому что в игре исполь­ зуется перспектива «сверху вниз». Если бы дверь находилась сбоку, игроки видели бы толыю ее верхнюю поверхность, &ЛОКИРОВКА ДВЕРЕЙ В ПОМЕЩЕНИll 273
поэтому мы должны точно знать, что такая важная деталь, как дверь, хорошо видна игрокам. Большинство дверей находятся в верхней части помеще­ ния и после того, как игрок открывает их, они не закрыва­ ются. Исключением является дверь между помещениями и 27, 32 на которой стоит таймер , закрывающий ее автоматиче­ ски. Этот таЙr·лер заставляет игрока слегка попотеть: ему придется поторопиться, чтобы попасть в помещение до того, как дверь закроется. Начинать же свой путь он бу­ дет от кнопки, которая открывает дверь. Двери в игре « Побег » - это объекты с номерами Их изображения и описания размещены в словаре 20-26. objects (см. « Словарь объектов космической станции» в главе Расположение дверей задается в словаре ление кода для реквизита » в главе 9). props 5). (см. « Добав­ Каждая дверь имеет свое r-.1есто на оси х, благодаря чеl\·1у мы можем поместить дверь именно в дверной проем помещения. Чтобы опреде­ лить положение двери по оси х, раздели ширину помеще ­ ния на чти 2, округли результат в 1\Iеньшую сторону, а затем вы­ 1. А теперь давай сделаем так, чтобы игроки могли откры­ вать двери. НАСТРАИВАЕМ ДОСТУП В ПОМЕЩЕНИЯ Чтобы игрок мог открывать двери, нам нужно добавить несколько новых инструкци1"::'1 в функцию use _ object ()в разделе В ЗАИ МОДЕ ЙСТ ВИ Е С ОБЪЕКТАМИ. Один из фраг­ ментов добавляемого нами кода позволит открыть дверь с таймером, ведущую в инженерный отсек. Для этого игрок должен нажать кнопку, размещенную в одном из помеще­ ний. Этот код нужно добавить между инструкциями, кото­ рые управляют объектами 16 и 68. Другой фрагмент нового кода позволит игроку исполь­ зовать карты доступа, чтобы с их помощью открывать две­ ри: размести его после кода, отвечающего за работу с ре­ цептами. В листинге 11-1 показан новый код, который необходимо добавить в программу. Поскольку эти инструкции 274 rЛАВА 11
относятся к функции use _ object (), перед первой из них следует добавить отступ в четыре пробела. Новая инструк­ ция ей elif elif, должна находиться на одном уровне с инструкци­ расположенной выше. Открой файл listingl0-7.py, с которым мы работали в пре­ дыдущей главе, и добавь в него новые строки кода. Сохрани программу в файл с именем listingJJ-1.py. стить ее с помощью команды Ты можешь запу­ pgzrun listingll-1.py, но по1\1ни, что наша работа по добавлению дверей в игру еще не закончена. Пока главное то, чтобы при запуске про­ граммы не появлялось никаких сообщен111':'1 об ошибках. --пропуск-- е1i f iteщplayer is оп - 16: energy += 1 if energy > 100: energy = 100 use_message = "Г~ жу~шь пе~рушку немного и вссс~анавлиR~ешь \ энергии" draw_energy_air() О б ~ О 0 elif item_player_is_on == 42: if current room == 27: open_door(26) props[25] [О] =О #Дверь из RМ32 в инженерный отсек props[26] [0] =О #Дверь внутри инженерного отсека clock.schedule_unique(shut_engineering_door, 60) use_message = " Ты нажимаешь кнопку " show_text( " Двepь в инженерный отсек открьuтась \ на 60 секунд " , 1) sounds.say_doors_open.play() sounds.doors.play() elif -· tem_ arrying 68 or item_p:ayer is_o~ ~= 68: energy = 100 use_message = "Ты ~u_;ь, чтоt'iы восстJновить 'J~."ргию" remove_object (68) draw energy a~r() --гrгоr1ус к-- for recipe in RECIPES: ingredientl recipe[O] ingredient2 = recipe[l] --пгипycк-- add_ob j ect ( comЬina t ion) sounds.comЬine.play() 0 # {номер объекта ключа : номер объекта двери} ACCESS_DICTIONARY {79:22, 80:23, 81:24) &ЛОКИРОВКА ДВЕРЕЙ В ПОМЕЩЕНИll 275
б if item_carrying in ACCESS_DICTIONARY: door_number = ACCESS_DICTIONARY[item_carrying] if props[door_numЬer] [О] == current_room: use_message = " Ты разблокировал дверь !" sounds.say_doors_open.play() sounds.doors.play() open_door(door_number) show text (ose .message, 0) time.sleep(0.5) --гн::опус к-- Л истинг 11-1. Теперь игроки смогут открывать двери Кнопка, позволяющая открыть дверь в инженерный от­ сек, - это объект 42. Одна из этих кнопок находится снару­ жи инженерного отсека, чтобы обеспечить туда досту п , а другая внутри него, чтобы и г рок не оказался заперт - в ловушке. Если игрок нажимает кнопку О , то запускается код, от­ крьшающий эту дверь. Если же игрок нажимает кнопку, рас­ положенную внутри помещения 6 , то для отображения open _ door () €). от­ крытой двери используется функция Вскоре мы вернемся к этой функции и поработаем с ней. Содержимое словаря props обновляется с той целью, чтобы сделать номер помещения для двери равным О, при этом дверь из текущего помещения удаляется (и из игры тоже) О . Эта дверь срабатывает по таймеру, поэтому через 60 секунд 0 программа запустит фуI-Iкцию закрытия двери. Если у тебя никак не получается добраться до помещения вовремя, ты r-.южешь изменить число 60 на любое другое, большее значение. Этого времени тебе должно хватить, не­ зависимо от т ого, играешь ты на ПК или на Raspberгy Pi 2/3, на которых игра выполняется немного медленнее. Второй фрагмент кода позволяет игрокам использовать карты доступа, чтобы открывать двери. Мы создали новый словарь, ACCESS _ DICTIONARY, в котором в качестве ключа используется номер карты доступа, а в качестве значения номер двери 0 . Так, для открытия двери 276 rЛАВА 11 объект 22. 79 - (карта доступа) используется
COBE'I' Все предметы, используемые для открытия дверей в игре «По­ бег», - это корты доступа, но если ты внесешь изменения в игру, то сможешь использовать для разблокировки дверей любой другой предмет. Например, ты можешь открывать двери ломом или же (если ты создаешь фэнтезийную игру) различны­ ми магическими заклинаниями. Главное, чтобы игроки понима­ ли, чем им нужно воспользоваться . Когда игрок нажимает клавишу U, дверь открывается, но только если в словаре выбран один из объектов, позво­ ляющих разблокировать дверь б , и если он находится в том же поr-.,1ещении, что и две рь, которую он планирует от­ крыть g. Кроме того, здесь воспроизводится звуковой эф­ фект со словами «две ри открыты!» 0. Он представляет со­ бой простую звукозапись, как и любой другой звук в игре. ОТКРЫВАЕМ И ЗАКРЫВАЕМ ДВЕРИ Помести функции, которые отвечают за открытие, закры ­ тие и анимацию дверей, в новый раздел программы ДВЕРИ. Он должен располагаться после раздела ВЗАИМОДЕЙСТВИЕ С ОБЪЕКТАМИ и до раздела СТАРТ, расположенного в конце. В листинге 11-2 показаны первые две функции, которые необходимо добавить, чтобы запустить раздел ДВЕРИ. Вставь новые строки и сохрани программу в фай л с именем listingl 1-2.py. Работа с разделом ДВЕРИ все еще не завершена: если запустить программу (командой pgzrun listingll-2 .ру), двери работать не будут, но зато можно проверить код на отсутствие ошибок. --пpoпycк-- sounds.completion.play() sounds.say_mission_complete.play() ############### ## ДВЕРИ ## ############### &ЛОКИРОВКА ДВЕРЕЙ В ПОМЕЩЕНИll 277
О def open_door (opening_door_numЬer): global door_frames, door_shadow_frames global door_frame_number, door_object_number door_frames = [images.doorl, images.door2, images.doorЗ, images.door4, images.floor] # (Последний кадр содержит тень, готовую к появлению двери) door_shadow_frames [images.doorl_shadow, images.door2_shadow, б images .doorЗ_shadow, images.door4_shadow, images.door_shadow] door_frame numЬer = О door_object_number = opening_door_number do_door_animation() €) О def close_door (closing_door_n umber) global door_frames, door_shadow_frames global door_frame_numЬer, door_object_number, player_y 0 door frames = [images.door4, images . doorЗ, images.door2, images.doorl, images.door] door_shadow_frames [images.door4_shadow, images.doorЗ_shadow, images.door2_shadow, images.doorl_shadow, images.door_shadow] door_frame_number = О door_object_number = closing_door_number # Если игрок находится в одном ряду с дверью, он должен \ # быть в открытом дверном проеме if player_y == props[door_object_number] [1]: if player_y == О: # если в верхнем дверном проеме player_y 1 # перемещаем вниз else : player_y = room_height - 2 # перемещаем вверх do_door_animation() Ф б Q 0 W'> ############### ## СТАРТ ## ############### --пгу1усу-- Листинг 11-2. Настройка анимации дверей Функции open _ door() и close _ door() отвечают за анимацию дверей, чтобы те могли открываться и закры­ ваться . Ты уже знаком(а) с функцие~'-i упоминалась в листинге 11-1 . В open _ door () О, она листинге 11 -2 мы определя­ ем эту функцию так, чтобы она работала, если, например, игрок открывает дверь картой доступа. 278 rЛАВА 11
Анимация двери состоит из пяти кадров, пронумерован­ ных от О до 11.1. Мы храним изо­ door _ frames 6 0 и передаем номер кадра переменной door _ frame _ number. В функциях open _ door () и close _ door () мы устанав­ 4, как показано в таблице бражения для этой анимации в списке ливаем номер кадра на О, что означает первый кадр анимации. В переменно1'::'1 door _ object _ number мы сохраняем ном ер объекта двери, которая будет открываться или закры­ ваться. После настройки этих переменных и списка запу­ скается функция do _ door _ animation (),которая вы­ полняет анимацию с учетом этих данных €) @.Вскоре мы добавим в программу эту функцию. Функция, которая закрывает дверь О , аналогична функции, позволяющей открыть дверь О. Однако пара отличий все же есть: их анимация выглядит по-разному, и код одной из них проверяет, чтобы дверь не захлопнулась прямо на игрока. Если игрок находится в той же позиции по оси у, что и дверь 0, это означает, что игрок стоит в дверном проеме. В таком случае, если игрок находится в верхней строке б, то мы устанавливаем его положение по оси у равным 1 9, чтобы переr.лестить его вниз на следующий ряд. Если игрок находится не в верхней строке, то мы устанавливаем его по­ зицию по оси у во второй строке снизу 0, сразу за дверью. Таким образом наш астронавт будто бы по собственной воле будет отскакивать от дверей в сторону, но в любом слу­ чае это СJ\ютрится реалистичнее, чем если бы он оказался внутри двери ! Таблица 11.1. Кадры анимации для дверей Номер о 2 з 4 кадра Открытие Последний кадр - это Закрытие &ЛОКИРОВКА ДВЕРЕЙ В ПОМЕЩЕНИll 279
АНИМАЦИll ДВЕРЕЙ За анимацию открытия и закрытия дверей будет отвечать функция do _ door _ animation (). По1\1ести ее в раздел ДВЕРИ после функции door (),которую мы добавили в листинге вые строки в листинг с именем 11-3 listingJJ-3.py. Добавь но­ и сохрани программу в файл Как и всегда, ты можешь запустить эту версию игры с помощью команды listingll-3.py. close _ 11-2. pgzrun Наконец-то двери, которые открываются картами доступа, начали работать. Вскоре я расскажу тебе, как протестировать их в учебно1':'1 миссии N~ player_y = room_height - 2 # do_door_animation() О перемещаем 1. вь~рх def do_door_animation (): global door_frames, door_frame_number, door_object_number, objects objects[door_object_number] [0] = door_frames[door_frame_numЬer] objects[door_object_number] [1] = door_shadow_frames[door_frame_number] door f rame_number += 1 if door_frame_number == 5: if door_frames[-1] == images.floor: props[door_object_numЬer] [0] =О # удаляем дверь из списка реквизита восстанавливаем б €) О 0 # карту помещения из реквизита , если необходимо # использовать дверь в помещение . generate_map() else : clock.schedule(do_door_animation, 0.15) @ б ~ ############### ## С ГАРТ ## #Н#Н###Н#### -- II опуск-- Лис тинг 11 -3. Создаем анимацию для дверей Словарь objects, помимо всего прочего, содержит изо­ бражение, которое используется для отображения конкрет­ ного объекта. Эта новая функция начинает свою работу с изменения изображения двери в словаре objects на теку­ щий кадр анимации О . После перерисовки помещения 280 rЛАВА 11
в нем будет использоваться и.менно этот анимационный кадр. Затем функция увеличивает ноJ\1ер кадра анимации на 16 , чтобы при следующем запуске функции можно было отобра­ зить следующш".'1 кадр анимации. Если номер кадра стал равен 5, это означает, что мы перебрали всю цепочку анимации @. Тогда мы проверяем, открылась ли дверь (а не закрылась), посмотрев, был ли последний кадр изображением напольной плитки, что означает отсутствие двери О. (Индекс - 1 возвращает послед­ ний элемент из списка.) Если дверь открыта полностью, данные реквизита будут обновлены: нOJ1..iep помещения двери становится равным О 0. Если текущий кадр аниJ1.1ации является последни\1, то неза­ висимо от того, открывается или закрывается дверь, созда­ ется новая карта помещения 0, благодаря чему мы можем быть уверены, что мы корректно добавили дверь в теку щ ую комна т у или удалили ее оттуда. Если текущий кадр не является последниl\1 кадром ани­ мации ды 8 , то функция вновь за п устится через 0,15 секун ­ Q , чтобы отобразить следующий кадр анимационной последовательности. Вероятно, ты обратил(а) внимание на то, что я не стал объединять эти две инструкции функция generate _ map () i f @ О . Дело в том, что должна выполняться в конце анимации, независимо от того, открываем мы дверь или за­ крьшаеJ\1. Если объединить эти два условия if, функция бу­ дет работать только в том случае, когда дверь открывается. YЧE&HAll МИССИll No 1 Итак, сейчас двери в нашей игре полностью функционируют. Кстати, это действительно так? Найди карту доступа к двери, рас ­ положенной в комнате отдыха экипажа, и воспользуйся ею. По­ дойди к двери комнаты отдыха и примени карту доступа, выбрав ее из своего инвентаря и нажав клавишу U. Если тебе нужна под­ сказка, обрати внимание на карту, изображенную на рис. 11.1 . Комната отдыха экипажа обозначена номером 39, а ключ от нее находится в лазарете (помещение 41 ). Помни, что люди иногда прячут свои вещи, поэтому ключ может быть не на виду. &ЛОКИРОВКА ДВЕРЕЙ В ПОМЕЩЕНИil 281
ЗАКРЫВАЕМ ДВЕРЬ, НА КОТОРОЙ УСТАНОВЛЕН ТАЙМЕР Нашей игре нужна еще одна функция. Она называется shut _ engineering _ door (),и ее назначение - автома­ тически закрывать дверь, ведущую в инженерный отсек. Эта функция срабатывает через (см. листинг 11-1), 60 секунд после открытия двери благодаря чему у игрока есть целая минута, чтобы после нажатия кнопки открытия двери добе­ жать до этой саJ1.юЙ двери, прежде чем она закроется! Помести эту функцию в раздел ДВЕРИ после только что добавленной функции do _ door _ animation ().Добавь новые строки в листинг 11-4 и сохрани программу в файл с именем listingl 1-4.py. Затем запусти программу с помощью команды pgzrun listingll-4 .ру. Никаких сообщений об ошибках быть не должно. Дверь с таймером уже должна работать , и вскоре я покажу тебе простой способ, как можно протестировать ее работу. --Гl[/)ГJуск else: clock.schedu~e(do_door_animation, 0.15) def shut_engineering_door (): global current_ room, door_room_numЬer, props О props [ 25] [0] = 32 #Дверь из помещения 32 в инженерный б props[26] [0] = 27 #Дверь внутри инженерного отсека . ~ generate_map() # Добавляем дверь в room_map, если она # находится в посещенной комнате . О if current room == 27 : 0 close_door(26) @ if current room == 32: б close_door(25) shоw_tехt( " Компьютер сообщает , что двери закрыты ." , 1) sounds . say_doors_closed.play() ##########Н### ## LГAf'T ## ## # # ##### # # # # # # - -прОПУL'К" -- Листинг 11-4. Добавление кода для автоматического закрытия двери, ведущей в инженерный отсек 282 rЛАВА 11 отсек .
Функция shut _ engineering _ door () с двумя реквизитами в виде дверей (объекты работает 25 и 26), по­ скольку игрок может видеть эту дверь с любо1':.'1 стороны в за­ висимости от того, в каком помещении он находится. Для начала мы обновим словарь props, чтобы эти двери 6. функцию generate _ map () появи­ лись в нужных комнатах О Затем мы вызываем t) . Если игрок находится в помещении, в котором расположена одна из указанных дверей, то данная функция обновляет карту текущего помещения. В остальных случаях функция generate _ map () продолжит выполняться, но уже неза ­ метно для тебя. Если игрок находится в инженерном отсеке (помеще- ние 27) О , он должен видеть, как закрывается дверь по­ 26 0 , этому программа запускает ани1'.1ацию. Если игрок находит ­ ся по другую сторону двери, в помещении увидеть, как закрывается дверь 32 0 , он должен 25 8. ij>Jфif.jj Не путай номера дверей с нол1ерами поме~цениii. Номера дверей - эпzо номера объектов, копzорые никак не связаны. с помеи~ением, в котором они находятся . Чтобы проверить, правильно ли работает дверь инженер­ ного отсека, нам нужно запустить игру, нажать кнопку и пулей помчаться в инженерный отсек. Поэтому, чтобы сэкономить время, предлагаю придумать сверхскоростной способ перемещения по космической станции. СОЗДАНИЕ ТЕЛЕПОРТА Во время строительства космической станции тебе может пригодиться возможность мгновенного перемещения между комнатами. Используя новейшие технологии моле­ кулярного перемещения, мы создадим телепорт, который позволит тебе попасть в любую комнату, просто указав ее номер. Таким образом тестировать игру становится значи­ тельно проще, хоть это и запрещенный приеrv1, который не одобрен для использования в реальной миссии &ЛОКИРОВКА ДВЕРЕЙ В ПОМЕЩЕНИil 283
на космической станции. Удали телепорт до того, как закон­ чи ш ь создание игры. Это очень секретная технология! Код телепорта нужно разместить рядом с другими эле­ ментами управления, в функцию game _ loop () в части ЦИКЛ ИГРЫ программы. Я рекомендую добавить этот фраг11лент после инструкций по запуску функции object (). use _ Поскольку эти инструкции находятся внутри функции, перед оператором if нужно сделать отступ в че­ тыре пробела, а инструкции, расположенные ниже, должны отступать от начала строки на восемь пробелов. Добавь новые строки в листин г файл под именем listing11-5.py. 11-5, а затем сохрани Как и всегда, ты можешь за­ пустить программу с помощью команды pgzrun listingll-5.py. if keyboard.u: use_object() #### #### О Раздел Удали телепорта этот для раздел тестирования из финальной if keyboard.x: current room = int ( input player_x = 2 player_y = 2 generate_map () start_room () sounds.teleport.play() б ~ О 0 ## - Конец раздела игры игры ( " Bвeди номер помещения :" )) телепорта прот1уск Лист и нг 11-5. Создаем телепорт Если нажать клавишу Х О , программа попросит ввести ншлер помещения 6. Э т от запрос появляется в окне ко­ мандной строки, где ты обычно вводишь инструкцию pgzrun для запуска программы. Возможно, тебе придется щелкнуть по этому окну, чтобы отобразить его поверх окна игры, а затем, чтобы продолжить играть, нужно будет клик­ ну т ь уже по игровому окну. Функция input ()считывает любые введенные тобой дан­ ные и помещает их в строку. Поскольку вводить мы будем 284 fЛАВА 11
число, то нам понадобится ф ункция для п р еобраз о ва­ int () ния р езультата в ц елое ч исло (то есть ч исло без остатка) 6. Введенное тобой значение пе р едается пе р еменной current _ room. Здесь нет п роверки на о т сутствие ошибок , поэ т ому п рог р амма .может выйт и из ст р оя, если ты введешь несуществующий номе р помещения. Кроме того, если ты вве ­ дешь какой - нибуд ь текст вместо числа, п рограмма зависнет. П осле теле п о рт ации на ш ас т ро н ав т окажется в точке с коорди н атами у = 2, х = 2 @ в ыб р а нн ого тобой п омеще­ ния. В большинстве помещений это место свободно, но если вдруг теле п орт поместит тебя в какую - нибудь деко ­ ра ци ю, то тебе ле г ко удастся выско ч ить из н ее. Ка р та ком н а­ ты будет об н овле н а О , а само поме щ ение п ерезапустится 0, тем самым завершая телепортацию персонажа в место на ­ значения. YЧE&HAlll МИССИll № 2 Используй телепорт, чтобы переместиться в помещение 27 и проверить дверь, ведущую в инженерный отсек. Нажми кноп­ ку, расположенную в верхней части помещения, чтобы открыть дверь (нажми клавишу U, подойдя к этой кнопке), и оставайся в помещении, пока дверь не закроется. Снова открой дверь, но на этот раз выйди из помещения и убедись, что, если смо­ треть с другой стороны, дверь закрывается. Анимация двери должна выполняться правильно. ВКЛЮЧЕНИЕ ЗАЩИТНОЙ ДВЕРИ wпюзовоrо ОТСЕКА Для того чтобы открыть дверь шлюзово г о отсека, ведущего на п ове р хность пла н ет ы, в кач естве ме ры безопасности используется нажимная п лита . Ч тобы откр ы ть дверь, один астронавт должен встать на плиту, позволяя товари щ у выйти че р ез двер ь шлюза. Благодаря этой кон с т р ук ц ии мы можем быть уверен ы , что аст р онавта, вышед ш е г о на поверхность пла н е ты , буде т кто-то страховать на косми ч еской стан ц ии. Ч тобы включить эту систему безопасности, нам необхо ­ димо добавить новую функцию в раздел ДВЕРИ. &ЛОКИРОВКА ДВЕРЕЙ В ПОМЕЩЕНИil 285
В листинге 11-6 приведен код для анимации двери. Помести его после функции shut _ engineering _ door (), добав­ ленной в листинге 11-4. Сохрани обновленную программу в файл с именем listingl1-б.py. Ты можешь запустить про­ грамму с помощью команды pgzrun listingll-6 . py, но дверь шлюзового отсека пока не работает. --пpor1ycк-- show_text ( ":<-:JмпLюте:r- -::-сооцает, sounds.say_doors_closed.play() О что двери закрыты.", 1) def door_in_room_26 (): global airlock_door_frame, room_map frames = [images.door, images.doorl, images.door2, images.door3, images.door4, images.floor shadow_f rames б о 0 @ if current_room != 26: clock.unschedule(door_in_room_26) return # реквизит 21 - это дверь в помещении 26 . if ((player_y == 8 and player_x == 2) or props[63] \ [26, 8, 2]) and props [21] [0] == 26: airlock_door frame += 1 if airlock_door_frame == 5: props[21] [О] =О #Удаляем дверь с карты после # полного открытия . room_map[O] [1] О room_map[O] [2] О room_map[O] [3] О if ((player_y != 8 or player_x != 2) and props[63] != \ [26, 8, 2]) and airlock_door_frame >О: if airlock_door frame == 5: # Добавляем дверь в реквизит и на карту , чтобы # отобразить анимацию. props [ 21] [О] = 2 6 room_map[O] [1] 21 room_map[O] [2] = 255 room_map[O] [3] = 255 airlock_door frame -= 1 objects [21] [О] objects[21] [1] 286 [images.door_shadow, images.doorl_shadow, images.door2_shadow, images.door3_shadow, images.door4_shadow, None ] rЛАВА 11 frames[airlock_door_frame] shadow_frames[airlock_door_frame]
############### ## СТАРТ ## #########Н#### --пропуск-- Листинг 11-6. Установка двери шлюзового отсека, которая активируется весом астронавта Я добавил функцию door _ in _ room _ 26 () в игру, чтобы реализовать интересную головоломку. Чтобы не рас­ сказывать ее решение и не портить тебе впечатление от этой головоломки, я не буду здесь описывать код полностью, но я уверен, что ты наверняка сможешь разобраться с ним, если возьмешься за это дело! Кадры анимации двери хранятся в списке f rames, вклю­ чая первый кадр, в котором изображена закрытая дверь, и последний кадр, в котором вместо двери изображена на ­ польная плитка О . Кадр анимации для двери шлюза хранится в переменной airlock _ door _ frame. ложении у = 8 их = 2), Если игрок стоит на плите (в по­ а дверь располагается в помеще- нии @ , номер кадра анимации увеличивается, чтобы у игро­ ка появилось впечатление, что дверь приоткрылась О . Если кадр аниJ\1ации равен ку, тогда словарь 5 0 , значит, props дверь открыта нараспаш­ и карта помещения будут обновле­ ны, чтобы удалить дверь из этого помещения. Теперь нам нужно добавить код, который будет закры­ вать дверь, когда игрок не стоит на нажимной плите, а дверь при этом уже приоткрыта @. Таким образом дверь закроется даже в том случае, если игрок сойдет с плиты. Программа отображает только те объекты, которые находятся на карте текущего помещения, поэтому начальные инструкции по­ мещают дверь (объект 21) на карту помещения, даже если в первом кадре анимации дверь будет полностью открыта. Наконец, мы J\1еняем файл изображения двери в словаре objects на текущий кадр анимации 8 . Обновляется и изо­ бражение тени, отбрасываемой дверью. В итоге, когда поме­ щение уже нарисовано, изображение двери будет соответ ­ ствовать ее текущему кадру анимации. Созданная нами схема открытия шлюзового отсека со­ здает эффект плавности. Дверь отк р ывается, когда игрок на­ ступает на плиту, и закрывается, когда персо н аж сходит &ЛОКИРОВКА ДВЕРЕЙ В ПОМЕЩЕНИll 287
с нее. Если астронавт зайдет обратно на плиту в тот J\юмент, когда дверь закрывается, шлюз снова начнет открываться. Чтобы дверь шлюзового отсека работала без запинок, функция door _ in _ room _ 26 () должна запускаться 0,05 секунды, если игрок вошел в помещение. При запуске функции door _ in _ room _ 26 ()выполняется проверка, находится ли игрок все еще в помещении 26. Если астронавт покинул комнату, инструкции 6 в листинге 11-6 каждые останавливают регулярное выполнение функции и завер­ шают ее (используя инструкцию ret urn), чтобы анимация двери не проигрывалась. Код, запускающий функцию нужно поместить в функцию door _ in _ room _ 26 (), start _ room(), она располо­ жена в верхней части раздела ЦИКЛ ИГРЫ. Функция start _ room () ние. В листинге запускается при входе игрока в помеще­ 11-7 приведены новые инструкции, которые тебе нужно добавить в свой код. --пропуск-­ ############### ## ЦИt<:Л llI ~ЪI ## ############### def sta room(): global airlock_door frame show_text("TP.O м ~ нах 1ждение: " + ro m_name, 0) if current room == 26: # Помещение с автоматической # дверью шлюза airlock_door frame = О clock.schedule_interval(door_in_room_26, 0.05) --пpC:JIIYr..'I<-- Листинг 11-7. Организуем анимацию дверей шлюзового отсека Сохрани программу в файл с именем ее с помощью команды listingl 1-7.py и запусти pgzrun listingll-7 .py. В игре на­ жми клавишу Х, чтобы воспользоваться телепортом и переме­ ститься в помещение 26. После того как окажешься в нем, про­ верь, работает ли нажимная плита должным образом (см. рис. 11.2). Попробуй встать на нее, сойти, а затем прой­ дись по ней, чтобы посмотреть, как будет вести себя дверь. Обрати внимание, что если ты выйдешь через выход, рас­ положенный в нижней части этого помещения, 288 rЛАВА 11
то обратный путь в шлюз будет прегра­ ждать дверь. (В обычном режиме игры ты можешь пройти в шлюзовой отсек, просто открыв дверь, которая после от­ крытия удаляется из игры.) Но если игрок попадает в помещение через теле порт, могут происходить всякие странные вещи, подобные описанной здесь ситуации. А все потому, что мы нарушили пространственно-временной континуум. УДАЛllЕМ ВЫХОДЫ ДЛll СОЗДАНИll СО&СТВЕННЫХ Иf РОВЫХ ПРОЕКТОВ Если ты закрыл(а) на станции некоторые выходы, создавая собственный дизайн кос­ Р ис. 11.2. Дверь открывается, когда игрок стои т на нажимной п лите мической базы, тебе наверняка придется перемещать или удалять двери этих выхо- дов. Чтобы удалить дверь из игры, измени ее запись в сло­ варе так, чтобы первое число было равно О, или же props ты можешь просто удалить соответствующую запись из сло­ варя. Настраивая игру, ты можешь удалить часть пользователь­ ского кода, который отвечает за двери безопасности в инже­ нерном и шлюзовом отсеках. Чтобы отключить дверь, сра­ батывающую с поrv·ющью нажимной плиты, удали новый код в листингах 11-6 и 11-7. Чтобы удалить автоматическую дверь, ведущую в инженерный отсек, удали код, указанный в листинге 11-4, и вместе с ним удали первый фрагмент но­ вого кода, который отвечает за нажатие кнопки (использует объект 42), в листинге 11-1. МИССИll ЗАВЕРШЕНА? Мы закончили строительство космической станции, и теперь она полностью функционирует. Пожалуй, в твоей жизни начинается новый этап, и ты уже можешь приступать &ЛОКИРОВКА ДВЕРЕЙ В ПОМЕЩЕНИll 289
к экспериментаJ\1, чтобы исследовать Красную планету. Погоди ... Что это? Кажется, у нас неприятности. rотов ли ты к ПОЛЕТУ? Отметь следующие пункты, если чувствуешь, что материал этой главы тебе понятен. D С помощью дверей мы можем закрывать области игровой карты, чтобы игроки решали головоломки в правильном порядке. D Двери должны располагаться в верхней или нижней части D Двери, открытые с помощью карт доступа, остаются помещения. открытыми. D Функции, приведенные в этой главе, можно использовать для установки автоматических дверей, таких как, например, дверь инженерного отсека. D Для размещения дверей используется словарь изображения и описания хранятся в словаре D props. Их objects. Чтобы анимировать движение двери, программа изменяет ее изображение в словаре objects. Когда помещение пере­ рисовывается, для двери используется новое изображение. D Если дверь видна с обеих сторон, то она должна быть пред­ ставлена реквизитом в виде двух дверей: по одной в каждом из помещений, в которых ее можно увидеть. D В словаре ACCESS _ DICTIONARY хранится информация о том, какие карты доступа открывают те или иные двери. Чтобы открывать двери не только ключами и картами досту­ па, нужно внести изменения в этот словарь. D Для настройки сложности игры измени время, в течение которого будет открыта дверь в инженерный отсек. D Телепорт позволяет игроку перемещаться в любую комнату в процессе тестирования игры. D Функция input () в Python обрабатывает вводимую тобой информацию как строки. Чтобы получить из них числовые значения, воспользу1":'1ся функцией ния ввода в целое число. int () для преобразова­
ВНИМАНИЕ! ОПАСНОСТЬ! &УДЬ ОСТОРОЖЕН! Когда системы космической стан­ ции выходят из строя, возникают всевозможные угрозы. В этой главе мы увидим, как из космической станции утекает воздух, и узнаем, что в комнатах могут запросто водиться всякие подвижные опасности, включая роботов-разбойников, сгустки плазмы и лужи токсичной жижи. Я оставил тему опасностей напоследок, чтобы ты 1\юг(ла) спокойно тестировать игру, не беспокоясь о том, что твоему персонажу не хватит времени или здоровья. В этой главе я покажу тебе, как сделать так, чтобы в нашей космической станции образовалась воздушная течь, а также мы научимся рисовать индикаторы, чтобы игрок знал, сколько у него ВНИМАНИЕ! ОПАСНОСТЬ! &УДЬ ОСТОРОЖЕН! 291
осталось воздуха. Кроме того, мы разместим на станции разные опасные объекты, которые fl.югут причинить вред игроку или даже убить его. И, наконец, мы закончим созда­ ние игры и полностью подготовим ее к запуску! ВВОДИМ СИСТЕМУ KOHTPOЛll ВОЗДУХА YPOBHll Проиграть в «Побег» ты можешь двумя способами: у тебя может закончиться воздух или здоровье (энергия). В ниж­ ней части экрана находятся две полоски-индикаторы, пока­ зывающие количество воздуха и здоровья, которые у тебя остались (см. рис. 1 ". 12.1). ~m• Zoro G.mo х Рис. 12.1. Две полоски, расположенные в нижней части экрана, показывают уровень твоих запасов воздуха и энергии Твоему здоровью будет нанесен урон, если ты наступишь в лужу ядовито1°'1 жижи или столкнешься с движущимися опасными объектами. В свою очередь воздух будет 292 rЛАВА 12
постепенно заканчиваться из-за пробоины в стене космиче­ ской станции. Надев скафандр, ты сможешь выиграть время, но запасы кислорода в баллоне космического костюма тоже не бесконечны. Сложнее всего тебе придется в те моменты, когда надо будет решить - пополнять или нет в данный .мо­ мент запасы воздуха, принимать или нет сейчас пищу, что­ бы восстановить энергию. ОТО&РАЖЕНИЕ ИНДИКАТОРОВ YPOBHll ВОЗДУХА и ЭНЕРrии Давай создадим в нашей программе новый раздел ИНДИКАТОРЫ, его нужно разместить после раздела ДВЕРИ, но до раздела СТАРТ в конце программы. Добавь новый код, указанный в листинге 12-1, в свою итоговую версию про ­ граммы из предыдущей главы под именем listing12-1.py. (listingl 1-7.py). Сохрани файл Если ты запустишь программу, ничего нового не произойдет, но в этом коде содержится функция, которая отображает индикаторы уровней воздуха и энергии. пpoпycк:-- ob j e ct s [ 2 l] [0] objects[21] [1] frames[airlock_door_frame] shadow ~rames"airlock door_frame] ################ ## ИНДИКАТОРЫ ## ################ О б €) О 0 @ б def draw_energy_air (): Ьох = Rect ( (20, 765), (350, 20)) screen.draw.filled_rect(box, BLACK) #Сброс индикатора # воздуха . screen.draw.text( " BOЗДYX " , (20, 766), color=BLUE) screen.draw.text ( " ЭНЕРГИЯ " , (360, 766), color=YELLOW) if air > О: Ьох = Rect ( (100, 765), (air, 20)) screen.draw.filled_rect(box, BLUE) # индикатор запаса воздуха . #Создаем новый if energy > О: Ьох = Rect ( (250, 765), (energy, 20)) screen.draw . filled_rect(box, YELLOW) # новый индикатор уровня энергии. #Создаем ВНИМАНИЕ! ОПАСНОСТЬ! &УДЬ ОСТОРОЖЕН! 293
# # # # # #### # ## # # # ## С ГАРТ ## ############### --пр,)rтус,,.-- Листинг 12-1. Отображение индикаторов воздуха и энергии Мы добавили в программу новую функцию, energy _ air (),которая draw _ рисует черный прямоугольник по­ верх области состояния, находящейся в нижней части окна игры О. Затем мы синим цветом рисуем надпись ВОЗДУХ 6 и желтым цветом t) надпись ЭНЕРГИЯ. Эта функция будет использовать переменные air и energy, каждой из которых в разделе ПЕРЕМЕННЫЕ присвоено значения 100. Если у игрока еще есть воздух (значение переменной air больше О) О , будет нарисован пряJ\юугольник, ширина ко­ торого равна значению переменной air 0 . моугольник закрашивается синим цветом Затем этот пря­ 0. Таким образом мы создаем индикатор ВОЗДУХ, максимальная ширина которо­ го в самом начале составляет 100 пикселов, шения значения переменной air но по мере умень­ становится все меньше. Отрисовка индикатора уровня энергии 8 происходит аналогично, только начальная позиция индикатора нахо­ дится немного правее (положение по оси х равно 250 вместо 100). ДО&АВЛllЕМ ФУНКЦИИ YMEHЬWEHИll УРОВНllВОЗДУХА Теперь нам нужно добавить три функции, чтобы реализо­ вать уменьшение уровня воздуха. Функция game () end _ the _ запускается в момент, когда у астронавта заканчива­ ется воздух. Она сообщает игроку, почему его :-..1иссия провалена, при этом воспроизводятся соответствующие звуки, и посередине игрового окна отображается большое сообщение ИГРА ОКОНЧЕНА. Функция air _ countdown () уменьшает количество воздуха. Также мы добавим функцию alarm(), которая сра­ ботает вскоре после начала игры, чтобы предупредить игро­ ка о том, что запасы воздуха истощаются. Все три функции приведены в листинге 12-2. Добавь в свою программу новый код, который размещен в разделе 294 rЛАВА 12
ИН ДИ КАТОРЫ программы, после недавно добавленной функ­ ции draw _ energy _ air (). Сохрани программу в файл с именем listing12-2.ру. Ты можешь запустить эту программу с помощью команды pgzrun listing1 2-2 .ру, но пока ты не ув идишь ничего нового. --пропуск-- i f energy > О: Ьох = Rect ( (250, 765), (energy, 20)) screen. draw. filled_rect (Ьох, YELLOW) # НО ,ый О б ~ 0 ИНДv:!'<:dТС-р Co::J~c!PM Э!-'Сргии. def end_the_game (reason) global game_over show_text(reason , 1) game_over = True sounds.say_mission_fail.play() sounds.gameover.play() screen.draw.text( " ИГPA ОКОНЧЕНА " , (120, 400) , color = " white " , fontsize = 89 , shadow = (1, 1), scolor = " Ыасk " 0 def air_countdown (): global air , game_over if game_over : return # Не трать воздух , @ air -= 1 б if air == 20 : sounds . say_air_low . play() if a ir == 10: sounds.say_act_now.play() (;) draw_energy_air() 0 if air < 1 : end_the_game( " Boздyx 4D> уро1,1- я это ) бесполезно . закончился! " ) def alarm () : show_text( " Boздyx + "! на исходе , Доберись помощь по в " + PLAYER_NAМE безопасное радио! " , место и вызови\ 1) sounds.alarm . play(З) sounds . say_ breach.play() ############### ## СТАРТ ## ##Н##Н####### --ПJ-;"r;уск Листинг 12-2. Добавляем функцию уменьшения уровня воздуха ВНИМАНИЕ! ОПАСНОСТЬ! &УДЬ ОСТОРОЖЕН! 295
При каждом запуске 0 функции air _ countdown () 0 air уменьшается на 1. Если ее значе­ 20 8 или 10, проигрывается специальный значение переменной ние падает до звуковой эффект, который предупреждает игрока, что у него осталось мало воздуха. Функция стинге draw _ energy _ air (),добавленная в ли­ 12-1, обновляет значения индикаторов воздуха и энергии закончился ция 0 . Если воздух end _ the _ game (), которая выводит сообщение «Воз­ 0 , срабатывает функ­ дух закончился!». СОВЕТ Звуковые файлы должны храниться в попке sounds и быть в формате .wov или .ogg. Чтобы воспроизвести файл bong.wav, воспользуйся функцией sounds.bang.play(). Кок и при ро­ боте с изображениями, тебе не обязательно указывать Pygome Zero расширение файла или каталог с оудиофойлом. Попро­ буй записать и добавить в иrру собственные звуковые эффекты для использования в различных игровых ситуациях. Внутри функции ем переJ\,1енную end _ the _ game () О мы использу­ reason для сохранения получаемых дан­ ных и выводим ее значение на экран в качестве причины гибели персонажа равно True 6. Значение переменной game _ over @ .Другие функции используют эту пере1\1ен ­ ную, чтобы узнать, что игра закончилась, а значит, их ра­ бота тоже должна быть остановлена. Затем функция end _ the _ game () выводит в центре экрана прописны­ ми буквами фразу ИГРА ОКОНЧЕНА. Текст рисуется в коор­ динатах по оси х = 120 и оси у = 400. Надпись выводится белым цветом с использованием шрифта раз~лером 89 О. Под текстом размещается тень просто для красоты. Тень смещена на 1 пиксел во всех направлениях и отрисовыва­ ется черным цветом (см. рис. 296 rЛАВА 12 12.2).
Рис. 12.2. О, нет! У тебя кончился воздух! Последняя функция в этом разделе называется alarm() @, она воспроизводит сигнал тревоги и выводит сообщение о том, что тебе нужно вызвать помощь по радио. Чтобы иг­ рок сильнее погрузился в игровой процесс, в сообщении ис­ пользуется его имя. Число, указанное в скобках в методе sounds. alarm. play (), обозначает, сколько раз будет воспроизведен звук (в листинге 12-2 их три). УМЕНЬШАЕМ УРОВЕНЬ ВОЗДУХА и ДО&АВЛllЕМ сиrНАЛ ТРЕВОrи До этого момента мы еще ни разу не запускали наши три новые функции. Давай добавим несколько инструкций в раздел программы С ТАРТ, который находится в конце листинга игры. Введи новые строки, указанные в листинге 12-3, и сохрани код в файл listing12-3.py. ВНИМАНИЕ! ОПАСНОСТЬ! &УДЬ ОСТОРОЖЕН! 297
--пр< юуск-­ ############### ## СТАРТ ## ############### generate_map () clock.schedule_interval(game_loop, 0.03) clock.schedule_interval(adjust wa:l transparency, clock schedu~e_unique(disp1ay_inventory, 1) clock.schedule_unique(draw_energy_air, 0.5) clock.schedule_unique(alarm, 10) # Чем больше значение , тем дольше длится 0.05) игра . clock.schedule_interval(air_countdown, 5) Листинг 12-3. Запуск функции уменьшения уровня воздуха Теперь у нашего астронавта не так .много вре!viени, как раньше. Как только закончится воздух, закончится и игра. Запусти программу с помощью команды pgzrun listing12-3 .ру. Ты увидишь, как твой запас воздуха по­ степенно расходуется. YЧE&HAlll МИССИll No 1 Когда воздух закончится, на экране появится сообщение ИГРА ОКОНЧЕНА. Ты обнаружишь, что больше не можешь переме­ щать астронавта. Твоя энергия (считай, з доровье) снижается на 1 процент каждые 5 секунд. Следовательно, она полностью иссякнет примерно через 8,5 минуты (500 секунд). Как дума­ ешь, можно как-то ускорить утечку воздуха со станции, чтобы тебе было проще проверить, что произойдет, когда закончится кислород? После завершения учебной миссии убедись, что ты вер­ нул(а) все значения, иначе тебе будет довольно трудно выпол­ нить свою миссию! Если во время тестирования финальной версии игры она кажется тебе слишком сложной, добавь себе сколько нужно времени, изменив в последней строке кода в листинге значение 5 12-3 на большее число. Это значение определяет, как часто функция air _ countdown () уменьшает уровень воздуха. Число обозначает секунды. Если ты используешь 298 rЛАВА 12
Raspberry Pi 2, слишком строгое ограничение во времени может сильно усложнить прохождение игры, поскольку на этом устройстве игра работает гораздо медленнее. Но даже если у тебя нет проблем с прохождением игры, ты все равно можешь заменить число 5 на большее, чтобы оста­ вить себе немного времени на, м-м, передышку. ДО&АВЯЕНИЕ ПОДВИЖНЫХ ОПАСНОСТЕЙ В игре есть три типа опасностей, которые могут двигаться: сгустки плазмы двух типов и летающий беспилотник, кото­ рый вышел из-под контроля. На рис. 12.3 показаны номера направлений, в которых могут двигаться эти опасные штуки. Угрозы движутся по прямой до того момента, пока не наткнутся i l на какое-нибудь препятствие, после чего мы прибавляем число, чтобы из­ 4 менить их направление. Это число +---- -----+ определяет траекторию перемещения опасного объекта. Например, если мы прибавим к номеру направления 1, Рис. 12.3. Номера опасность будет двигаться по часовой направлений, в которых движутся опасности, стрелке (вверх, вправо, вниз, влево). Если мы прибавим нумеруются по часовой -1, опасность бу­ стрелке дет двигаться против часовой стрелки (влево, вниз, вправо, вверх). Если прибавим дет отскакивать влево и вправо (1 и 3). Взгляни на рис. 12.3 (2 и 4) 2, опасность бу­ или вверх и вниз и убедись, что ты понимаешь, как это работает. Каждая опасность может двигаться по собственной траектории. Если в результате сложения получается число, превы­ шающее 4, r-.1ы вычитаем 4. Например, если опасность дви­ жется по часово~':'r стрелке, мы прибавляем 1 к ее номеру на­ правления каждый раз, когда она с чем-то сталкивается. Если опасность движется вниз (направление 3), то, когда она во что-то врезается, мы прибавляем 1, поэтому она на­ чинает двигаться влево (направление При следующем ее 4). ВНИМАНИЕ! ОПАСНОСТЬ! &УДЬ ОСТОРОЖЕН! 299
столкновении мы прибавим ние будет равно 5. 1, но в этом случае направле­ Поэтому мы и вычитаем номер направления станет равен 4, после чего 1. Как видно из рис. 12.3, если мы будем двигаться по часовой стрелке, то дующий но:мер направления после В таблице 12.1 1- это сле­ 4. приведены числа, которые мы можем ис­ пользовать для получения различных траекторий перемеще­ ния. Таблица 12.1. Как изменить направление, когда опасность с чем-то сталкивается Траекториs~ Число, которое нужно прибавить перемещениs~ к номеру направлениs~ По часовой стрелке О Против часовой стрелки U -1 +-+ 2 Лево/право t Верх/низ ij>Jфif.jj 2 Осторожн.о! Не перепутай числа, которые отвечают за движение. Номер направления (см. рис. 12.3) указывает програ.мме, в каком направлении движепzся опасноспzь. Число, которое мы. прибавляе.kt к но.лtеру направления (см. таблицу 12.1 ), указывает программе, в какую сто­ рону должен поверн.уть опасный объект, если он. с чем-rпо ополкнулся. ДО&АВЛllЕМ В Иf РУ ОПАСНЫЕ О&ЪЕКТЫ Давай добавиJ\1 в программу новый раздел ОПАСНОСТИ и разместим его между разделами ИНДИКАТОРЫ и СТАРТ. В листинге 12-4 приведен код, благодаря которому на нашей станции появятся опасные объекты. Добавь его в свою про­ грамму и сохрани ее в файл lz:sting12-4.py. После запуска программы ты не увидишь никаких изменений, но сможешь проверить, что код набран верно, если в окне командной строки не будет сообщенш'::1 об ошибках. --пpoпycк-- sounds. alarm. play ( 3) sounds.say_breach.play() 300 fЛАВА 12
############### ## ОПАСНОСТИ ## ############### hazard_da t a = { # номер помещения о 28 : 34 : 36 : 40 : 4 1: 42 : 46 : 48 : [ [ 1, [ [5 , [2 , [ [2 , [ [3 , [ [4 , [ [2 , [ [2 , [ [ 1, 8, 1, 5, 1, 1, 5, 1, 1, 8, 2, 1, 2, 2, 3, 2, 2, 2, 3, : [[у , 1], [7 ' 1], [5 , 2]] , 2 ] ] , 38 1], [ 6 , 2], [ 6 , 2 ], [ 4' 2]], 2 ], [ 8 , : направление , х , 3, 5, 4, 1, [ 5, 3, 3, [1, 2, 8, отклонение]] 32 : 35 : [ [ 1, [ [4 , 4' 2, 4 , 3 , 2] 2] , [ 7 ' 2] ' [ 8 , 2] ' [ 6 , 1, 2] ' 1]] ' 2]] ' [3, 5, 4, 1, 1, 1 ] ], 2] , , [5 , 5, 4, 1, 2 , 5, 2 , 8, 2]] 2]] 2]] 1, ' ' ' 2 ]], 9, 2]] 3, ############### ## СТАРТ ## #######Н## Н## --пропуск-- Листин г 12-4. Создаем опас н ости Давай создадим новый словарь , hazard _ data, ключа­ ми которого будут номера помещений. Теперь у нас дл я каждого помещения есть список опасностей, которые нахо­ дятся в этой комнате. О каждой опасности известно сле­ дующее: положение опасности по оси у и по оси х, началь­ ное направление д вижения и значение, которое необходимо прибавить, когда опасность столкнется с те J\'1 или иным объ­ ектом. Например, в помещении следующегов ида: 3, [7, 28 4, О находится опасность 1 ] . Это означает, что в самом = начале угроза располагается в позиции с координатами у 7, х = 3. Она начинает двигаться влево (направление 4), и в случае столкновения с каким-либо препятствием про­ д олжит свое д в и жение по часово i:1 стре л ке, поскольку мы прибавляем 1 к номеру ее направления. В поме щ ении 41 нас поджидают три опасности (в трех списка х), которые движутся слева направо и наоборот. От­ куда нам это известно? Потому что они начинают свое д ви ­ жение в напр а влениях 2 или 4 (вправо или влево), а затем, когда они сталкиваются с каким-либо объектом, мы прибав­ ляем к их направлению 2 (в результате значение б удет равно ВНИМАНИЕ! ОПАСНОСТЬ! &УДЬ ОСТОРОЖЕН! 301
4 или ем 2 6: не забывай, что направление 6 станет направлени­ после того, как мы вычтем из его номера число 4). УМЕНЬШЕНИЕ ЭНЕРrии (ЗДОРОВЬЯ) иrРОКА После того как мы добавили в нашу игру опасности, наJ\1 нужна функция, которая уменьшает энергию игрока при встрече с этими объектами. Функция называется energy () и нахолится н листинге из листинга 12-5. deplete _ Добанh ее после кола 12-4 в раздел ОПАСНОСТИ и сохрани свою про­ грамму в файл с именем listing12-5.py. pgzrun listing12-5.py С помощью команды ты можешь запустить программу, чтобы проверить ее на отсутствие ошибок. Но пока ничего нового программа нам не выдаст. --:-1[-.Jпусх-- 4 б: 48: [[2, .[1, 1, 8, 2, 3, 2]], 2], [8, 8, 1, 2], [3, 9, ~' 2]] О def deplete_energy (penalty): global energy, game_over if game_over: return # Не трать энергию , 6 energy = energy - penalty draw_energy_air() if energy < 1: end_the _game( "Энepгия это бесполезно . закончилась! " ) ############### ## С ГАРТ ## ############### - nr",,..Jnycr.. - Листинг 12-5. Уменьшение уровня энергии игрока Функция deplete _ energy ()в качестве аргумента при­ нимает число О и использует его для уменьшения значения переменной energy нашего игрока б . Следовательно мы можем использовать эту функцию при взаи:-.юдействии с различными опасностями, каждая из которых будет отни­ мать разное количество энергии у игрока. 302 fЛАВА 12
ЗАПУСКАЕМ И ОСТАНАВЛИВАЕМ ОПАСНОСТИ Когда игрок входит в новую комнату, функция start () hazard _ размещ ает в ней опасности. Код этой функции приведен в листинге после функции 12-6. Его необходимо разместить deplete _ energy ()в разделе ОПАСНОСТИ. Сохрани свою программу в файл с именем listing12-6.ру. Если ты запустишь се с помощью команды pgzrun li st ing12-6 .ру, то не заметишь никаких изме­ нений, потому что мы еще нигде не вызывали эту функ­ цию. - ПР'"mуск- - if energy < 1: erid_the_game ( 31: 11 ~р Ин 3""КОН -1И.Т dСЬ ! 11 ) def hazard_start (): global current_room_hazards_list, hazard_map О if current_room in hazard_data.keys(): 6 current_room_hazards_list = hazard_da ta[current_ room] ~ for hazard in current_room_hazards list: hazard_y = hazard[O] hazard_x = hazard[l ] hazard_map[hazard_y] [hazard_x] = 49 + о (current_room % 3) 0 clock.schedule_interval(hazard_move, 0.15) ############### ## СТАРТ ## ### Н########## - пропуск Листинг 12-6. Размещаем опасности в помещении, в котором сейчас находится игрок Функция hazard _ start ()будет запускаться всякий раз, когда игрок входит в новое помещение, поэтому ее ра­ бота начинается с проверки словаря hazard _ data на на­ личие записи о текущем помещении О. Если такая запись есть, значит, в этом помещении должны быть движущиеся опасности, и тогда выполняется оставшаяся часть функции. Для каждой из комнат данные о ее опасностях мы помещае!'.t в список current _ room _ hazards _ list б. Далее в функции запускается цикл @, который по очереди обраба­ тывает каждую угрозу, указанную в списке. ВНИМАНИЕ! ОПАСНОСТЬ! &УДЬ ОСТОРОЖЕН! 303
Опасности используют отдельную карту ПОJ\·tещения, hazard _ map, поэтому они с легкостью могут пролетать над объектами, расположенными на полу, так что нам не придет­ ся перезаписывать их на карте помещения. Если бы для угроз использовалась та же карта помещения, что и для объектов, первые, пролетая над реквизитом, п росто затерли бы его, или же нам пришлось бы придумать сложный способ, чтобы запомнить, над чем именно пролетели опасности. В словаре рами objects опасные объекты находятся под номе­ 49, 50 и 51. Программа довольно простым способом определяет, какой из них находится в том или ином поме­ щении. Как я уже говорил, в Python оператор % возвращает остаток после выполнения операции деления. Когда ты де­ лишь какое-нибудь число на 3, остаток будет равен О, 1 или 2. Таким образом, программа делит номер помещения на 3 и прибавляет остаток к 49, чтобы получить номер объ ­ екта О . Например, если бы мы находились в помещении п рограмма вычислила бы, что ла бы 1 к 49, 34, 34 % 3 равно 1, и добави- в результате чего для всех опасностей в этом помещении их номер будет равен 50. Подобный способ выбора опасностей гарантирует, что в помещении, в которое вошел игрок, опасности всегда бу­ дут одного и того же типа. Поскольку в ширину карта вме­ щает пять комнат, мы можем быть уверены, что в двух со­ седних помещениях не будет одинаковых опасностей. Это придает карте разнооб р азия, несмотря на то что не в каждой комнате есть у г розы. Поэтому на практике игрок, пройдя через несколько пустых комнат, вполне может столкнуться два раза подряд с одной и той же опасностью. Завершая свою работу, функция устанавливает время вы­ полнения функции hazard _ move ()так, чтобы та запуска­ лась каждые О, 15 секунды 0. Чтобы запустить функцию hazard _ start (), когда иг ­ рок входит в новое помещение, добавь в функцию start _ room ()инструкцию, Сохрани как показано в листинге программу в файл с именем listing12-7.py. 12-7. Если ты покинешь помещение, в котором начинается игра, эта версия програм­ мы зависнет, поскольку мы еще не за!{ончили работу над ко­ дом, который отвечает за опасности. 304 fЛАВА 12
--пропуск-­ ############### ## цикл l:II СJЫ # # ############### def start_room(): global airlock door frame show_te..::t ( "Твс,Е' местонах'iж,цЕ'ние: " + room_namE', 0) if curr""nt room =~ 26: # Г.vмА цение с ав~омсi~ической # дверьК' ILЛJC'"-:sa airlock_door frame - О clock.schedule_interval(door in hazard_start () rоощ 26, 0.05) -- про пуск-- Листинг 12-7. Опасности, с которыми сталкивается игрок, когда входит в помещение Не во всех помещениях есть опасности, поэтому, когда игрок покинет поl\1ещение, мы остановим работу функции, отвечающей за движение опасностей. Ранее мы добавили инструкции в функцию game _ loop (),чтобы отключить функцию, которая заставляет опасности двигаться, когда игрок меняет комнату. Мы оставили эту часть кода заком­ ментированной, поскольку еще не были готовы дать угро­ зам отпор. Но теперь все изменилось! Чтобы раскомментировать инструкции, выполни следующие действия (нечто подоб­ ное мы делали в главе 8). 1. Выбери команду меню Edit ~ Replace (Правка ~ Заменить) (или нажми сочетание клавиш Ctгl+H) в IDLE, чтобы открыть диалоговое окно поиска и замены текста. 2. Введи #clock.unschedule(hazaгd_шove) в поле Find (Найти). 3. Введи clock.unschedule(hazaгd_шove) в поле Replace With (Заменить на). 4. Нажми кнопку Replace All (Заменить все). IDLE должна заменить инструкцию в четырех местах и перейти к последней в списке. В листинге 12-8 показана новая строка, которая будет выделена в результате этих действий (не вводи этот код). Над этим блоком кода находятся три аналогичных блока, которые теперь тоже останавливают опасности, когда игрок покидает помещение через один из выходов. ВНИМАНИЕ! ОПАСНОСТЬ! &УДЬ ОСТОРОЖЕН! 305
- -пp,mycк-if player_y == -1: # в д р clock.unschedule(hazard_move) current room - МAP_WIDTH generate_map() player_y = room_hcight - 1 # ьхrд сни·~у player_x = int(room_width / 2) # вхоп в player_frame = О start_room () return дв'"рь --пропус.к-- Листинг 12-8. Прекращаем движение опасностей, когда игрок покидает помещение Сохрани обновленную программу в фаi::'1л с именем listing12-8.py. Если ты сейчас запустишь эту версию игры, то на экране консоли высветится сообщение об ошибке, а когда ты выйдешь из поr,лещения, игра зависнет. Причина этого в том, что мы еще не создали функцию hazard _ move(). СОЗДАНИЕ КАРТЫ ОПАСНОСТЕЙ Теперь мы должны убедиться, что при создании карты помещения для декораций и реквизита создается и пустая карта для опасностей. Функция hazard _ start () запол­ нит ее всеми видами опасностей, которые находятся в помещении. Добавь код, указанный в листинге generate _ map () 12-9, в конец функции в разделе ПО МЕЩЕНИЕ. Новые инструк­ ции должны располагаться непосредственно перед разделом ЦИКЛ ИГРЫ. Убедись, что перед каждо1':1 стро!{ОЙ есть от­ ступ, ПОС!{ОЛЬ!{У !{ОД находится внутри фун!{ЦИИ. Сохрани программу в файл с именем listingf 2-9.py. Если запустить программу, ты увидишь, что она не работает !{а!{ надо, потому что ;r...1ы еще не закончили над ней работу. --пропуск-- for in range(l, image_width_in_tiles): room_map[prop_y] [prop_x + tile_numЬer] = 2~5 tile_numЬer hazard_map = [] # пустой список for у in range (room_height): hazard_map.append([O ] * room_w idth) 306 fЛАВА 12
############### ## ЦИК:Л ИI РЫ ## ############### --пропуск-- Листинг 12-9. Создание пустой карты опасностей Эти новые инструкции создают пустой список для карты опасностей и заполняют его строками нулей в количестве, равном ширине помещения. ЗАСТАВЛЯЕМ ОПАСНОСТИ ДВИfАТЬСll Нн1 очень не хватает функции hazard _ move (),чтобы опасности наконец-то начали двигаться. Помести ее в конец раздела ОПАСНОСТИ после функции показано в листинге с именем 12- 10. hazard _ start (), как Сохрани программу в файл listing12-10.py. --пponycк-- hazard_map [ hazard_y] [hazard_x] - 49 + (current_room ~ 3) c-lock.schedulP_interval(hazard_move, 0.15) def hazard_move (): global current_room_hazards_list, hazard_data, hazard_map global old_player_x, old_player_y if game_over: return for hazard in current room_hazards list: hazard_y = hazard[O] hazard_x = hazard[l] hazard_direction = hazard[2] о old_hazard_x = hazard_x old_hazard_y = hazard_y hazard_map[old_hazard_y] [old_hazard_x] i f hazard- direction hazard_y -= 1 1: if hazard_direction hazard_ х += 1 i f hazard- direction hazard_y += 1 if hazard- direction hazard_ х -= 1 2: # вправо 3: # вниз 4: влево hazard- should_bounce False # # о вверх ВНИМАНИЕ! ОПАСНОСТЬ! &УДЬ ОСТОРОЖЕН! 307
if (hazard_y == player_y and hazard_x == player_x) or \ (hazard_ y == from_ player_y and hazard_x from_player_x and player_frame > 0) sounds.ouch.play() deplete_energy(lO) hazard_should_bounce = True о # Остановка опасности после выхода через дверь if hazard_x == room_width : hazard_should_bounce = True hazard_x = room_width - 1 if hazard_x == -1 : hazard_should_bounce = True hazard_x = О if hazard_y == room_height : hazard_ should_ bounce = True hazard_y = room_height - 1 if hazard_y == -1 : hazard_should_bounce True hazard_y = О 0 # # Остановка , другой если опасность столкнется с декорацией опасностью . if room_ map[hazard_ y] [hazard_ x] \ not in items_p l ayer_may_stand_on \ or hazard_map[hazard_y] [hazard_x] != hazard_should_bo unce = True if hazard_should_bounce : hazard_y = old_hazard_y # # допустимой Переход к О : последней позиции . hazard_x = old_hazard_x hazard_direction += hazard[З J if hazard_direction > 4 : hazard_direction -= 4 if hazard_direction < 1: hazard_direction += 4 hazard [ 2] = hazard_direction hazard_map [hazard_y] [haza r d_x] = 49 + (current_room % 3) hazard [OJ hazard_y hazard_x hazard[l] ############### ## С ГАРТ ## ############### --пропус<-- Листинг 12-1 О. Добавляем функцию, отвечающую за передвижение опасностей 308 fЛАВА 12 или
Работа функции hazard _ move ()основана на той же концепции, что заставляет двигаться нашего астронавта. Расположение опасностей сохраняется в переменных hazard _ х и old _ hazard old _ _у О . Затем опасность пере­ мещается б . Далее мы проверяем, столкнулась ли опасность с игро­ ком €) , покинула ли помещение О , столкнулась с объектом декорации или другой опасностью 0.В каждом из этих слу­ чаев @ значение ее позиции сбрасывается к начально1\1у, и :vrы меняем ее направление, прибавляя последнее в ее списке данных число к номеру направления б . Если при до­ бавлении этого числа номер направления становится боль­ ше ет 4 Q , то, как я уже говорил в этой главе, функция вычита­ 4, поскольку 4 - это максиI1-1ально допустимое значение направления. В свою очередь, если при добавлении этого числа номер направления становится меньше 1, функция прибавляет число 4. В конце мы запоминаем новое направ­ ление, сохраняя его в списке данных опасности 0. По завершении работы функции @ угроза по1\,1ещается в карту опасностей. COBE'I' Если опасность сталкивается с тобой О, функция deplete _ ene rgy () понижает уровень твоей энергии на 1О процентов. Если ты чувствуешь, что тебе очень трудно играть, ты можешь сде­ лать это значение равным 5. Если ты завершил( а) игру и хочешь в следующий раз усложнить себе задачу, сделай так, чтобы опас­ ности отнимали у тебя 20 процентов здоровья за раз! Как обычно, ты можешь запустить эту программу с по ­ мощью команды pgzrun listing12-10.py. Первое поме­ щение с опасностями находится справа от комнаты, с кото­ рой ты начинаешь свой путь. Как только ты туда войдешь, твоя энергия начнет таинственным образом снижаться, правда, ты этого не заметишь. Причина заключается в том , что мы пока еще не добавили код, который будет рисовать опасности. ВНИМАНИЕ! ОПАСНОСТЬ! &УДЬ ОСТОРОЖЕН! 309
ОТО&РАЖАЕМ ОПАСНОСТИ В ПОМЕЩЕНИИ Не кажется ли тебе несправедливым, что опасности нельзя увидеть? Давай-ка добавим немного кода, чтобы мы нако­ нец-то взглянули угрозам в лицо. В листинге 12-11 приведены три новые строки, которые необходимо добавить в функцию draw() в разделе ЭКРАН. Размести их ближе к концу функции, перед кодом, отвечающим за отрисовку игрока. Рис. 12.4. Этот сгусток плазмы движется против часовой стрелки Сделай перед этими инструкциями отступ в 12 пробе­ лов, поскольку они находятся внутри функции draw () (4 пробела), внутри цикла у (еще один отступ в с именем --ГJГ 4 4) и внутри циклах (еще пробела). Сохрани свою програ1\1му в файл listing/2-11.py. mуск-- # Ис-поль"!r• м rень пс &ИРУ'НЕ' объР'<'Iа. for z in range(O, shadow_width): draw_shadow(shadow_image, у, x+z) else: draw_shadow(shadow_image, у, х) hazard_here = hazard_map[y] [х] if hazard_ here != О : # Если есть опасность # в этой позиции draw_image(objects[hazard_here] [0], у , if (player у == у) : draw_player () --[1J: опус к-- Листинг 12-11. Отображение движущихся опасностей 310 fЛАВА 12 х)
ЭтиI\-1 листингом мы завершаем работу с движущимися опасностями. Запусти свою программу с помощью КОI\tанды pgzrun listing12-11.py. И ... беги со всех ног! Теперь опасности видны как на ладони, например сгусток плазмы, ПОI{азанный на рис. 12.4. YЧEliHAlll МИССИll No 2 Протестируй работу подвижных опасностей. Войди в помеще ­ ние, расположенное справа от комнаты, в которой ты начина­ ешь игру (при необходимости телепортируйся в помещение 32). Уменьшается ли твоя энергия, когда сгусток плазмы попадает в тебя? Меняет ли он свое направление по­ сле столкновения с тобой? Пройди в оба дверных проема, что­ бы убедиться, что сгусток плазмы остался в комнате. Что будет, если у тебя закончится энергия? ЗАПРЕЩАЕМ Иf РОКУ ПРОХОДИТЬ СКВОЗЬ ОПАСНОСТИ Давай добавим в наш код инструкцию, которая не позволит игроку проходить сквозь опасности. При столкновении с игроком подобные объекты обычно отскакивают от него, но если мы не внесем в игру изменения, указанные в листинге 12-12, может случиться так, что игроку иногда будет удаваться пройти сквозь угрозу. Мы уже добавили необходимый код в функцию loop (), game _ но до сих пор он был закомментирован. Пришло время включить его, удалив символ строки и символ # # перед \ в конце одной в начале следующей строки. Кроме того, нужно удалить двоеточие, которое стоит по­ сле значения items _ player _ may _ stand _ on. Для быстрого поиска нужного текста в программе нажми соче­ тание клавиш C tгl +F, чтобы открыть поисковое окно, а за­ тем введи в нем#\. В листинге 12-12 показаны строки, кото­ рые необходимо изменить. -проr1уск-- # Е.сли ;;<Гре,,,ц: 'IЧутил~я в недuпустимом для нег'I мес ге, ВНИМАНИЕ! ОПАСНОСТЬ! &УДЬ ОСТОРОЖЕН! 311
# ер и н за~. if r oo m_map[p l ayer_y ] [p l a yer_x) \ not in items_player_may_s t an d_o n \ or hazard_map[play e r _y] [play er_x ] ! = playe .х old_ player х player у = old player_y player_frame = О О : --пропуск-- Ли стинг 12-12. З а прещаем игроку проходить сквозь о пасности Сохрани программу в файл с именем пусти ее с по:мощью команды listz:ng/2-12.py и за­ pgzrun listing12-12.py. Удастся ли тебе отыскать летающие опасности всех трех ти­ пов, которые присутствуют на этой космической станции? СОЗДАЕМ ЛУЖИ ЯДОВИТОЙ ЖИЖИ Вероятно, ты уже заметил(а) на полу кляксы зеленого цвета, показанные на рис. 12.4. Это лужи ядовитой жижи, кото­ рые отнимают у твоего персонажа энергию, если он прой­ дется по ниr-.1. Здесь нужно мыслить стратегически. Стоит ли пробежаться по такой луже, пожертвовав частью энергии, чтобы добраться куда-нибудь быстрее? Или будет выгоднее осторожно обо1':'пи ее, сохранив энергию, но при этом, возможно, потратить больше времени? В листинге 12- 13 приведены инструкции , истощающие твою энергию, когда ты проходишься по кляксе из ядовитой жижи. Эти инструкции относятся к функции game _ loop () и должны идти сразу после инструкций, с которы­ ми ты работал(а) в листинге 12-12. Сохрани прогр а мму в файл с именем listing12 -13.py. Про­ верь, работает ли она, запустив ее с помощью команды pgzrun listing12-13.py, после чего пройдись по одной из токсичных луж. Плитка с ядовитой жижей ект 48, - это объ ­ который размещается в помещении в качестве деко­ рации. --'1г 1пуск- it 1',сти - игро< о<утиrс>. # Г( р( М( С TVM ЕГО d' dL. if rоощ map[player у] [player_x] not in items г~ayPr_may Scc1nd .on \ o:r :~azarci_map [play~~ _у] [olaye:r_x. ~ О: player_x - old_playc~_x 312 fЛАВА 12
player_y = old_player player_frame = О у 48 : # if room_map[p l ayer_ y] [player_ x ] deplete _ energy(l) if pldyer_direcr1on player of+ser х "right" and -l + (0.L5 * ядовитая player_fra~e > жижа О: plпyer_frпme) --п;: опуск-- Листинг 12-13. Снижаем уровень энергии игрока, если тот проходит по ядовитой жиже, разлитой но полу ВНОСИМ ПОСЛЕДНИЕ ШТРИХИ Наша игра почти готова. Прежде чем ты приступишь к исследованию космической станции, давай уберем из кода программы некоторые инструкции, которые нам были нужны исключительно для тестирования игры. ОТКЛЮЧАЕМ ТЕЛЕПОРТ Правила участия в м и ссии запрещают пользоваться теле­ портом с момента начала твоей работы на космической станции. Н айди инструкции, отвечающие за его р аботу, в функции game _ loop(), выде л и их мышью, а затем выбери команду меню F о г пыt ~ Со 111111 еп t O u t Reg ion (Форматирование ~ Закомментировать фрагмент кода), чтобы отключить их. Теперь тво1"::1 код должен выглядеть , как показано в листинге 12-14. --пропуск-- #### #### ## ## ## ## ## ## ## #### Раздел Удали телепорта этот раздел для из тестирования финальной if keyboard.x: current room = int(input( player_x = 2 player_y = 2 generate_map () start_room () sounds.teleport.play() Конец раздела игры " Bвeди номер помещения: " )) телепорта -- пропуск -- Л и ст и н г 12-14. Те л епорт о т ключен ВНИМАНИЕ! ОПАСНОСТЬ! &УДЬ ОСТОРОЖЕН! 313
ПРИВОДИМ В ПОРЯДОК ДАННЫЕ Во время тестирования игры ты, возможно, менял(а) значе­ ния некоторых переменных и содержимое списков. После запуска игры она должна выглядеть, как показано на рис. 12.5. В противном случае перейди в раздел про­ граммы ПЕРЕМЕННЫЕ и убедись, что переменной current _ room присво ено значение 31. Если в твоем инвентаре лежит что-то, кроме йо-йо, обра­ тись к части РЕКВИЗИТ программы и убедись, что эта стро­ ка выглядит именно так: in_my_ pockets = [55] 1 - Pf9omo Zoto Gomt Рис. 12.5. х Отправляемся в путешествие ТВОЕ ПРИКЛЮЧЕНИЕ НАЧИНАЕТСЯ Вот и настал этот волнующий момент! Наша подготовка завершена, а космическая станция с нетерпением ждет 314 fЛАВА 12
своих гостей. Твоя 1\н1ссия на Марсе вот-вот начнется. Сове­ тую тебе добавить в начало игры звуковое сопровождение в стиле научной фантастики. В листинге 12-15 приведена самая последняя инструкция, которую мы с тобой добавим в игру « Побег » . проr;уск clock.schedule unique(alarm, 10) # Увелич€..нИ(. Зt.а•rеt:ия увеличивсс.ет Вl=>~~1я игры. clock.schedule_interval(air_countdown, 11) time limit. soun d s . mission . pla y() # Вступительная музыка Л и стинг 12- 15. Звуковое со п ровождение и г р ы Сохрани итоговую версию программы в файл Введи команду pg zr un escape .ру, escape.py. чтобы сыграть в игру. Во введении ты найдешь все инструкции по ее за­ пуску. П оздравляю с завершением строительства космической станции. Ты по праву заслужил(а) свое место в этой мис­ сии! Вы х оди на поверхность планеты и приступай к ее ис ­ следованию! ТВОЯ СЛЕДУЮЩАЯ МИССИЯ: НАСТРОЙКА иrРЫ под СЕ&Я Удалось ли тебе спастись в игре « Побег » ? Твоя жизнь была на волоске! Чтобы выполнить свою следующую миссию, попробуй изменить игру. В этой книге было предложено множество различных вариантов по изменению проекта, так что ты, возможно, уже кое-что добавил(а), работая над ней. Предлагаю тебе несколько вариантов измененш'-'~ , начи­ ная с самого простого. • Измени имена персонажей в игре на имена своих друзей. См. листинг • 4-1 в главе 4. Использ у й д ругие изображения. Ты можешь отредактиро­ вать мои изображения или создать собственные. В игре есть доска для записей, которую ты можешь за1\-1енить на люби­ мые произведения искусства. Если ты подгонишь размер своих картинок под мои, будешь использовать те же имена ВНИМАНИЕ! ОПАСНОСТЬ! &УДЬ ОСТОРОЖЕН! 315
файлов и сохранишь их в папке images, они должны без каких-либо проблем появиться в игровом мире. • Измени планировку помещений. В главе 6 объясняется, по какому принципу располагаются декорации в помещении. • Добавь в игру собственные объекты. Для начала создай их изображения. Реквизит должен быть размером в 30 пиксе­ лов (каждая сторона). Декорации могут быть и больше, но они обязательно должны по краям касаться левой и пра­ вой сторон плиток, иначе это будет выглядеть странно, ведь игрок не сможет приблизиться к декорациям вплотную, а упрется в соседнюю плитку. (Например, если ширина тво­ его изображения 30, 60 или 90 пикселов и касается пола с обеих сторон, то это то, что тебе надо.) Не забудь доба­ вить новые элементы в словарь objects (см. главу 5). Если ты затрудняешься при размещении декораций , см. главу 6. Чтобы получить советы по размещению реквизита, см. главу • • 9. Придумай свою карту космической станции (см. главу 4). Используй игровой движок, чтобы разработать собствен­ ную игру. Ты можешь заr-.1енить изображения и карты, а также написать код для собственных головоломок, чтобы создать какую-то другую игру на основе игры « Побег». Раздел В ЗАИ МОД Е ЙС Т ВИЕ С ОБЪЕКТАМИ - это место, где программируются головоломки для игры. В нем подробно описывается, что произойдет, если использовать объекты по отдельности или в комбинации с другими объектами. Иногда бывает полезно сохранить код, позволяющий объ­ единять объекты (получая рецепты), и впоследс т вии просто обновлять его (см. главу 10). Также полезно сохранять код, отображающий стандартные ответы (с.м. главу позволяющий открывать двери (см. главу 10), или код, 11). Если ты внесешь какие-либо изменения, которые повлия­ ют на помещение 26, тебе придется скорректировать код, от­ вечающий за нажимную пли т у в этом помещении (см. гла­ ву 11). Имей в виду, что любые внесенные тобой изменения мо­ гут негативно повлиять на головоломки в исходной игре 316 fЛАВА 12
«Побег». В этом случае игру будет невозможно проi:'пи до конца. Например, игрок просто не сможет найти нужные предметы. Я рекомендую сохранять любые внесенные тобой изменения в отдельные файлы, чтобы ты в любой момент смог(ла) вернуться к исходному коду. СОВЕ1' Я бы с удовольствием взглянул на твой вариант игры! Ты можешь найти меня в «Твиттере», введя тить мой веб-сайт по адресу : @musicandwords, или посе­ www.sean.co.uk. Там ты най­ дешь дополнительные материалы к этой книге . Если ты решишь поделиться с другими людьми своей версией игры «Побег» или другими играми, созданными тобой с использованием кода, звуков или изображений из этой книги, укажи , что ты учился по данной книге, и не забудь упомянуть ее автора! Спасибо! rотов пи ты к nonETY? Отметь следующие пункты, если чувствуе ш ь, что материал этой главы тебе понятен. D Ты ;r..ложешь воспользоваться Pyga111e Zero, чтобы вывести на экран надпись с тенью и настроить разJ1.1ер шрифта ото­ бражаемого текста. D Ты можешь воспроизвести звук сколько угодно раз, указав количество повторений в скобках метода sounds. sound _ name .play (). D Направления движущихся опасностей пронумерованы от 1 до 4, начиная сверху и по часовой стрелке. Ч т обы создать траекторию движения угрозы, необходимо указать число , которое следует прибавить к номеру направления, когда опасность столкнется с тем или иным объектом. D Функция deplete _ energy ()уменьшает уровень энергии игрока. D Для опасностей используется отдельная карта помещения, hazard _ map, что позволяет им легче перемещаться отно­ сительно предметов, расположенных на полу. ВНИМАНИЕ! ОПАСНОСТЬ! &УДЬ ОСТОРОЖЕН! 317
D Перед началом игры проверь, правильно ли указаны значе­ ния переменны х, которые отвечают за начальное местопо­ ложение игрока. ОБСУЖДЕНИЕ МИССИИ Ниже представлены ответы на учебные миссии из этой главы. УЧЕБНАЯ МИССИЯ № 1 В последней строке листинга 12-3 значение 5 измени на 1. Тогда срункци.я, уменьшающа.я уровень воздуха, будет срабатывать раз в секунду, а не каждые 5 секунд. Ты можешь сделать так, что воздух будет заканчиватьс.я еще быстрее, если изменишь значение на о, или на любое другое дес.ятичное число. 5
полный пистинr иrРы «ПО&Еr» Приложение содержит окончатель­ ный вариант листинга игры «Побег». Ты можешь обращаться к нему, чтобы понять, где разместить те или иные функ­ ции и разделы, или можешь просто пробе­ жаться по тексту, чтобы просмотреть весь код целиком. В этот листинг не входят временные разделы, которые мы включали во время создания игры. Сюда относится, например, раздел ОБЗОР. Здесь размещен только тот код, который используется в финальной версии игры. ПОЛНЫЙ листинr ИfРЫ «ПО&Еf)) 319
Поr..ши, что ты можешь просто скачать файл и просмотреть его в IDLE, escape.py где доступен поиск нужных строк кода с по.rvющью сочетания клавиш Ctrl+F. В листинге я изменил значение переменной PLAYER _ NAME на Капитан. При создании или настройке игры ты можешь использовать свое имя (см . О в листинге ве 4-1 в гла­ 4). Чтобы проверить работоспособность кода, я собрал игру, используя инструкции, представленные в этой книге. Про­ грамма была проверена в и # Побег # # Шана - Raspberry Pi 2 на языке Python / www.sean.co.uk приключения Макмануса Графика Windows, Raspberry Pi 3 модели В+ модели В. Рафаэля Пимента import time, random, math ################ ## ПЕРЕМЕННЫЕ ## ################ WIDTH = 800 # HEIGHT = 800 # переменные PLAYER_NAМE размер для = окна описания " Капитан " # игроков измени " Кэрол " # FRIEND2_NAМE " Лео " измени current room 31 # FRIENDl_NAМE top_left_x top_left_y # измени начальное на на на собственное имя имя своего еще помещение имя! друга! одного своего друга! = 31 100 150 DEMO_OBJECTS = [images.floor, images.pillar, images.soil] LANDER_SECTOR = random.randint(l, 24) LANDER_X random.randint(2, 11) LANDER_Y = random.randint(2, 11) ТILE SIZE = 30 player_y , player_x game_over False 2, 5 PLAYER = " left " : [images.spacesuit_left, images.spacesuit_ left _l , images . spacesuit_left_2, images.spacesuit_left_3 , 320 ПРИЛОЖЕНИЕ А
images.spacesuit_left_4 ' [images.spacesuit_right , images .spacesuit_right_l, images.spacesuit_right_2, images.spacesuit_right_З, images.spacesuit_right_4 ] "right" : "up" : ] ' [ images. spacesuit_back, images. spacesuit_back_l, images.spacesuit_back_2, images.spacesuit_back_З, images.spacesuit_back_4 ] "down" : ' [ images. spacesui t_front, images. spacesui t_front_l, images.spacesuit_front_2, images.spacesuit_front_З, images.spacesuit_front_4 player_direction = "down" player_frame = О player_image = PLAYER[player_direction] [player_frame] player_offset_x, player_offset_y = О, О PLAYER_SHADOW = { "left" : [ images. spacesuit_left_shadow, images.spacesuit_left_l_shadow, images.spacesuit_left_2_shadow, images.spacesuit_left_З_shadow, images.spacesuit_left_4_shadow ' [images.spacesuit_right_shadow, images.spacesuit_right_l_shadow, images.spacesuit_right_2_shadow, ] "right" : images.spacesuit_right_З_shadow, images.spacesuit_right_4_s hadow "up" : ] ' [images. spacesuit_back_shadow, images.spacesuit_back_l_shadow, images.spacesuit_back_2_shadow, images.spacesui t_bac k_З_shadow, images.spacesuit_back_4_shadow ] "down" : ' [images.spacesuit_front_shadow, images.spacesuit_front_l_shadow, images.spacesuit_front_2_shadow, images.spacesuit_front_З_shadow, images.spacesuit_front_4_shadow ] player_image_shadow PLAYER_SHADOW [ "down" ] [О] PILLARS = [ images.pillar, images.pillar_95, images.pillar_80, полный nистинr иrРы « ПО&Еr )) 321
images . pil l ar_ 60 , images .pil l ar_ 50 ] wall_tr ansparency_frame О BLACK = ( 0 , О , 0 ) BLUE = ( 0 , 155 , 255 ) YE LLOW = (255 , 255 , 0 ) WHITE = (2 55 , 255 , 255 ) GREEN = ( 0 , 255 , 0 ) RED = (128 , О , 0 ) air , energy = 10 0, 1 0 0 suit_ st i tched , a i r fixed launc h_f r ame = О False , False ############### ## КАРТА ## ############### _WIDTH = 5 = 10 МАР S I ZE МAP_WIDTH * МАР МA P_ HE IGHT [[ " Помещение GАМЕ _МАР О, О, МAP_H EIGHT О здесь хранятся неиспользуемые предметы " , False , False ]] outdoor_rooms = range ( l , 26 ) for plane t sect ors in range (l , 26 ): #rooms 1 to 25 are generated here GАМЕ_МАР . append ( [ " ПЬmьная поверхность планеты " , 13 , 13 , True , True ] ) GАМ Е_МАР += [ #["Название # Выход помещения ", [ " Лаборатория " , [ [ [ [ [ [ [ [ [ [ [ [ [ 322 ширина , выход вверх? , 13 , 5 , True , False ] , #помещение 26 13 , 13 , False , False ] , #помещение 27 " Система управления посадочным модулем " , 9, 13 , False , True ] , # помещение 28 " Обсерватория " , 9 , 1 5, False , False ] , #помещение 29 " Санузел " , 5 , 5 , Fals e , False ] , #помещение 30 " Шлюзовой отсек " , 7 , 11 , True , True ] , #помещение 31 " Проход слева " , 9 , 7 , True , False ], #помещение 32 " Проход справа " , 7 , 13 , True , True ] , #помещение 33 " Библиотека " , 13 , 1 3 , False , True ], # помещение 34 " Теплица " , 13 , 1 3 , True , False ] , #помещение 35 " Каюта " + PLAYE R_NAМ E + " а " , 9 , 1 1 , False , False ], # помещение 36 " Западный коридор " , 15 , 5 , True , True ], #помещение 37 " Переговорная " , 7 , 13 , False , True ] , #помещение 38 " Комната отдыха " , 1 1 , 13 , True , False ], #помещение 39 " Центр управления полетами " , 14 , 14 , False , False ] , # помещение 40 " Лазарет " , 1 2 , 7 , True , False ], #помещение 41 [ " Шлюз " , [ высота, справа?] ПРИЛОЖЕНИЕ А
9 , 7 , True , False ] , #помещение 42 9 , False , True ] , #помещение 43 [ " Инженерный отсек " , 9 , 11 , False , False ] , #помещение 44 [ " Секретный проход в ЦУП " , 7 , 7 , True , False ] , #помещение 45 [ " Каюта " + FRIENDl_NAМE , 9 , 11 , True , True ] , #помещение 46 [ " Каюта " + FRIEND2 _NAМE , 9 , 11 , True , True ] , #помещение 47 [ " Насосная " , 13 , 1 1 , True , False ] , #помещение 48 [ " Каюта главного научного сотрудника " , 9 , 7 , True , True ] , # помещение 49 [ " Робототехническая мастерская " , 9 , 11 , True , False ] # помещение 50 [ " Западный коридор " , 9, [ " Диспетчерская " , #простая assert не проверка работоспособности len (GAМE_МAP ) -1 == карты для МAP_SIZE , " Размер проверки карты и ввода данных G АМЕ _МАР совпадают " ############### ## ОБЪЕКТЫ ## ############### ob j ects = { О : [images . floor , None , " Чистый и блестящий пол " ] , 1 : [images . p i l l ar , images . ful l _s h adow , " Гладкая и холодная \ стена " ] , 2: [images . soi l, None , " Похоже на пустыню . Или тут кто-то \ живет? " ], 3 : [images . pil l ar_low , images . half_shadow , " Гладкая и холодная \ стена " ], 4 : [images . bed , images.half_shadow , " Аккуратная и удобная \ кровать " ] , 6: 7: 8: images . half_shadow , " Стол из прочного \ , [images . chair_left , None , " Стул с мягким сидением " ] , [images . chair_ righ t, None , " Стул с мягким сидением " ], [images . bookcase_ tal l, i mages . fu ll_shadow , 9: [images . bookcase_sma ll, 5: [images . taЫe , пластика " ] " Высокий книжный " Маленький шкаф " ] , книжный images .ha l f_shadow , шкафчик " ] , 10 : [images . cabinet , images . half_shadow , 11 : [images.desk_ computer, images . ha l f _ shadow , " Небольшой шкафчик " Компьютер . для хранения Используй для личных диагностики вещей " ] , систем \ жизнеобеспечения " ], 12 : [images.plant , images . plant_shadow , выращена " Космическая ягода , \ здесь " ] , 13 : [images . electricall , images . half_ shadow , 14 : [images.electrica l 2 , images .ha l f_shadow , 15 : 16 : [images . cactus , images.cactus_shadow , [images . shrub , images.shrub_shadow , " Электрические " Электрические " Космическая системы системы петрушка . для для работы работы Слегка космической космической подвяла , " Ай ! но Это все станции " ], станции " ], же кактус !" ] , еще растет! " ], полный nистинr иrРы « ПО&Еr » 323
17 : 18 : [images.pipesl , images.pipesl_shadow , [images . pipes2 , images . p i pes2_shadow , 19 : [ images . pipes3 , images . pipes3_shadow , 20 : [images . door , images . door_shadow , " Трубы систем " Трубы рабочий 22 : жизнеобеспечения " ] , автоматически , если " Безопасный космонавт целях выход . \ надел\ скафандр. " ], [ images . door , images . door_shadow , В трубы " ] , систем жизнеобеспечения " ] , Открывается 2 1: " Водоочистные безопасности " Дверь открывается двумя \ шлюза . людьми ." ] , 25: [images.door , images . door_shadow , " Закрыто . Нужна карта \ доступа " \ + PLAYER_NAМE + " а " ] , [ images . door , images . door_ shadow , " Закрыто . Нужна карта \ доступа " \ + FRI END l _NAМ E] , [ images . door , images. door_shadow , " Закрыто . Нужна карта \ доступа " \ + F R IEND2_NAМE ], [images.door , images . door_shadow , 26 : [images . door , images . door_shadow , 27 : [images . map , images.full_shadow , 23 : 24 : " Закрыто . Можно " Запертая попасть дверь " Сказано , что в У: центра инженерный крушение управления полетами " ] , отсек ." ] , произошло + " 11 + str (LANDER_SECTOR) " 11 из здесь : " \ " + str (LANDER_X) Х: 28 : [images . rock_ large , images . rock_large_shadow , 29 : [images . rock_small , images . rock_small_shadow , 30: 3 1: [images . crater, None , [images . f ence , None , 32 : [images . contraption , images . contraption_shadow , 33 : [images . robot_arm , images . robot_arm_shadow , 34 : [ images . toilet , images . half_ shadow , " Порода . + \ + str (LANDER_Y ) ], " Поверхность " Небольшой , но " Мелкоячеистый " Результат тяжелый для на кусок точильный на Защищает станцию от Мягко " порода " ], породы " ] , поверхности эксперимента . подъема камень " , марсианской " Кратер забор. научного " Манипулятор похожа планеты " ] , пьmьных бурь " ] , вибрирует " ] , тяжестей " ] , " Сверкающий чистотой \ унитаз " ] , 35 : 36 : [images . s i nk, None , " Раковина с текущей [images.globe , images.globe_shadow , 37 : [images . science_lab_taЫe, 38 : [images . vending_machine , images.full_shadow , 39 : [images .f loor_pad , None , 40 : [images . rescue_ship , images . rescue_ship_shadow , \ 41 : [images . mission_control_desk , \ images . mi ssion_control_des k_shadow, " Панели [images . button , images . button_shadow , " Гигантский " Стол для глобус анализа " Торговый плита , " Спасательная 42 : " Кнопка , 324 грунта автомат . " Нажимная на ПРИЛОЖЕНИЕ А планеты . " краники " ] , светится " ] , None , и Требует чтобы Слегка водой " , пьmи с поверхности денежку ." , никто не планеты " ] , " торговый выходил автомат " ], один ." ], шлюпка !" ], время открывающая дверь в ЦУПа ." ], инженерный отсек ." ] ,
43 : [images . whit eboard , images . full_s h adow , " Доска и для записей во время мозговых штурмов \ переговоров ." ], 44 : [images . window , images . full_shadow , 45 : [images . robot , images . robot_ shadow , " Из окна открывается вид на поверхность планеты ." ], " Робот-уборщик , \ выключен ." ], 46 : [images . robot2 , images . robot2_shadow , " Робот для исследования поверхности планеты, ожидает \ инструкций ." ], 47 : [ i mages . rocke t, одноместная 48 : 49 : 50 : 51 : 52 : " Ремонтная images . rocket_shadow , \ шлюпка ." ] , 53 : [images . toxic_f loor , None , " Ядовитая жижа - не наступай! " ] , [images . dro n e , None , " Дран-курьер " ] , [images . e n e r gy_ball , None , " Сгусток плазмы - опасно !" ] , [ images . energy_ball2 , None , " Сгусток плазмы - опасно !" ] , [ images . compu t er , images . computer_shadow , " Рабочая станция для управления системами космической \ станции. " ] , [images . c li p b oard , None , " Доска-планшет . Кто-то изрисовал \ 54 : [ images . b u bЫe_g u m , ее ." , " доска-планшет " ], None , " Кусочек липкой жвачки . Аромат космической ягоды ." , \ " жвачка " ], 55 : [images . yoyo , None , и пластика . Не из " йо-йо " + PLAYER_NAМE [images .thread , None , 57 : [ima g es . needle , None , 58 : [ima g es . t h reade d_need l e , None , 59 : [images . canister , None , тонкой " Острая " Игла игла прочной с с воздухом , " негерметичный 60 : " кусок " игла нитью " , прочной нити \ \ " а " ], нити " , кактуса " , кактуса " Баллон + тонкой , гравитации ." , 56 : " Кусок нити " ] , кактуса " ], " иголка с ниткой " ] , негерметичен ." , баллон с воздухом " ] , [images . can i ste r, None , " Похоже , с " Игрушка подвержена теперь утечки нет! " , " герметичный баллон воздухом " ] , 61 : [ima g es . mirror , None , 62 : [images . bin_ empty , None , 63 : [images . bin_full , None , 64 : [images . rags , Non e , " Зеркало " Почти отбрасывает новый " Тяжелый " Тряпка , " тряпка 65 : 66 : 67 : бак , бак из полный пропитанная в круг света легкого воды " , маслом . на стены ." , пластика " , " бак с Возьми " зеркало " ] , " бак " ] , водой " ] , за уголок , если нужно !" , масле " ], [ima g es .hammer , None , " Молоток. Пригодится , чтобы сломать \ что-нибудь ... " , " молоток " ] , [images . s p oon , Non e , " Большая столовая ложка " , " ложка " ] , [images . food_pouch , None , полный nистинr иrРы << ПО&Еr» 325
" Пакетик 68 : 69 : с обезвоженной пищей. паникуй! ' на обложке . [images . mp3_player , None , 7 1: [ images . lander , None , " МР3-плеер черном с ящике последними есть хитами " , " Небольшой " сухпаек " ] , чтобы\ \ " книга " ] , " МР3-плеер " ] , посадочный радиопередатчик ." , модуль. " посадочный " Радиопередатчик \ [images . gps_module , None , " GРS - модуль " , " GРS-модуль " [ images . positioning_ system, None , " Неработающая \ ] , " радиопередатчик " ] , навигационная " детали из В его\ модуль " ], посадочного [images. r adio , None , Модуля " , 73 : 74 : вода ." , Обнадеживает !" , 70 : 72 : Нужна [images . food , None , " Пакетик с едой. Используй его, получить 1 О О% энергии. " , " готовая еда " ] , [images . boo k, None , " На обложке этой книги написано 'Не система . системы Необходимо вставить GРS-модуль ." , 75 : [ images . position i ng_system , None , навигационная система " , " навигационная система " ] , 76 : [ images . scissors , None , " Ножницы. Слишком чтобы " Рабочая резать . Может , их как-то 77 : [images . scissors , None , 78 : [ images . credi t , None , 79 : [images . access_card , None , " Острые ножницы . " Монетка 80 : 81 : для заточить? " , Осторожно! " , торгового " острые автомата " , " Карта доступа " + P L AYER_NAМE + [images . access_card , None , " Карта доступа " + F RI ENDl _NAМ E, [images . access_card , None , " Карта доступа " + FRIEND2_NAМE , тупые, " тупые " монетка-кредит " ] , "а" , " карта доступа " ], " карта доступа " ] , " карта доступа " ] ядовитой жиже . [ О, 39 , 2 , 48 ) ############### ## ДЕКОРАЦИИ ## ############### Декорации - это объекты , которые невозможно перемещать помещениями. #номер помещения : [[номер объекта , позиция по оси # ПОЗИЦИЯ ПО ОСИ Х) ... ) scenery = { 26 : [[39 , 8 , 2)) , 27 : [ [33 , 5 , 5) , [33 , 1 , 1] ' [ 33 , 1 , 8] , [ 4 7 , 5 , 2] , [47 , 3 , 10) , [ 4 7 , 9 , 8 ], [ 42 , 1 , 6)] , 28 : [ [27 , О , 3) , [ 41 , 4 , 3) , [ 41 , 4 , 7]] , 29 : [ [7 , 2 , 6) ' [ 6 , 2 , 8] , [ 12 , 1 , 13) ' [ 44 , о , 1] , [36 ,4, 10 ), [ 10 , 1 , 1 ], [ 19 , 4 , 2 ], [ 1 7 , 4 , 4] ], 30 : [ [34 , 1 , 1) , [35 , 1 , 3) ), 3 1: [[11 , 1 , 1) , [19 , 1 , 8) , [ 46 , 1 , 3) ), 326 ПРИЛОЖЕНИЕ А \ ножницы " ] , ножницы " ], i tems_player_may_carry = list ( range (53 , 82 )) # Числа ниже относятся к полу , нажимной плите , грунту , items_player_may_s t and_on = items_player_may_carry + # # \ навигации " ] , у, между
32 : 33 : 34 : 35 : 36 : 37 : 38 : 39 : 40 : 41: 44 : 45 : 46 : 47 : 48 : 49 : 50 : [ [ 48 , 2 , 2 ], [ 48 , 3 , 4 ], [ [ 13 , 1 , 1], [ 48 , 2 , 7 ], [ [ 37 , 2 , 2 ], [ [ 16 , 2 , 9] , [ 16 , 1 , 8 ], [ 15 , 4, 6 ], [ [ 4, 3 , 1 ], [ 5 , 5 , 4 ], [ [ 48 , 3 , 1], [ 48 , 7, 2 ], [ [ 43 , О , 2 J, [ [ 38 , 1 , 1], [ 6 , 3 , 9 ], [ [ 41 , 5 , 3 ], [ 13 , 1 , 1], [ [ 4 , 3 , 1], [ 1 0 , 7 , 5 ], [ [ 46 , 4, 3 ], [ 19 , 1 , 5 ], [ [ 48 , 2 , 1 ], [ [ 10 , 1 , 1], [ 7 , 3 , 2 ]], [ [ 9 , 1 , 1 ], [ 4 , 1 , 8 ]], [ [ 17 ,4,1 ] , [ 17 , 4 , 7 ], [ 17 , 8 , 5 ], [ [ 14 , 2 , 2 ], [ [ 45 , 4 , 8 ], [48 , 2 , 3] , [ 48 , 2, 4] , [48 , 3 , 2] , [ 48 , 3 , 3 ], [48 , 4 , 2] , [ 48 , 4 , 3] , [ 4 8 , 4 , 4]] , [ 1 3 , 1 , 3] , [ 1 3 , 1, 8] , [13 , 1 , 1 0 ] , [ 48 , 2 , 1] , [ 48 , 3 , 6] , [48 , 3 , 3]] , [32 , 6 , 7] , [ 37 ,10,4 ] , [28 , 5 , 3]] , [ 16 , 2 , 2] , [ 16 , 3 , 3 ], [ 16 , 3 , 8] , [ 1 6 , 8 , 9 ], [ 16 , 8 , 2] , [ 1 6 , 1 , 3] , [ 1 2 , 8 , 6] , [ 12 , 9 , 4 ] , [ 12 , 9 , 8 ], [ 12 , 7 , 1] , [12 , 7 , 11]] , [ 9 , 1 , 7] , [ 8 , 1, 8 ], [ 8 , 1, 9 ], [ 6 , 5 , 7] , [ 10 , 1 , 1 ] , [ 12 , 1 , 2]] , [48 , 3 , 2] , [ 48 , 7 , 1] , [ 4 8 , 5 , 2] , [ 4 8 , 5 , 3], [ 48 , 9 , 2] , [ 48 , 9 , 3] , [ 4 8 ,1 1 ,1 ] , [ 4 8 ,11, 2]] , [6 , 2 , 2] , [ 6 , 3 , 5] , [ 6 , 4, 7 ] , [ 6 , 2 , 9 ], [ 45 ,1,1 0 ]], [ 7, 3 , 4] , [ 7 , 6 , 4] , [5 , 3 , 6] , [5 , 6, 6 ], [ 6 , 6 , 9 ], [ 4 5 , 1 , 11 ] , [ 12 , 1, 8] , [ 1 2 , 1, 4] ], [ 41 , 5 , 7] , [ 41 , 9 , 3] , [ 4 1 , 9 , 7] , [ 13 , 1 , 3] , [42 , 1 , 12]] , [ 10 , 3 , 5] , [ 4 , 5 , 1 ] , [1 0, 5 , 5] , [ 4, 7, 1], [ 1 2 , 1 , 1] , [ 1 2 , 1, 5 ] ] , [ 4 6 , 4 , 5] , [ 1 8 , 1, 1] , [ 19 , 1 , 3 ], [ 52 , 4 , 7] , [ 1 4 , 1, 8]] , [ 4 8 , 2 , 2] , [ 48 , 3 , 3] , [ 48 , 3 , 4] , [ 48 , 1 , 4] , [ 48 , 1 , 1] ], [ 4 , 1 , 2] , [ 8 , 1 , 7] , [ 9 , 1 , 8] , [ 8 , 1, 9 ], [ 5 , 4 , 3 ], [ 9 , 1, 2 ], [ 10 , 1 , 3] , [ 12 , 1 , 7] , [5 , 4, 4] , [ 6 , 4, 7] , [ 17 ,4, 2] , [ 17 , 4 , 3 ], [ 17 , 4, 4] , [ 1 7 , 4 , 5 ], [ 17 , 4 , 6] , [ 1 7 , 8 , 1] , [ 17 , 8 , 2] , [ 1 7 , 8 , 3 ], [ 1 7 , 8 , 4], [ 1 7 , 8 , 6] , [ 1 7 , 8 , 7] , [ 14 , 1 , 1]] , [ 1 4 , 2 , 4 ] , [ 7 , 5 , 1] , [5 , 5 , 3] , [ 48 , 3 , 3 ], [ 48 , 3 , 4 ] ] , [ 1 1 , 1 , 1] , [ 1 3 , 1, 8] , [33 , 2 , 1] , [ 46 , 4 , 6 ] ] checks u m = о check cou n te r = о for key , room_scenery_list in scenery .items(): for s cene r y_i tem_l ist in r oom_scene r y_l i st : chec k sum += ( scenery_item_list[OJ * key + scenery_item_list[l ] * (key + 1) + scen e ry_ ite m_l ist[2 ] * (key + 2) ) c h ec k cou nt e r += 1 print (c he c k_co un te r, " (количество декораций) " ) assert c h ec k_co un t er == 16 1, " Декораций должно быть 161 assert checks u m == 2 0 009 5, " Ошибка в данных декораций " рrint ( " Контрольная сумма декораций: " + str (chec k sum)) предмет " for r oom in range ( l, 26 ): #Добавление случайных декораций в локациях # планеты . if room ! = 1 3 : # Пропуск помещения 13. scenery_item = ran d o m. c h oice( [ 16 , 28 , 29 , 30] ) scenery [ room ] = [ [ scener y _item , random . ran d in t( 2 , 10 ), random . ra n d int( 2 , 1 0) ]] полный листинr иrРы « ПО&Еr » 327
# Используем # планеты . циклы , чтобы установить for room_ coordinate in range (O , 13 ): for room_number in [1 , 2 , 3 , 4 , 5] scenery[room_number] += [ [31 , for room_ number in [1 , 6 , 11 , 16 , scenery[room_ number ] += [ [31 , for room_ number in [5 , 1 0 , 15 , 20 , scenery[room_number ] += [ [31 , del scenery[21] [- 1] # del scenery[25] [- 1] # ограждение : # Верхнее на поверхности ограничение room_coordinate]] 21] : # Ограничение слева room_ coord i nate , 0]] 25 ]: # Ограничение справа room_coo r dinate , 12]] О, Удаление последнего ограничения из помещения Удаление последнего ограничения из помещения 21 25 ############### ## ПОМЕЩЕНИЕ ## ############### def get_floor_type ( ) if current room in outdoor rooms : return 2 # грунт else : return О # кафельный пол def generate_map ( ) : # Эта функция создает карту для текущего помещения , # используя данные о комнате , декорациях и реквизите . global room_map , room_width , room_height , room_name , hazard_map global top_left_x , top_lef t _y , wall_transparency_frame room_data = GAМE_МAP[current_room] room_ name = room_ data[O J room_ height = room_ data[l ] room_width room_data[2] floor _ type = get _ floor _type () 21 ) bottom_ edge = 2 # грунт side _edge = 2 # грунт if cur r ent room in range (2 1, 26 ) bottom_edge 1 # стена side _edge = 2 # грунт if current room > 25 : bottom_edg e 1 # стена side_edge = # стена i f current room in range (l , # Создание верхней строки карты помещения. room_map= [[side_edge] * room_widt h ] # Добавление средних строк карты помещения (стена, пол для # заливки по ширине, стена) . for у in range (room_height - 2 ): room_map . append([side_edge] + [floor_type]*(room_width - 2 ) + [side_edge] ) # Создание нижней строки карты помещения . 328 ПРИЛОЖЕНИЕ А
room_map.append([bottom_edge] * room_width) # Добавление дверных проемов . middle_row = int (room_height / 2) middle_column = int (room_width / 2) if room_data[4 ]: # Если выход в правой части помещения room_map[middle_row] [room_width - 1] = floor_type room_map [middle_row+l] [room_width - 1] floor_type room_map[middle_row-1] [room_ width - 1] = floor _ type if current _ room% МAP _WIDTH != 1 : # Если помещения слева нет room_ to_l eft = GAМE_МAP[current _r oom - 1] # Если в помещении слева есть выход справа , добавляем выход # слева в это помещение if room_to_left[4] : room_map[middle_row] [О] = floor_type room_map[middle_row + 1] [0] floor_type room_ map [middle_ row - 1] [0] = floor_type if # Если выход в верхней части помещения room_map[O ] [middle_column] = floor_type room_map[O J [middle_column + 1] floor_type room_map[O J [middle_column - 1] = floor_type room_data[ЗJ: if current_room <= МAP_SIZE - МAP_WIDTH : # Если помещения снизу нет room_below = GAМE_МAP[current_room + МAP_WIDTH] # Если в помещении внизу есть выход вверху , добавляем выход # внизу в это помещение . if room_below[З] : room_map[room_height-1] [middle_column] = floor_type room_map[room_height-1] [middle_column + 1] floor_type room_map[room_height-1] [middle_column - 1] = floor_type if current_room in scenery : for this_scenery in scenery[current_room] : scenery_number = this_scenery[O] scenery_y = this_scenery[l] scenery_x = this_scenery[2] room_map[scenery_y] [scenery_ x] = scenery_n umber image_here = objects[scenery_number] [0] image_ width = image_here.get_width() image_widt h_in_tiles = int (image_width / TILE_SIZE) for tile_number in range (l , image_width_in_tiles): room_map[scenery_y] [scenery_x + tile_number] 255 center_y = int (HEIGHT / 2) #Центр игрового center_x = int (WIDTH / 2) room_pixel_width room_width * TILE SIZE # # в пикселах окна Размер помещения полный nистинr иrРы «ПО&Еr)) 329
room_pixel_height = room_height * TILE_SIZE top_left_x center_x - 0.5 * room_pixel_width top_left_y = (center_y - 0.5 * room_pixel_height) + 110 for prop_number, prop_info in props.items(): prop_room = prop_info[OJ prop_y = prop_info[l] prop_x = prop_info[2] if (prop_room == current_room and room_map[prop_y] [prop_x] in [О, 39, 2]): room_map[prop_y] [prop_x] = prop_number image_here = objects[prop_numЬer] [О] image_width = image_here.get_width() image_width_in_tiles = int (image_width / TILE_SIZE) for tile_number in range (l, image_width_in_tiles) room_map[prop_y] [prop_x + ti le_number] = 255 hazard_map = [] # пустой список for у in range (room_height): hazard_map . append([O] * room_width) ############### ## ЦИКЛ ИГРЫ ## ############### def start_room () global airlock_door_frame show_text( " Tвoe местонахождение : " + room_name, 0) if current_room == 26: # Помещение с автоматической airlock door frame = О clock.schedule_interval(door_in_room_26, 0.05) hazard_start() def game_loop (): global player_x, player_y, current_room global from_player_x, from_player_y global player_image, player_image_shadow global selected_item, item_carrying, energy global player_offset_x, player_off set_y global player_frame, player_direction if game_over: return if player_frame > О: player_f rame += 1 time.sleep(0.05) if player_frame == 5: player_f rame = О player_offset_x player_offset_y = 330 ПРИЛОЖЕНИЕ А О О дверью шлюза
# сохраняем текущую позицию old_player_x player_x old_player_y = player_y игрока # двигаемся , если нажата клавиша if p l ayer_frarne == О : if keyboard . right : frorn_p l ayer_x player_x frorn_player_y = player_y player_x += 1 player_direction = " right" player_fr arne = 1 elif keyboard . left : #elif запрещает # по диагонали frorn_p l ayer_x player_x frorn_player_y player_y player_x -= 1 player_ direction " left " player_f rarne = 1 elif keyboard . up : frorn_player_x player_x frorn_player_y player_y player_y -= 1 player_direction " up " player_f rarne = 1 elif keyboard.down : frorn_player_x player_x frorn_player_y = player_y player_y += 1 player_direction = " down " player_f rarne = 1 # проверка на выход из помещения if p l ayer_x == r oorn_width : # через c l oc k. unschedule(hazard_ rnove) current roorn += 1 generate_rnap () player_x = О # вход слева player_y = int (roorn_height / 2) p l ayer_ frarne = О start_ roorn () return дверь игроку двигаться СПРАВА #вход в if player_x == -1 : # через дверь СЛЕВА c l ock . unschedule(hazard_rnove) current roorn -= 1 generate_rnap () p l ayer_x = roorn_ width - 1 # вход справа player_y = int (roorn_height / 2 ) #вход в p l ayer_f rarne = О start_roorn () return дверь дверь полный листинr иrРы « ПО&Еr » 331
if player_y == room_height : # через дверь clock.unschedule(hazard_move) current_room += МAP_W I DTH generate_map() player_y = О # вход сверху player_x = int (room_width / 2) #вход player_ f rame = О start_ room () return ВНИЗУ в if player_y == -1: # через дверь ВВЕРХУ clock.unschedule(hazard_move) current_room -= МAP_WIDTH generate_map () player_y = room_height - 1 # вход снизу player_x = int (room_width / 2) #вход в player_f rame = О start_room () return дверь дверь if keyboard.g : pick_up_object() if keyboard . tab and len (in_my_pockets) > О : selected_item += 1 if selected_item > len (in_my_pockets ) - 1: selected_item = О item_carrying = in_my_pockets[selected_item] display_inventory() time . sleep(0.2) if keyboard.d and item_carrying : drop_object(old_player_y , old_p l ayer_x) if keyboard . space: examine_ object() if keyboard.u : use_object() #### #### ## ## ## ## ## ## ## #### # # 332 Раздел Удали телепорта этот раздел для тестирования из финальной if keyboard . x : current room = int(input( player_x = 2 player_y = 2 generate_map () start_room () sounds . teleport.play() Конец раздела Если его игрок ПРИЛОЖЕНИЕ А " Bвeди номер помещения :" )) телепорта очутился назад . игры в недопустимом для него месте , переместим
if room_map[player_y] [player_x] not in items_player_may_stand_on \ or hazard_map[player_y] [player_x] != О: player_x = old_player_x player_y = old_player_y player_f rame = О if room_map[player_y] [player_x] deplete_energy(l) 48: # ядовитая жижа " right " and player_ frame > О: = -1 + (0.25 * player_frame) == " left " and player_frame > О: = 1 - (0.25 * player_frame) == " up " and player_ frame > О: = 1 - (0.25 * player_ frame) == " down " and player_frame > О: = -1 + (0.25 * player_frame) if player_direction player_offset_x if player_direction player_offset_x if player_ direction player_ offset_ y if player_direction player_offset_y ############### ## ЭКРАН ## ############### def draw_image (image, screen. Ыit ( image, (top_left_x + top_left_y + у, (х х): * TILE_SIZE), (у* TILE_SIZE) - image.get_height()) def draw_shadow (image, у, х): screen.Ыit ( image, (top_left_x + (х * TILE_SIZE) , top_left_y + (у* TILE_SIZE)) def draw_player (): player_image = PLAYER[player_direction] [player_frame] draw_image(player_image, player_y + player_offset_y, player_x + player_offset_x) player_image_shadow = PLAYER_SHADOW[player_direction] [player_frame] draw_shadow(player_image_shadow, player_y + player_offset_y, player_x + player_offset_x) def draw (): if game_over: return # Очистка игровой арены . = Rect ( (0, 150), (800, 600)) screen.draw.filled_rect(box, RED) Ьох = Rect ( (0, 0), (800, top_left_y + (room_height - Ьох 1) *30)) полный листинr иrРы « ПО&Еr » 333
screen.surface . set_clip(box) f l oor_type = get_floor_ type() for у in range (room_height) : # Кладем на пол сначала плитку , # затем предметы на пол . for х in range (room_width) : draw_ image(objects [ floor_ type ] [ 0] , у , х) # Следующая строка кода позволяет теням падать # на предметы на полу if room_map[y] [х] in items_player_may_stand_on : draw_image(ob j ec t s[room_map[y] [х ] ] [О] , у , х) # Нажимная платформа в комнате 2 6 , нужна для размещения # реквизита . if current_room == 26 : draw_image (objects [39 ] [0 ], 8 , 2) image_on_pad = room_map[8] [2] if image_on_pad > О : draw_image(objects [ image_on_pad] [0] , 8 , 2) for in range (room_height) : for х in range (room_w i dth) item_here = room_map [y] [х] # Игрок не допускается в 255 : это позиция для широких # объектов . if item_here not in items_player_may_stand_on + [255] : image = objects[ i tem_here] [ 0] if (current_room in outdoor_rooms and у == room_height - 1 and room_ map[y] [х] == 1 ) or \ (current _ room not in outdoor_ rooms and у == room_height - 1 and room_map[y] [х] == 1 and х > О and х < room_width - 1): # Прозрачность изображения стены в первой строке . image = P I LLARS[wal l _transparency_frame] у draw_image(image , у, х) if objects [ item_here] [ 1 ] is not None : # # у объекта есть тень shadow_image = objects [ item_here] [1] # если тени понадобится Если горизонтальная « выкладка » if shadow_image in [ images.half_shadow , images.full_shadow]: shadow_width int (image . get_width() / TILE_SIZE) # Используем тень по ширине объекта . for z in range (O , shadow_width) : draw_shadow(shadow_image , у , x+z) else : 334 ПРИЛОЖЕНИЕ А
draw_shadow(shadow_image, hazard_here = hazard_map[y] if hazard_here != О: # Если у, х) [х] есть опасность в этой ПОЗИЦИИ draw_image(objects[hazard_here] [О], у, х) if (player_y == у) : draw_player () screen.surface.set_clip( None ) def adjust_wall_transparency () : global wall_transparency_frame if (player_y == room_height - 2 and room_map[room_height - 1] [player_x] == 1 and wall_transparency_frame < 4): wall_transparency_frame += 1 # Исчезновение стены . if ((player_y < room_height - 2 or room_map[room_height - 1] [player_x] != 1) and wall_transparency_frame > 0): wall_transparency_frame -= 1 # Появление стены . def show_text (text_to_show, line_number): if game_over: return text_lines = [15, 50] Ьох = Rect((O, text_lines[line_number]), (800, 35)) screen.draw.filled_rect(box, BLACK) screen.draw.text(text_to_show, (20, text_lines[line_number]), color=GREEN) ############### ## РЕКВИЗИТ ## ############### # Реквизит - объекты, которые можно перемещать между комнатами, они # могут #Любой появляться реквизит или должен исчезать. настраиваться здесь. Реквизит вне игры # отправляется в помещение О. # номер объекта : [помещение, у, х] props = { 20: [31, о, 4], 21: [26, о, 1], 22: [41, о, 2], 23: [39, о, 5], 24: [45, О, 2]' 25: [32, О, 2]' 26: [27' 12, 5]' # две стороны одной двери 40: [0, 8, 6]' 53: [ 45, 1, 5], 54: [0, О, о]' 55: [О, О, 0], 56: [О, о, О], 57: [35, 4' 6]' 58: [О, о, о]' 59: [31, 1, 7], 60: [О, о, О]' 61: [36, 1, 1]' 62: [36, 1, 6]' 63: [о, о, О], 64: [27' 8, 3]' 65: [50, 1, 7], 66: [39, 5, 6]' 67: [ 4 6, 1, 1]' 68: [О, О, О]' 69: [30, 3, 3], 70: [ 4 7, 1, 3]' полный листинr иrРы « ПО&Еr » 335
71 : 74 : 78 : [О , LANDER_Y , LANDER_X ] , 7 2 : [ О , О , О] , 7 3 : [ 2 7 , 4 , 6] , [28, 1 , 11] , 75 : [О , О , 0] , 76 : [4 1, 3, 5 ], 77 : [0, О , 0] , [35, 9, 1 1], 79 : [26 , 3 , 2] , 80 : [ 41 , 7 , 5] , 8 1: [29 , 1 , 1] checksum = О for key , prop in props . items() : if key != 71 : # номер 71 пропускаем, потому что в каждой игре # разный. checksum += (prop[O] * key + prop [ l ] * (key + 1) + prop[2 ] * (key + 2)) print (len (props) , " (количество реквизита) " ) assert len (props) == 37 , " Ожидается 37 предметов реквизита " рrint ( " Контрольная сумма реквизита :" , checksum) assert checksum == 61414 , " Ошибка в данных реквизита " in_my_pockets [55] se l ected_item о # первый элемент item_carrying in_my_pockets[selected_item] RECIPES [62 , [59 , [88 , = [ 35, 63 ], 54 , 60 ], 58, 89 ], [76 , 28 , 77] , [77 , 55 , 56] , [ 89, 60, 90] , [78 , [ 56 , [67, 38 , 54] , 57 , 58] , 35 , 68] [ 73 , 74 , 75] , [ 71 , 65 , 72], checksum = О check counter = 1 for recipe in RECIPES : checksum += (recipe[O] * check_ cou n ter + recipe[l] * (check_ counter + 1 ) + recipe[2] * (check_counter + 2)) check_counte r += 3 print (len (RECIPES) , " (количество рецептов) " ) assert len (RECIPES) == 11 , " Ожидается 11 рецептов " assert checksum == 37296 , " Ошибка в данных рецептов " рrint ( " Контрольная сумма рецептов :" , checksum) ################################# ## ВЗАИМОДЕЙСТВИЕ С РЕКВИЗИТОМ ## ################################# def find_ o bject_start_x () : checker_x = player_x while room_map[player_y ] [checker_x] checker_x - = 1 return checker_x 255 : def get_item_under_player () : item_x = find_object_start_x( ) item_player_is_on = room_map[player_y] [item_x ] return item_ player_ is_ on 336 ПРИЛОЖЕНИЕ А он
def pick_up_object (): global roorn_rnap # Получение номера объекта в месте нахождения игрока . itern_player_is_on = get_itern_under_player() if itern_player_is_on in iterns_player_rnay_carry: # Освобождение пространства на полу . roorn_map[player_y] [player_x] = get_floor_type() add_object(itern_player_is_on) show_text ( " Ты взял : " + objects [itern_player_is_on] [3], sounds.pickup.play() tirne.sleep(0.5) else : show_text ( ·· ты не можешь взять это! " , О) 0) def add_object (itern): #Добавление предмета в инвентарь . global selected_itern, itern_carrying in_rny_pockets.append(itern) itern_carrying = itern # Вычитаем единицу , потому что индексирование начинается с О . selected_itern = len (in_rny_pockets) - 1 display_inventory() props[item] [О] =О #Перенесенные предметы попадают в комнату # (за пределами карты) О def display_inventory (): Ьох = Rect ( (0, 45), (800, 105)) screen.draw.filled_rect(box, BLACK) if len (in_rny_pockets) return == О: start_display = (selected_itern // 16) * 16 list_to_show = in_rny_pockets[start_display: start_display + 16] selected_marker = selected_itern% 16 for itern_counter in range ( len (list_to_show)) itern_nurnber = list_to_show[itern_counter] irnage = objects[itern_nurnber] [О] screen.Ыit (irnage, (25 + (46 * itern_counter), 90)) box_left = (selected_rnarker * 46) - 3 = Rect ( (22 + box_left, 85), (40, 40)) screen.draw.rect(box, WHITE) itern_highlighted = in_rny_pockets[selecte d_itern] description = objects[itern_highlighted] [2] screen.draw.text (description, (20, 130), color= " white " ) Ьох def drop_object (old_y, old_x): global roorn_rnap, props if roorn_rnap[old_y] [old_x] in # предметы props[itern_car rying] [О] [О, 2, 39]: # куда можно положить current roorn полный nистинr иrРы « ПО&Еr » 337
props[item_carrying ] [1 ] = old_ y props[item_carrying] [2 ] = old_ x room_map[o l d _ y] [old_ x ] = item_carrying show_text( " Tы положил " + objects[item_carrying] [3 ], 0) sounds . drop . play() remove_ object(item_carrying) time.sleep(0.5) else : # Этот код выполняется только в том случае, если здесь # есть реквизит show_text( " Tы не можешь положить это тут ." , 0) time.sleep(0.5) уже def remove_object (item) : #Извлечение предмета из инвентаря global selected_item, in_my_pockets , i tem_carrying in_my_pockets.remove(item) se l ected_item = selected_item - 1 if selected_item < О : selected_item = О if len (in_my_pockets) == О : # Если инвентарь пуст item_carrying = False # Присваиваем переменной item_carrying # значение False else : # В противном случае присваиваем выбранный предмет item_carrying = in_my_pockets[selected_item ] display_inventory() def examine_object () : item_player_is_on = get_item_ under_player() left_tile_of item = find_object_start_x() if item_player_is_on in [О , 2]: # не учитываем пол return description = " Ты видишь : " + objects [ item_ p l ayer_ is_ on] [ 2] for prop_ number , details in props . items() : # реквизит = номер объекта : [номер помещения , у , х] if details[OJ == current_ room: #если реквизит в помещении # Если реквизит скрыт {= в позиции игрока, # но не на карте) if (details[l] == player_y and details [ 2 ] == left_tile_of_item and room_map[details[l] ] [details [ 2] ] != prop_ number) add_object(prop_number) description = " Ты нашел " + ob j ects [prop_number] [3] sounds.combine.play() show_text(description , 0) t i me.sleep(0 . 5) #################################### ## ВЗАИМОДЕЙСТВИЕ С ОБЪЕКТАМИ ## #################################### def use_object () : global room_ map , props, i tem_ carrying , air , selected_i tem , energy 338 ПРИЛОЖЕНИЕ А
global in_rny_pockets, suit_stitched, air_fixed, garne_over use_rnessage = " Ты попытался что-то сделать, но ничего не вьШJЛо ." standard_responses = { 4 : " Воздух на исходе! Поторопись !" , 6 : " Не время сидеть сложа руки !" , 7 : " Не время сидеть сложа руки !" , 32 : " Все затряслось и загрохотало , но ничего не произошло ." , 34 : " Ага! Так лучше. А теперь вымой руки ." , 35 : " Ты моешь руки и стряхиваешь воду ." , 37 : " Из пробирок идет дымок , когда ты их трясешь. " , 54 : " Ты жуешь жвачку. Она липкая , как клей .", 55 : " Йо-йо подпрыгивает вверх и вниз , немного медленнее , \ чем 56 : с 59 : на " Это Земле " , слишком неудобно . Можешь ли ты соединить ее \ чем-нибудь? " , " Прежде чем использовать емкость , необходимо устранить \ течь " , 6 1: 62 : 67 : " Ты подаешь " Не выбрасывай " Чтобы # светом , ничего . насладиться добавь 75 : сигнал А но вдруг вкусной никто его не пригодится ... " космической видит ." , , едой, просто \ воды !" , " Ты здесь : " + str (current_roorn) + " 11 Х : " + str (player_x) + " 11 У : " + str (player_y) Получение номера объекта в месте нахождения игрока . itern_player_is_on = get_itern_under_player() for this_itern in [itern_player_is_on , itern_carrying ] if this itern in standard_responses : use_rnessage = standard_responses[this_itern ] if itern_carrying == 70 or itern_player_is_on == 70 : use_rnessage = " Играет музыка !" sounds . steelrnusic.play(2) elif itern_player_is_on == 11: use_rnessage = " ВОЗДУХ : " + str (air) + \ " % / ЭНЕРГИЯ " + str (energy) + " % / " if not suit stitched : use_ rnessage += "! ! ! СКАФАНДР ПОРВАН !!! / " if not air_fixed : use_rnessage += "! ! ! НЕТ БАЛЛОНА С ВОЗДУХОМ !!!" if suit stitched and air_fixed: use_rnessage += " СКАФАНДР В ПОРЯДКЕ " show_text(use_rnessage , 0) sounds . say_status_report . play() tirne.sleep(0 . 5) # # # # Если компьютер " включен " , игрок явно намерен обновить статус . Команда объекта , ret ur n , который чтобы прекратить случайно использование переопределил другого этот . полный nистинr иrРы <<ПО&Еr» 339
return elif item_carrying 60 or item_player_is_on == 60: use_message = " Ты вставил " + objects [ 60] [ 3] + " в скафандр " air_fixed = True air = 90 air_countdown () remove_object(60) elif (item_carrying == 58 or item_player_is_on == 58) \ and not suit stitched: use_message = " Ты использовал " + objects[56][3] + \ " для ремонта ткани скафандра " suit_stitched = True remove_object(58) elif item_carrying == 72 or item_player_is_on == 72: use_message = " Ты запросил помощь по радио . Спасательная \ шлюпка прибывает . Отправляйся в сектор 13 , \ снаружи. " props [ 4 О] [О] = 13 elif (item_carrying == 66 or item_player_is_on 66) and current room in outdoor rooms: use_message = " Ты ищешь ... " if (current_room LANDER_SECTOR and player_x == LANDER_X and player_y == LANDER_Y) : add_object(71) use_message = " Ты нашел посадочный модуль! " elif item_player_is_on == 40: clock.unschedule(air_countdown) shоw_tехt( " Поздравляем тебя , "+ show_text( " Mиccия пройдена PLAYER_NAМE успешно ! Ты в \ + "!" , 0) безопасности ." , 1) game_over = True sounds.take_off .play() game_completion_sequence() elif item_player_is_on == 16: energy += 1 if energy > 100: energy = 100 use_message = " Ты жуешь петрушку немного и восстанавливаешь \ энергии " draw_energy_air() elif item_player_is_on == 42: if current room == 27: open_door(26) props[25] [О] = О #Дверь из 340 ПРИЛОЖЕНИЕ А помещения 32 в инженерный отсек
props[26] [0] =О #Дверь внутри инженерного отсека clock.schedule_unique(shut_engineering_door, 60) use_message = " Ты нажимаешь кнопку " show_text( " Двepь в инженерный отсек открыпась на 60 секунд " , 1) sounds.say_doors_open.play() sounds.doors.play() elif item_carrying == 68 or item_player_is_on == 68: energy = 100 use_message = " Ты ешь , чтобы восстановить энергию " remove_object(68) draw_energy_air() if suit_stitched and air_fixed: # доступ в шлюз if current_room == 31 and props[20] [0] == 31: open_door(20) # включая удаление двери sounds.say_airlock_open.play() shоw_tехt( " Компьютер сообщает , что elif props [20] [0] == 31: props[20] [0] =О #удаление двери sounds.say_airlock_open.play() shоw_tехt( " Компьютер сообщает , что шлюз с открыт ." , 1) карты шлюз открыт ." , 1) for recipe in RECIPES: ingredientl recipe[O] ingredient2 = recipe[l] comЬination = recipe[2] if (item_carrying == ingredientl and item_p layer_is_on == ingredient2) \ or (item_carrying == ingredient2 and item_player_is_on == ingredientl) use_message = " Ты соединяешь " + objects [ingredientl] [3] \ + " и " + objects [ ingredient2] [ 3] \ + ", чтобы получилось : " + objects [ comЬination] [ 3] if item_player_is_on in props.keys(): props[item_player_is_on] [0] =О room_map[player_y] [player_x] = get_floor_type() in_my_poc kets.remove(item_carrying) add_object(combination) sounds.combine.play() # {номер объекта ключа : номер объекта двери} ACCESS_DICTIONARY = {79:22, 80:23, 81:24} if item_carrying in ACCESS_DICTIONARY: door_numЬer = ACCESS_DICTIONARY[item_carrying] if props[door_number] [О] == current_room: use_message = " Ты разблокировал дверь !" sounds.say_doors_open.play() sounds.doors.play() open_door(door_numЬer) ПОЛНЫЙ листинr ИfРЫ « ПО&Еf )) 341
show_text(use_message, 0) time.sleep(0.5) def game_ completion_sequence () global launch_frame # (начальное значение О , # в разделе ПЕРЕМЕННЫЕ) Ьох = Rect ( (0, 150), (800, 600)) screen.draw.filled_rect(box, (128, О, 0)) Ьох = Rect ( (0, top_left_y - 30), (800, 390)) screen.surface.set_clip(box) for in range (0, 13): for х in range (O, 13) draw_image(images.soil, настраивается у у, х) launch_frame += 1 if launch_frame < 9: draw_image(images.rescue_ship, 8 - launch_frame, 6) draw_shadow(images.rescue_ship_shadow, 8 + launch_frame, 6) clock.schedule(game_completion_sequence, 0.25) else : screen.surface.set_clip( ~one ) (200, 380), color = " white " , fontsize = 89, shadow = (1, 1), scolor = " Ьlack " ) screen.draw.text( " ЗABEPШEHA " , (145, 480), color = " white " , fontsize = 89, shadow = (1, 1), scolor = " Ыасk " ) sounds.completion.play() sounds.say_mission_complete.play() screen.draw.text( " MИCCИЯ " , ############### ## ДВЕРИ ## ############### def open_door (opening_door_number) global door_frarnes, door_shadow_frames global door_frame_number, door_object_number door_frames = [images.doorl, images.door2, images.door3, images.door4, images.floor] # (Последний кадр содержит тень , готовую к появлению двери) door_shadow_frames [images.doorl_shadow, images.door2_shadow, images.door3_shadow, images.door4_shadow, images.door_shadow] door_frarne_nurnber = О door_object_number = opening_door_number do_door_animation() def close_door (closing_door_numЬer) global door_frarnes, door_shadow_frames global door_frarne_number, door_object_nurnber, player_y door_frames = [images.door4, images.door3, images.door2, images.doorl, images.door] door shadow_frames = [images.door4_shadow, images.door3_shadow, 342 ПРИЛОЖЕНИЕ А
images . door2_shadow , images.door_shadow] images . doorl_shadow , door_frame_numЬer = О door_object_number = closing_door_number # Если игрок находится в одном ряду с дверью, он должен # в открытом дверном проеме if player_y == props[door_object_number] [1] : if player_y == О : # если в верхнем дверном проеме player_y 1 # перемещаем вниз else : player_y = room_height - 2 # перемещаем вверх do_ door_ animat i on() быть def do_door_animation () global door_frames , door_frame_numЬer, door_object_numЬer , objects ob j ects[door_object_number ] [О ] = door_frames[door_frame_number ] objects[door_object_numЬer ] [ 1] = \ door_shadow_ frames[door_ frame _numЬer] door frame numЬer += 1 if door_frame_numЬer == 5 : if door_ frames[-1] == i mages.floor : props[door_object_numЬer] [0] = О #удаляем дверь # из списка реквизита восстанавливаем карту помещения # из реквизита , если необходимо использовать дверь # в помещение . generate_map ( ) else : cloc k .schedule(do_door_animation , 0 . 15) def shut_engineering_door () : global current_room, door_room_number, props props [ 25] [ 0 ] = 32 #Дверь из помещения 32 в инженерный отсек. props [ 26] [ О ] = 27 #Дверь внутри инженерного отсека. generate_map() # Добавляем дверь в room_map, если она находится # в посещенной комнате . if current room == 27 : c l ose_door(26) if current room == 32 : close_door(25) shоw_tехt( " Компьютер сообщает , что двери закрыты. " , 1) sounds . say_ doors_ closed.play() def door_in_room_26 ( ): global airlock_door_frame , room_map frames = [images . door , images . doorl , images.door2 , images . door3 , images . door4 , images . floor shadow_f rames [ i mages . door_shadow , images.doorl_shadow , i mages . door2 _ shadow , images . door3_ shadow , i mages.door4_shadow , None ] if current room != 26 : ПОЛНЫЙ nистинr ИfРЫ « ПО&Еf )) 343
clock.unschedule(door_in_room_26) return # реквизит 21 - это дверь в помещении 26 . if ((player_y == 8 and player_x == 2) or props[63] 2]) [26, 8, \ and props[21] [0 ] == 26: airlock door frame += 1 if airlock_door_frame == 5: props[21] [0] = О #Удаляем # открытия. room_map[O] [1] О room_map[O] [2] О room_map[O] [3] О дверь с карты после полного if ((player_y != 8 or player_x != 2) and props[63] != [26, 8, 2]) \ and airlock_door_frame > О: if airlock_door frame == 5: # Добавляем дверь в реквизит и на карту , чтобы отобразить # анимацию . props [21] [0] = 26 room_map[O] [1] 21 room_map[O] [2] = 255 room_map[O] [3] = 255 airlock door frame -= 1 objects[21] [0] objects[21] [1] frames[airlock_door_frame] shadow_frames[airlock_door_frame] # ##### ######## ## ## ИНДИКАТОРЫ ## # ## ## ###### ##### def draw_energy_air (): Ь ох = Re с t ( ( 2 О , 7 65 ) , (3 5 О , 2 О ) ) screen.draw.filled_rect(box, BLACK) screen.draw.text( " BOЗДYX " , (20, 766), color=BLUE) screen.draw.text( " ЭHEPГИЯ " , (360, 766), color=YELLOW) if air > О: = Rect ( (100, 765), (air, 20)) screen.draw.filled_rect(box, BLUE) Ьох #Создаем новый индикатор # запаса воздуха . if energy > О: Ьох = Rect ( (250, 765), (energy, 20)) screen.draw.filled_rect(box, YELLOW) # индикатор уровня энергии . def end_the_game (reason): global game_over 344 ПРИЛОЖЕНИЕ А #Создаем новый
show_text(reason, 1) game_over = True sounds.say_mission_fail.play() sounds.gameover.play() ОКОНЧЕНА " , (120, 400), color = " white " , fontsize = 89, shadow = (1, 1), scolor = "Ыасk " screen.draw.text( ' ИГPA def air_countdown () global air, game_over if game_over: return # Не трать воздух, air -= 1 if air == 20: sounds.say_ air_low.play() if air == 10: sounds.say_act_now.play() draw_energy_air() if air < 1: end_the_game( " Boздyx это ) бесполезно . закончился! " ) def alarm (): show_text( " Boздyx + "! на Доберись исходе, в " + безопасное PLAYER_NAМE место и \ вызови помощь\ " , 1) sounds.alarm.play(3) sounds.say_breach.play() по радио! ############### ## ОПАСНОСТИ ## ############### hazard_data = { # номер помещения : [[у, х , направление , отклонение]] 28: [ [ 1, 8, 2, 1], [7, 3, 4, 1]], 32: [ [ 1, 5, 4, -1]], 34: [ [5, 1, 1, 1], [5, 5, 1, 2]], 35: [ [ 4, 4, 1, 2], [2, 5, 2, 2]], 36: [ [2, 1, 2, 2]], 38: [ [ 1, 4, 3, 2], [5, 8, 1, 2]], 40: [ [3, 1, 3, -1], [ 6, 5, 2, 2], [ 7, 5, 4, 2]], 41: [ [ 4, 5, 2, 2], [ 6, 3, 4, 2], [ 8, 1, 2, 2]], 42: [ [2, 1, 2, 2], [ 4, 3, 2, 2], [ 6, 5, 2, 2]], 46: [ [2, 1, 2, 2]], 4 8: [ [ 1, 8, 3, 2], [ 8, 8, 1, 2], [3, 9, 3, 2]] def deplete_ energy (penalty): global energy, game_ over if game_over: return # Не трать энергию , energy = energy - penalty draw_energy_air() if energy < 1: end_the_game( " Энepгия это бесполезно . закончилась! " ) ПОЛНЫЙ листинr ИfРЫ « ПО&Еf » 345
def hazard_start () : global current_room_hazards_list, hazard_map if cur r ent_room in hazard_ data . keys() : current _ room_ hazards_li st = hazard_ data[current_ room] for hazard in current_room_hazards list : hazard_y = hazard [ O] hazard_x = hazard [ l] hazard_map[hazard_y] [hazard_x ] = 49 + (current_room% 3) clock . schedule_ interva l (hazard_ move , 0 . 15) def hazard_rnove () : global current_roorn_haza r ds_list, hazard_data , hazard_rnap global old_player_x , old_player_y if garne_ over : return for hazard in current_room_hazards list : hazard_y = hazard[O ] hazard_x = hazard[l ] hazard_direction = hazard[2] old_hazard_x = hazard_x old_hazard_y = hazard_y hazard_map[old_hazard_y] [old_hazard_x] if h azard- direction hazard_у -= 1 if h azard- direction hazard- х += 1 i f hazard- direction hazard_ у += 1 if h azard- direction hazard- х -= 1 1: # О вверх 2: # вправо 3: # вниз 4: # влево hazard_should_bounce = False if # (hazard_ y == player_ y and hazard_ x == player_ x ) or \ (hazard_ y == from_ player_ y and hazard_ x == from_ player_x and p l aye r_ frame > 0) : sounds . ouch . p l ay() deplete_energy(lO ) hazard_should_bounce = True Остановка опасности после выхода if h azard_x == r oom_width: hazard_should_bounce = True hazard_x = room_width - 1 if hazard_x == -1 : hazard_should_bounce = True hazard_x = О if hazard_y == room_height : hazar d_should_bounce = True 346 ПРИЛОЖЕНИЕ А через дверь
hazard_y = room_height - 1 if hazard_y == -1: hazard_should_bounce True hazard_y = О # Остановка , если опасность столкнется с декорацией или другой # опасностью. if room_map[hazard_y] [hazard_x] not in items_player_may_stand_on \ or hazard_map[hazard_y] [hazard_x] != О: hazard_should_bounce = True if hazard_should_bounce: hazard_y = old_hazard_y # # Переход к последней допустимой позиции . hazard_x = old_hazard_x hazard_direction += hazard[3] if hazard_direction > 4: hazard_direction -= 4 if hazard_direction < 1: hazard_direction += 4 hazard[2] = hazard_direction hazard_map[hazard_y] [hazard_x] hazard[O] hazard_y hazard_x hazard[l] 49 + (current_room % 3) ############### ## СТАРТ ## ############### generate_map() clock.schedule_interval(game_loop, 0.03) clock.schedule_interval(adjust_wall_transparency, 0.05) clock.schedule_unique(display_inventory, 1) clock.schedule_unique(draw_energy_air, 0.5) clock.schedule_unique(alarm, 10) # Чем больше значение , тем дольше длится игра . clock.schedule_interval(air_countdown, 5) sounds.mission.play() # Вступительная музыка

ТА&ЛИЦА С ПЕРЕМЕННЫМИ, СПИСКАМИ И СЛОВАРllМИ Чтобы тебе было проще разобраться в листинге игры «Побег», здесь при­ ведена таблица, в которой содержатся важные переменные, списки и словари. Все эти штуки использовались при создании игры. В данную таблицу включено то, что, по моему мнению, наверняка пригодится тебе при работе над созданием игры. Кроме того, ты можешь воспользоваться предмет­ ным указателем книги, чтобы найти ссылки на некоторые термины. Если имя переменной, списка или словаря написано с прописной буквы, э то означает, что их содержимое нельзя изменить после того, как ты их настроишь. ТА&ЛИЦА С ПЕРЕМЕННЫМИ, СПИСКАМИ И СЛОВАРSIМИ 349
Переменная, список Описание иnисnоварь ACCESS DICTIONARY Словарь, который связывает карты доступа с дверьми. См. раздел «Настраиваем доступ в помещения» (глава 11) air Хранит уровень воздуха игрока. Присвой ей начальное значение в разделе ПЕРЕМЕННЫЕ air fi xed Должна быть равно True, если игрок устано­ вил на скафандр баллон с воздухом. В против­ ном случае значение должно быть checksum False Используется, чтобы проверить, верно ли были введены в код игры данные. Если ты изме- нишь код игры, тебе потребуется изменить или отключить проверку контрольной суммы. Поставь # перед инструкциями assert, чтобы за комментировать их current room Обозначает номер помещения, в котором находится игрок. В разделе ПЕРЕМЕННЫЕ установи значение, соответствующее номеру комнаты , из которой игрок начинает свой путь energy Хранит уровень энергии (здоровья) игрока. Установи ее начальное значение в разделе ПЕРЕМЕННЫЕ FRIENDl NAME Имя друга, используемое при описании поме­ щений и предметов FRIEND2 NAME Имя второго друга, используемое при описа­ нии помещений и предметов. GAME МАР Хранит карту с информацией о том, как помещения соединены друг с другом. См. раз­ дел «Создание картографических данных» (глава 4) game _ over Установи значение True, когда игра закон­ чится. В противном случае значение должно быть hazard data False Словарь, содержащий информацию о место­ положении и перемещении движущихся опас­ ностей. См. раздел «Добавление подвижных опасностей» (глава 12) hazard _ map Используется для отслеживания положения движущихся опасностей в той комнате, где сейчас находится игрок. Генерируется автома­ тически. Не вноси сюда какие-либо изменения 350 ПРИЛОЖЕНИЕ &
Описание Переменная, список иnисnоварь HEIGHT Высота игрового окна в пикселах iп _ту_ pockets Список номеров объектов для предметов, которые несет игрок. Настрой раздел РЕКВИЗИТ так, чтобы в нем содержались только те предметы, с которыми игрок начи­ нает игру iteт _ carryiпg Номер объекта, выбранного игроком в инвентаре iteт _ player _ is iteтs _ p l ayer _оп _тау_ Номер объекта, на котором стоит игрок carry Список, содержащий номера объектов, кото­ рые игрок может подобрать iteтs _ player staпd _тау_ оп LANDER Список, содержащий номера объектов, по которым игрок может ходить SECTOR Номер «помещения», в котором спрятан поса­ дочный модуль LANDER Х Координата по оси х места, где спрятан поса­ дочный модуль LANDER У Координата по оси у места, где спрятан поса­ дочный модуль МАР HE I GHT МАР WIDTH Сколько помещений расположено на карте в длину (см. главу 4, рис. 4.1) Сколько помещений расположено на карте в ширину (см. главу 4, рис. 4.1) objects Словарь, содержащий изображения и описа­ ния всех объектов игры. См. раздел «Словарь объектов космической станции» (глава 5) outdoor rooтs Диапазон номеров «помещений», располо­ женных на поверхности планеты (см. главу 4, рис. 4.1) PILLARS Словарь, содержащий кадры анимации «зату­ хания» передней стены PLAYER player _ Словарь, содержащий кадры анимации игрока direc t ioп Направление движения игрового персонажа. Значения могут быть следующими: right , up p l ayer _ frame l eft, или dow п Используется для отображения кадра анима­ ции игрока ТА&ЛИЦА С ПЕРЕМЕННЫМИ, СПИСКАМИ И СЛОВАРSIМИ 351
Переменная, список Описание иnисnоварь PLAYER NAME Используется при описании объектов и в сооб­ щениях для игрока. Измени свое имя в раз­ деле ПЕРЕМЕННЫЕ PLAYER SHADOW Словарь, содержащий тени для анимации игрока player _ х Положение игрока по оси х в помещении, измеряемое в плитках. Установи его исходную позицию в разделе ПЕРЕМЕННЫЕ player _у Положение игрока по оси у в помещении, измеряемое в плитках. Установи его исходную позицию в разделе ПЕРЕМЕННЫЕ props Словарь, содержащий местоположения всех подвижных объектов в игре. См. раздел «Добавление кода для реквизита» (глава 9) REC I PES Список, содержащий способы комбинации объектов для создания новых предметов. См. раздел «Комбинирование объектов» (глава 1О) room _ map Используется для запоминания объектов, находящихся в каждой позиции в помещении, где находится игрок . Генерируется автомати­ чески. Не вноси сюда какие-либо изменения scenery Словарь, содержащий данные для размеще­ ния неподвижных объектов в помещениях. См. раздел «Изучение словаря данных для декораций» (глава 6) standard _ responses Словарь сообщений, отображаемых при использовании игроком предметов, которые нужны для какой-то единственной цели suit st i tched Если игрок починил костюм, установи значе­ ние use _ message True . Иначе False Текст, которой видит игрок, когда он исполь­ зует или пытается использовать объект WID TH Ширина игрового окна в пикселах
ОТЯАДКА КОДА Некоторые из листингов, приведен­ ных в этой книге, могут не рабо­ тать с первого раза. Не пугайся! Это обычное дело даже для опытных программистов. Очень легко упустить детали, которые потом окажутся невероятно значи­ мыми для программы. Исправление ошибок в программе называется отладкой. Чтобы максимально избежать проблем, я постарался сде­ лать каж д ый листинг как можно короче , поэтому, если что-то не сработает, тебе не придется проверять бесчислен­ ное множество инструкций. Кроме того, если на что-то тебе стоит обратить пристальное внимание, например на что-то очень сложное, прочти комментарии, которые я сделал для тебя. ОТЛАДКА КОДА 353
ПоJ1.-1ни, что, если ты не знаешь, как исправить програм­ му, всегда можно воспользоваться моей версией листинга, который: ты скачал(а) в качестве примеров для книги (см. раздел « Содержимое ZIР-архива » во введении). Если ты из­ менил(а) программу, попробуй скопировать и вставить фрагменты моей версии кода в свою програм]\Iу. В это приложение я включил несколько советов, которые помогут тебе исправить любого вида программы, если они по каким-то причинам не работают. Когда Python обнару­ живает ошибку, он обычно указывает на строку в програм­ ме, в которой: что-то пошло не так. Ошибка не всегда будет начинаться именно с этого места: это означает лишь, как да­ леко продвинулся интерпретатор Python, прежде чем столк­ нулся с очень серьезной: проблемой. Если выделенная стро­ ка выглядит нормально, сначала проверь предыдущую строку, а затем остальные добавленные в код инструкции. РАССТАНОВКА ОТСТУПОВ Отступы используются для указания Python на то, как фрагменты кода программы соотносятся друг с другом. Например , все инструкции, принадлежащие функции, дол­ жны иметь одинаковый: отступ и находиться ровно под инструкцией: def, определяющей: эту функцию. Инструк­ ции, относящиеся к командам while, for, if и else, также должны иметь отступ. В листинге В-1 приведен пример функции - get _ floor _ type (). ГJ О def get_ floor _type () : б i f curre n t r oom in o u t do o r rooms: €) return 2 # грунт о else : 0 return о # кафельный пол --пропуск-- Л истинг В-1. Фрагмент кода, демонстрирующий различ н ые уровни отступов Все инструкции относятся к функции type () get _ floor _ О , поэтому перед каждой строкой: есть отступ как минимум в четыре пробела (см. б и О ). Инструкции return 354 ПРИЛОЖЕНИЕ В ( С) и 0) относятся не только к функции,
но и к кш1,1андам if б и else О , поэтому они отделены до­ полнительными четырьмя пробелами, что в общей сложно­ сти составляет восемь пробелов. Когда ты указываешь двое­ точие в конце строки при вводе инструкций def, if и else, то отступ в следующей строке в ски. Нажимай клавишу I DLE вводится автоматиче­ Del, чтобы удалить ненужный тебе отступ. Если ты неправильно определил(а) отступ для некоторых инструкций, програr..1ма может вытворять странные вещи или работать медленнее, даже если Python не говорит о каких-либо ошибках. Тщательно проверяй длину отступов. Если Python выдает ошибку, в которой сообщается, что перед фрагментом кода должен стоять отступ, это означает, что ты не поставил отступ там, где это надо было сделать. Если Python сообщает, что в том или ином месте стоит не­ правильный отступ, это означает, что ты поставил(а) слиш­ ком много пробелов перед началом инструкции, или же от­ ступы одних и тех же инструкций стоят на разных уровнях. В таком случае правильно выровняй строки кода. В этой книге я использую четыре пробела для обозначения каждо­ го уровня отступа. ЧУВСТВИТЕЯЬНОСТЬ К РЕfИСТРУ Python чувствителен к регистру. Это означает, что .между прописными (АБС ... ) и строчными (аЬс ... ) буквами есть огромная разница. В большинстве случаев при написании кода на Python следует использовать строчные буквы. Но есть и исключения. • В начале значений True , False и None буква. Если ты введешь их правильно, в стоит прописная I DLE они будут отображаться оранжевым цветом. • Некоторые имена переменных, словарей и списков в про­ г рамме написаны прописными буквами, например: TILE _ SIZE и PLAYER. Если ты будешь вводить прописные буквы непоследовательно, то может высветиться сообщение об о ш ибке, в котором говорится, что какое-то неизвестное имя. Python Python встретил не поймет, что два имени, ОТЛАДКА КОДА 355
написанных прописными буквами в разных местах, являют­ ся одним и тем же именеr'л переменной, например и • Player playeR. (Также проверь опечатки в ~н.~енах.) Все, что заключено в кавычки , может быть набрано в разном регистре. Это просто текст, который программа использует для той или иной цели, п оэтоJ\1у его часто пишут так, чтобы он был понятен другим людям. • Py thon игнорирует все, что находится в строке после сим­ вола #, поэтому после него ты можешь вводить любые слова, которые только пожелаешь. КРУfЯЫЕ, КВАДРАТНЫЕ И ФИfУРНЫЕ СКО&КИ Убедись, что ты ставишь скобки правильной форJ\<tЫ и в пра­ вильном порядке, особенно если Python сообщает тебе о какой-либо проблеме со списками или словарями. • Круглые скобки () используются при работе с кортежами и для передачи данных функции. Например, в функциях range () , print () и len () используются круглые скобки, так же как и в наших функциях в игре «Побег » , таких как remove _ object () • и draw _ image (). Квадратными скобками [] отмечается начало и конец спис­ ка. Иногда ты можешь столкнуться со с п иском внутри дру­ гого списка, поэтому тебе придется использовать несколько пар таких скобок. • Фигурными скобками {} обозначается начало и конец словаря. ДВОЕТОЧИЯ Если строка кода начинается с def, for, while, if, else в конце необходимо поставить двоеточие - :. или В сло­ варе двоеточиями отделяют ключ от данных. В коде игры « Побег » не используется точка с запято~~1, ;, поэтому, если где-то в своем коде ты увидишь этот знак, замени его на двоеточие. 356 ПРИЛОЖЕНИЕ В
ЗАПЯТЫЕ Элементы в списках или кортежах должны разделяться запятыми. Добавляя новые строки в список, обязательно ставь запятую после последнего элемента перед тем, как добавить новые. Ищи закономерности в данных, которые помогут тебе обнаружить любые ошибки, связанные с запя­ тыми. Haпpиrviep, кажды1:'1 список в словаре recipes props и списке содержит три числа. ИЗО&РАЖЕНИll И ЗВУКИ Если Pytho11 сообщает, что каталог изображений или звуков не найден, убедись, что ты загрузил(а) нужные файлы и сохранил(а) их в правильном месте. См. раздел «Загрузка игровых файлов» во введении и листинг 1-1вглаве1. ОРФОf РАФИll Выделение кода разным цветом в IDLE поможет тебе обна­ ружить орфографические ошибки в инструкциях. Убедись, что цвет кода на твоем экране соответствует цвету кода в книге. Будь вниr-.втельнее при написании имен перемен­ ных и списков: любые ошибки могут привести к остановке программы или разнообразным сбоям.

НАСТРОЙКА КАРТЫ КОСМИЧЕСКОЙ СТАНЦИИ Моя к н ига по программированию на языке Python поможет тебе создать космическую п р и ключе н ческую и г ру. В этой книге объясняется, как работает код, благодаря чему ты сможешь использовать в игре собствен н ые д и зайны карт и голово ­ ломок . Чтобы помочь тебе в создании твоей собственной плани­ ровки космической станции, я подготовил шаблон карты в формате PDF, его можно открыть и распечатать. Ты мо­ жешь отмечать на нем расположение выходов и стен поме ­ щений, а также добавлять заметки, касающиеся планировки помещений и 1\~естонахождения объектов. В процессе игры ты также можешь использовать этот ша­ блон, чтобы создать собственную карту и отметить места, НАСТРОЙКА КАРТЫ КОСМИЧЕСКОЙ СТАНЦИИ 359
в которые тебе, возможно, захочется вернуться. (Инструк­ цию по установке игры и руководство к ней ты найде ш ь во введении.) Чтобы просJ1.ютреть шаблон, найди среди файлов-приме­ ров, прилагаемых к книге, файл rnission_rnap _template.pdf и от­ крой его в программе для просмотра РDF-файлов, например Adobe Reader ( https://get.adobe . coш/ru/reader/) (рис. Г.1). Миссия Pythoп Шаблон карты Распечатай этот шаблон и используй его для планировки своей космической станции . Отслеживай расположение реквизита . опасных объектов . дверей и выходов . Не обращай внимания на масштаб : помещения моrут быть разного размера . Локации с 25 1 по находятся на поверхности планеты , и попасть в них можно через выход в помещении 26. 27 26 1 31 132 + 36 30 29 +++ -- -;)8 -- -зэ -- 40 + -1--lц- --- - 41 45 ---- ---- ---- ---- 44' 47 8 4Э SO Рис. Г.1. Шаблон карты космической станции ты найдешь в ф а йле mission_map_template.pdf Совсем необязательно делать все помещения одинаково­ го разJ1.1ера и квадратно1'::'1 формы. Назначение этого шабло­ на 360 - упростить планировку и сделать ее более легкой ПРИЛОЖЕНИЕ r
по сравнению с игровой картой. Сетка поделена на квадра­ ты 5х5 исключительно для удобства добавления заметок, не путай ее ячейки с плитками. Помещение размером 5х5 плиток оказалось бы слишко!\I маленьким для большинства игровых целей. Обрати внимание на то, что изменение размеров поме ­ щения в итоговом коде игры может привести к возникнове­ нию о ш ибок, п оскольку в результате объекты могут ока­ заться за его стенами. Ты можешь удалить или закомментировать код, относящийся к декорациям и рекви ­ зиту, или вернуться к листингу 4-3, чтобы создать собствен­ ную карту помещений и просмотреть результат с помощью кода в разделе ОБЗОР. СОЗДАНИЕ КРУТОЙ ПЛАНИРОВКИ Нсс!\ютря на относительную компактность космической станции, ты можешь сделать ее более интересной, добавив дополнительные уголки для исследования. Использу1':'1 гене­ ратор лабиринтов для получения разнообразных схем рас­ положения комнат на космической станции. Стену в правом нижнем углу нужно будет перекрыть. Чтобы запустить генератор, найди среди файлов-приме­ ров, прилагаемых к книге, файл Генератор cmaнifuu.l1tml и открой его в браузере (рис. Г.2). + +---+---+---+---+ 1 +---+---+ + 1 +---+-- - +---+ + 1 1 +---+---+---+---+ + + 1 + 1 + +-- -+---+ + +---+---+---+---+---+ 1 Создать новую cxer.1y 1 Рис. Г.2. Ген е ратор к а рты космиче с кой станции находится в файле Генератор станции. h tml НАСТРОЙКА КАРТЫ КОСМИЧЕСКОЙ СТАНЦИИ 361
ИЗМЕНЕНИЕ КОДА КАРТЫ В главе 4 описан процесс создания игровой карты с помо ­ щью списка GAME _МАР, который содержит списки данных для каждого помещения. Для составления карты достаточно указать наличие выхода из помещения вверху или справа. Поскольку все комнаты связаны между собой, программа сама определяет, расположен ли выход в соседних помеще­ ниях слева или внизу. Такой подход обеспечивает согласо­ ванность карты (то есть гарантирует, что дверь не исчезнет после того, как игрок пройдет через дверной проем), а также позволяет печатать меньше кода. Для перепланировки космической станции просто измени данные в списке GAME _МАР из листинга 4-1. Для внесения быстрых изменений обратись к ответу на Учебную миссию №~ 2 в главе 4, где показано, как можно добавить секретный проход в нижней части карты. Затем ты можешь дополнить эти помещения декорациями, как описано в главе 6. РА&ОТА С ПОВЕРХНОСТЬЮ ПЛАНЕТЫ В игре «Побег» «помещения» с 1 по 25 находятся на поверх­ ности планеты, поэтому нумерация помещений космиче­ ской станции начинается с числа щения 26 26. В верхней части поме­ находится выход на поверхность планеты. Я рекомендую сохранить все как есть. Если ты не хочешь использовать эти наружные «помеще­ ния», то можешь просто заблокировать выход к ним. Выход на поверхность планеты находится в помещении 26. Измени код этого помещения в списке GAME _МАР, чтобы убрать расположенный вверху выход . При этом тебе также нужно будет удалить дверь в этой комнате (см . ниже). Ты можешь использовать номера помещений, начиная с 26, и расширить карту вниз, чтобы помещения в игре были расположены ис­ ключительно внутри космической станции. Тогда тебе не нужно будет вносить изменения в код, касающийся по­ верхности планеты. Если ты хочешь добавить собственные карты для «поме­ щений» с 362 ПРИЛОЖЕНИЕ r 1 по 25, удали код, который автоматически
генерирует их (см. листинг 4-1). После этого ты сможешь добавить собственные данные для каждого из этих «помещений» в список МАР. GAME Декорации для первых 25 помещенш':'r тоже генерируются автоматически, как показано в листинге 6-2. Удали или за­ комментируй соответствующий код, если он не имеет отно ­ шения к «помещениям» на поверхности планеты с огражде ­ ния1\н1 и случайно выбранными декорацияrv1и. В разделе КАРТА для описания того, какие п омещения находятся на поверхности планеты, используется инструк­ ция outdoor _ rooms = range (1, 26). Внутренние по ­ мещения имеют пол и стены, в наружных помещениях сте­ ны отсутствуют, а р оль пола играет грунт. Ч тобы изменить тип помещения, тебе нужно изменить код, который создает карту помещений, он находится в ли­ стинге 4-2. Этот код опирается на номера помещений при создании краев изображений комнат (см. раздел листинга, отмеченный @). Р ис. Г.2. Одна из ло каций на поверхности п ланеты НАСТРОЙКА КАРТЫ КОСМИЧЕСКОЙ СТАНЦИИ 363
УДАЯЕНИЕ ДВЕРЕЙ Чтобы избежать проблем, связанных с удалениеJ\·1 дверей, исполь­ зуя любой из листингов игры, приведенных в первых 10 главах. Если ты удалишь выход вверху помещения в финальном коде игры, тебе может потребоваться отдельно удалить соответ­ ствующую ему дверь, как описано в конце главы ние двереi-'r показано на рис. 11.1, 11. Расположе­ ниже я его продублировал. Чтобы удалить дверь из игры, измени запись для этой двери в словаре props так, чтобы первое число было равно О. На­ пример, чтобы удалить дверь, которая является объектом под номером 24, задай О в качестве первого числа в соответствую­ щем списке, как показано в листинге 11 -6. Объекты со значе­ нием О в качестве первого числа в их списке не отображаются в игре (если только позднее это число не будет изменено са­ мой програм1\ю1':'1). Чтобы удалить все двери, измени первое число после открывающей квадратной скобки [на О для каждо­ го из предметов реквизита 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 20, 21, 22, 23, 24, 25 и 26. Рис. Г.З. Карта и г ры с д верьми, они о т мечены красным цветом p r op s 2 0: 2 3: 2 6: [31 , [39, [ 27 , 4] , 2 1: [ 26 , о, 1 ], 22 : [41 , о, 2] , 5] , 2 4: [ О , О , 2 ], 2 5: [3 2 , О, 2], 12 , 5 ], # две стороны одной двери о , О, При настройке игры тебе также может потребоваться удалить код, который автоматически закрывает и открывает двери инженерного отсека и шлюза. См. конец главы 11, чтобы узнать, какой именно код следует закомментировать.
3Af PY3KA КАРТЫ КОСМИЧЕСКОЙ СТАНЦИИ Моя книга - это практическое руко­ водство по созданию компьютерной п р иключенческой и гры. Ты може ш ь сначала поиг р ать в иг р у, а затем прочитать книгу, чтобы понять, как работает про­ г рамма, или сначала создать игру, а затем п оиграть в нее в качес т ве награды. Если тебе нужна помощь или дополнительные рекомен­ дации по планировке космической станцин, Nюжешь вос­ пользоваться готовой картой в формате PDF. Увеличив мас ­ штаб, ты сможешь рассмотреть каждое из помещений. Имей в виду, что на этой карте могут быть спойлеры. Некоторые из этих диза1"::'1нов помещений я не публиковал (даже в кни­ ге), чтобы дать тебе возможность побродить по ним непо ­ средственно в игре. 3ArPY3KA КАРТЫ КОСМИЧЕСКОЙ СТАНЦИИ 365
Чтобы просмотреть готовую карту, найди среди файлов­ примеров, прилагаемых к книге, файл missz:on-pytlюn-map.pcif и открой его в програ1\1ме для просмотра РDF-файлов, на­ пример Adobe Reader (https://get.adobe.coш/rн/reader/) (рис. Д.1). Рис. Д.1. Готовую карту космической станции ты найдешь в файле mission-python-map.pdf На мини-картах справа указаны помещения, в которых есть подвижные опасности (выделены желтым цветом на верхней карте) и предметы реквизита, с которыми можно взаимодействовать (выделены желтым цветом на нижней карте). При желании ты можешь использовать мой шаблон кар­ ты в качестве образца для создания собственной , а также де­ лать на нем заметки во время игры или в процессе ее на­ стройки.
ДО&АВЯЕНИЕ РЕАЯЬНЫХ ЗВУКОВ МАРСА в иrРУ «ПО&Еr» В декабре 2018 года после шестиме­ сячного полета посадочный модуль NASA InSight совершил посадку на поверх­ ности Марса. Этот аппарат будет искать признаки тектонической активности на пла­ нете и следы падения метеоритов, измерять температуру поверхности планеты и отсле­ живать колебания орбиты по мере ее вра­ щения вокруг Солнца. Этот аппарат пред­ ставляет собой совершенно невероятное техническое изобретение (рис. Е.1). ДО&АВЛЕНИЕ РЕАЛЬНЫХ ЗВУКОВ МАРСА в иrРУ «ПО&Еr» 367
Рис. Е. 1 . Аппарат l пSight, работающий на поверхности Марса, в представлении художника. Изображение предоставлено Лабораторией реактивного движения NASA при Калифорнийском технологическом институте Одним из первых фрагментов данных, переданных этим аппаратом на Землю, стала запись шума марсианского ветра, сделанная сейсмографо!\t. Когда я услышал запись этих да­ лею1х звуков, меня охватил трепет. Я подумал, что было бы забавно добавить звукозапись NASA в игру «Побег » , описанную в книге « Миссия Python», чтобы игроки могли услышать настоящие звуки Марса, оказавшись на поверхности планеты. Половина иг­ ровой карты отведена под область, расположенную за пре­ делами космической станции, это помогает создать ощуще­ ние огромного безлюдного марсианского пространства, в которое попадает игрок, выходя из тесных и замкнутых внутренних помещений. Я создал код, воспроизводящий звуки марсианской атмо­ сферы, когда астронавт оказывается на поверхности плане­ ты. Проще всего добавить этот код в функцию room(), start _ которая запускается при входе игрока в новое помещение. Чтобы этот звук не был слышен внутри поме­ щения, код останавливает его воспроизведение, а затем за ­ пускает его, если и г рок оказывается в помещении, рас п оло­ женном снаружи станции . Остановка и запуск воспроизведения звука может сопровождаться слышимыми хлопками. Избавиться от них можно, если запускать 368 ПРИЛОЖЕНИЕ Е
воспроизведение звука только при переходе из внутренних помещений в наружные. Однако это требует внесения в код более значительных изменений, включая использование но­ вой переменно1"::'1 для запоминания номера предыдущего по­ мещения. Кро11ле того, эти хлопки не так уж сильно замет­ ны. Вот код, который нужно добавить в конец функции start _ room (): c:ock.schedule interval:door in_room_26, hazard_start () ### ### Дополнения, позволяющие марсианской атмосферы воспроизводить на поверхности 0.05) звуки планеты sounds.mars_ atmosphere.stop() if c u rrent room in outdoor_ rooms: sounds.mars_ a t mosphere .p l ay( - 1) def game_loop(): global player_x, player у, current room Файл со звуковым эффектоJ1.1 называется mars _ atrnospl?ere. wav, его нужно добавить в папку Sounds. Инструкция sounds.mars _ atmosphere.play(-1) обеспечивает бес­ конечное повторение этого звука, поэтому он будет воспро­ изводиться все то время, что игрок находится на поверхно­ сти планеты. Несмотря на то что люди способны слышать необработанные записи, они содержат слишком N1ного низ­ кочастотных звуков, поэтому специалисты NASA повысили высоту звука на две октавы, создав версию записи, которую легче воспринимать при воспроизведении на обычном ком­ пьютере. Я использовал именно эту звукозапись. Ты найдешь звуковой эффект и измененный файл с ко­ дом игры в папке \bonus\mars. Я снова включил телепорт в этuй в~рсии и1·ры, 11uэтuму ты мuж~шь лtткu 11~р~м~стить­ ся в помещение на поверхности планеты, нажав клавишу Х и введя номер помещения от 1 до 25. Помни, что тебе нуж­ но переключиться на окно командной строки для ввода но­ мера помещения, в которое ты хочешь попасть. Я благодарю NASA за вдохновение и предоставление звуковой записи для использования в подобных проектах.

ДО&АВЯЕНИЕСКРЫТЫХ О&ЪЕКТОВ В КАЧЕСТВЕ ПАСХАЯЬНЫХ ЯИЦ Моя книга поможет тебе создать космическую приключенческую игру, в которой ты будешь собирать различные предметы и использовать их для совершения побега с космической станции. Одна из сложностей игры состоит в том, чтобы отыскать на станции нужные объекты, которые могут быть скрыты от глаз. Скрыть такие объекты (называемые реквизитом) в игре ~< Побег » 1\ЮЖНО тремя способами. • • • За декорациями. Внутри декораций. Внутри случайным образом выбранной декорации (требует добавления нового кода). ДО&АВЛЕНИЕ СКРЫТЫХ О&'ЫКТОВ В КАЧЕСТВЕ ПАСХАЛЬНЫХ llИЦ 371
В этом приложении я расскажу тебе обо всех этих спосо­ бах. В книге работа кода объясняется более подробно, но даже без прочтения книги ты J\южешь использовать при­ веденный здесь код для добавления в игру пасхальных яиц. ПРЯЧЕМ РЕКВИЗИТ ЗА ДЕКОРАЦИЕЙ Самый простой способ скрыть предмет реквизита - это спря­ тать его за декора1~ией. Чтобы не заставлять игрока исследо­ вать кажды1':1 уголок в поисках того или иного объекта, сделай так, чтобы этот объект был частично на виду. В изображенном ниже помещении 39 я спрятал позади стола ложку, часть кото­ рой осталась видимой. Я думал о том, чтобы спрятать молоток за манипулятором для подъема тяжестей в помещении 50, но не был уверен, что это будет честно, учитывая, что до этого момента манипулятор игрокам еще не попадался, поэтому они могли принять молоток за его часть. Поэкспериментируй с расположением предметов реквизита , частично закрывая их другими объектами. Используй файл mission-python-map.pcif в качестве источника вдохновения! " ... () " " Рис. Ж.1. Комната отдыха экипажа, здесь ложка частично заслонена столом 372 ПРИЛОЖЕНИЕ Ж
ПРЯЧЕМ РЕКВИЗИТ ВНУТРИ ДЕКОРАЦИИ Ты также можешь спрятать реквизит внутри декорации, при исследовании которой его можно будет найти. Например , предмет 76 (ножницы) находится в помещении в позиции с координатами у = 3, х = 5 41 (см. ниже) (нумерация начина­ ется с О, соответствующего левому верхнему углу, и учи ты­ вает пространство, занимаеl\юе стеной). В указанной выше точке помещения 41 также находится предмет 10 (шкаф). Если ты заглянешь в него , то обнаружишь в нем ножницы. Важно дать игрокам толковую подсказку относительно того, где им следует искать тот или иной предмет. Для этого нужно либо предусмотреть особый элемент декорации, либо добавить в помещение что-то, внутри чего обычно находятся предметы в реальном мире. Игра вызвала бы у игроков раздражение, если бы им пришлось исследовать каждую часть стены или стола в поисках нужного предмета. Рис. Ж.2. Лазарет со шкафами-сейфами, в которых можно спрятать предметы ДО&АВЛЕНИЕ СКРЫТЫХ О&'ЫКТОВ В КАЧЕСТВЕ ПАСХАЛЬНЫХ llИЦ 373
ПРЯЧЕМ РЕКВИЗИТ ВНУТРИ СЛУЧАЙНОЙ ДЕКОРАЦИИ Трети~'-'~ способ сокрытия предметов предполагает выбор их расположения случайным образом. Более ранняя версия игры «Побег» содержала головоломку, суть которой заклю­ чалась в том, что игрок оказывался запертым в своей спальне и должен был найти карту доступа, чтобы выбраться оттуда. Эта карта была спрятана случайным образом внутри выбранного фрагмента декорации. Судя по полученным отзывам, игрокаl\-1 не очень понравилась моя задумка, поэтому, переделывая карту, я убрал эту голово­ ломку. Однако игры наподобие Ani111al Crossing показали, что поисковые головоломки могут оказаться интересными при условии, что у игроков есть достаточно подсказок, бла­ годаря которым им не придется обыскивать все подряд. Тем не менее ты можешь спрятать несущественные предметы в качестве награды для тех игроков, которые исследуют игровое пространство более тщательно. Далее я покажу тебе, как добавить объект в игру «Побег» и как расположить его случаi::'1ным образом, чтобы игроки смогли развлечься, разыскивая пасхальные яйца. ДО&АВЛЕНИЕ новых О&ЪЕКТОВ в иrРУ 1. Доба вь ф айл изображения: я буду использовать изображе­ ние пасхального яйца, созданное пользователем OpenGa111eArt под ником Fraang 6 , которое распространяется по лицензии CC-BY-SA 3.0. Я обрезал это изображение и изменил его раз1\1ер для использования в игре «Побег». Воспользуйся картинкой 2. egg.png из папки \bonus\eggs\images. Добавь о б ъект в список объектов: тебе необходимо доба­ вить в игру имя файла изображения, а также подробное и краткое описания. Если изображение должно отбрасывать тень, тебе следует указать и ее параметры. Мы добавляем реквизит, а предметы реквизита не отбрасывают тени, так что в этом случае мы используем слово 1 ' 374 https://opengaшear t .org/coпte n t/eas t er-egg ПРИЛОЖЕНИЕ Ж None в качестве ее
параметра. В части программы ОБЪЕКТЫ добавь новый объ­ ект в конец списка, как указано здесь (обрати внимание на дополнительную запятую после объекта 81 !): ############### ## Оt'ЪЕКТЫ ## ############### objects О: ~ { [images.floor, None, "Чистый и бп~"тящий пол"], --пг,)пуск-- 81: 82: [images.access_card, None, т :/ " + FRIEND2_NAМE, "картd ;J:O<'TYПc:J"] [images.egg, None , " Кажется , это вкусное \ шоколадное яйцо !" , items player may carry 3. " Пасхальное ' яйцо " ] list(range(53, 83)) Сделай объект переносимым: чтобы игрок мог подобрать пасхальное яйцо, нужно добавить его в список player _ may _ carry. items _ Мы можем сделать это, изменив диапазон этого списка так, чтобы он включал объект Изr-.лени значение 82 на 83 82. в инструкции, которая превра­ щает этот диапазо н в список (последнее число в этой инструкции не входит в диапазон): items_player_may_carry = list (range (53, 4. 83)) Размес ти новый предмет реквизита: теперь ты можешь добавить запись для этого предмета реквизита в список props, чтобы объект появился в игре. Реквизит, которого еще нет в игре, обычно попадает в помещение О, но давай поместим его в помещение 31 (откуда стартует игрок), чтобы проверить, все ли работает нормально. Обрати вни­ мание на новую запятую после предмета 81. Также тебе необходимо закомментировать код вычисления контроль­ ной суммы, который используется для проверки корректно­ сти введенного кода. Если этого не сделать, програмi\1а воспримет добавление нового объекта как опечатку. Далее приведен измененный код. Тебе нужно внести изменения, ДО&АВЛЕНИЕ СКРЫТЫХ О&'ЫКТОВ В КАЧЕСТВЕ ПАСХАЛЬНЫХ llИЦ 375
начиная с запятой после объекта нается с числа 81 в строке, которая начи­ 78. ############### ## Pt l'СЬ.~ЗИ с' ## ############### # Ppj< ,и<,,1Т - ООЪЕ:"" ГЕI, iC(J 'ОР !Е' можнс г ept>'l.1 _L\ctTE Mt>Ж,r,y KOJY.1- l' с м:vr' # они м-:>r-у г юявп,.. гь.::-,:r и,r:л И"ЧЕ"'-j э.-г~-,. Люб'1;/J: рЕ"КР.А"iИТ ri.o ЖЕ'Н # HdC,..pdИBd гы-:R зде(.ь. ~'екгизит ьн :--:гpLr OTПf.'dBлqeтcq .- помещение О. # 1-.nмер r_,бъ~J< та: [ ,>мещ-=ние, у, х. propq ~. { 20: [31, О, 4], 21: ~26, О, 1], 22: (41, о, 2], 23: (39, о, 5], --приr,уск-- 78: 81: ### ЯЙЦО (35, (29, 9, 1, 11), 79: (26, 3, 2), 1)' 82: (31, 3, 3) закомментировал следующий код, 80: чтобы [41, 7, добавить 5), ПАСХАЛЬНОЕ ### ##checksum = О ##for key , prop in props.items() : ## if key != 71 : # номер 71 пропускаем, потому что ## в каждой игре он разный . ## checksum += (prop[O] * key ## + prop[l) * (key + 1) ## + prop[2) * (key + 2)) ##print(len (props) , " (количество реквизита) " ) ##assert len(props) == 37 , " Ожидается 37 предметов реквизита " ##рrint( " Контрольная сумма реквизита :", checksum) ##assert checksum == 61414 , "Ошибка в данных реквизита " in_my_pockets selected_item (55) # О п~rвый эл~ме~~ 5. Добавь возможность взаимодействия: часть ВЗАИМОД ЕЙСТ В ИЕ С ОБЪЕКТАМИ программы содержит инструкции, отвечающие за возможности использования объекта. Самым простым действием является показ сообще­ ния при использовании предмета. Просто добавь новый объект 82 в список standard _ responses с соответ­ ствующим сообщением, как показано ниже. (При желании сделать что-то более сложное, обрати внимание в этом раз­ деле на код для объекта 376 ПРИЛОЖЕНИЕ Ж 16, который повышает уровень
энергии персонажа, когда он жует петрушку.) Не забудь добавить новую запятую после объекта в списке. 75 standard_responses = { -- прппуск-75: 82 : # "Ты зr~с ,: "+ + str (player_x) " Ты ешь .!олучени<"' шоколад . номера +" 11 Х:" + str (player_y) , str(current_roorт) У: " // " Божественный объе><:та в \1t стс- вкус ... " чах'JждРниq игро><:rt. item_player_is_on - get_item_under_player() Теперь ты можешь начать игру как обычно и увидеть пас­ хальное яйцо в помещении, в ко т ором ты находишься в на­ чале игры. Это яйцо r-..южно подобрать, бросить и даже от­ кусить от него кусочек. Ты можешь скрыть яйцо за декорацией, изменив номер помещения и координаты у их объекта (см. шаг 4 82 в списке props выше). Чтобы добавить больше яиц, добавь дополнительные объекты (83, 84 и т. д.), использующие тот же файл images.egg. СОКРЫТИЕ О&ЪЕКТОВ в Иf РЕ На следующем этапе мы должны с п рятать пасхальное я~":'що в выбранном случайным образом фрагменте декорации, чтобы при ее исследовании игрок мог обнаружить подго ­ товленный тобой сюрприз. Ниже представлена новая функ­ ция для сокрытия объектов в декорациях помещения. Мы указываем ей, какой объект и в каком помещении необхо­ димо спрятать. Программа выбирает для его сокрытия слу­ чайный фрагмент де!{орации. Не забудь предоставить игро­ кам содержательную подсказку относительно того, в какой комнате спрятан объект, чтобы они не тратили время на исследование комнат, в которых ничего нет. Например, ты 1\южешь изменить описание помещения, указав, что в нем приятно пахнет шоколадом. Я скрыл пасхальное яйцо в комнате отдыха экипажа (см. иллюстрацию выше). Ты легко найдешь это помещение. ДО&АВЛЕНИЕ СКРЫТЫХ О&ЬЕКТОВ В КАЧЕСТВЕ ПАСХАЛЬНЫХ llИЦ 377
Используй карту игры (файл rnz:ssion-pytlюn-map.pcif) на слу­ чай, если заблудишься. Добавь следующий фраплент кода перед разделом СТАРТ, который находится ближе к концу программы: hazard[O] hazard[l] hazard_y haг:drd_x ######################## ## СОКРЫТИЕ ОБЪЕКТОВ ## ######################## def object_to_hide): # эта функция помещает объект в то место , где уже есть # фрагмент декорации , ее можно использовать для того , # чтобы спрятать предмет в случайном месте в конкретном # помещении, например пасхальное яйцо в комнате отдыха # экипажа global props number_of_items_in_room = len(scenery[room_number]) item_to_hide_in = random.randint(O, number_of_items_in room - 1) print (item_to_hide_in) props[object_to_hide] [0] = room_number props[object_to_hide] [1] = scenery[room_number] [item_to_hide_in] [1] props[object_to_hide] [2] = scenery[room_number] [item_to_hide_in] [2] hide_object (room_numЬer, hide_object(39, 82) #скрывает объект 82 (яйцо) # в помещении 39 (комната отдыха экипажа) ############### Н СТАРТ ## ############### код иrРЫ «ПО&Еf», в КОТОРОЙ ЕСТЬ ПАСХАЛЬНЫЕ ЯЙЦА В папке \bonus\eggs тебя ждет игра, дополненная «Пасхал­ ками», запустить ее можно с помощью файла /Jiding.py. Удачи в поисках! escape_random _
ЛУЧШИЕ КНИГИ О БИЗНЕСЕ С ЛОГОТИПОМ ВАШЕЙ КОМПАНИИ? ЛЕГКО! Удивить своих 1<лиентов, бизнес-партнеров, сделать памятный подаро1< сотрудни1<ам и расс1<азать о своей 1<омпании читателям бизнес-литературы? Приглашаем стать партнерами выпус1<а а1<туальных и популярных 1<ниг. О вашей 1<омпании узнает наиболее а1<тивная аудитория. ПАРТНЕРСКИЕ ОПЦИИ : • Специальный тираж уже существующих логотип ВАШЕЙ 1<ниг с логотипом вашей 1<омпании . КОМПАНИИ НА ОБЛОЖКЕ • Размещение логотипа на супер­ облож1<е для малых тиражей (от 30 штук) . • Поддержка выхода новин1<и, которая ранее не была доступна читателям (50 книг в подарок). ПАРТНЕРСКИЕ ВОЗМОЖНОСТИ: • Ре1<ламная полоса о вашей 1<омпании внутри к ниги. • Вступительное слово в 1<ниге от первых лиц 1<омпании-партнера. • Обращение первых лиц ЛОГОТИП на супероблож1<е. НА КОРЕШКЕ • Отзыв на обороте облож1<и вложение информационных материалов о вашей 1<омпании (за1<лад1<и, листов1<и, мини-бу1<леты). ТЕКСТ ДЛЯ КЛИЕНТОВ НА ОБЛОЖКЕ У вас есть возможность обсудить свои пожелания с менеджерами 1<орпоративных продаж. Ка1<? Звоните: +7 495 4116859,доб. Заходите на сайт: eksmo.ru/b2b 2261
ПРЕДМЕТНЫЙ УКАЗАТЕЯЬ cuгved bгackets. См. paгentheses гegulaг inteгvals. См. clock displaying numbeгs. См. pгint() text. См. stгing displaying text. См. pгint() Windows, ОС downloads 46 example listings 46 IDLE, среда 29 запуск программ Pygame Zего 31 распаковка файлов 28 установка Pygame Zего 23 запуск в Windows 8 37 запуск в Windows 1О 37 запуск на RаsрЬеггу Pi 38 оболочка Python 38 inteгactive mode. См. shell Автоматический картограф принцип работы 102 Анимация 178 кадр 179 PNG, формат 95 Аргумент 39, 200 Pygame Zего, программа 21 Блокировка дверей 271 завершение работы 50 Вывод 40 поддержка русского языка 25 Выкладывание предмета 249 Python Editoг. См. IDLE Python, язык программирования 21 запуск редактора 37 установка 22 RаsрЬеггу Pi 22 загрузка и распаковка файлов на 27 запуск программ Pygame Zего на 30 Выход в открытый космос 35, 54 разбор кода 56 Выходы удаление 289 Вычисление 41 Глобальная переменная 57 Двери 230 ПРЕДМЕТНЫЙ УКАЗАТЕЛЬ 379
анимация 280 Корта блокировка 271 автоматизация процесса создания 102 выбор место размещения 272 открытие и закрытие отображение с помощью циклов 84, 277 85 размещение 273 очистка с таймером 282 поиск объекта но 236 шлюзового отсека 285 90 помещения 84 Движущиеся опасные объекты 299 Квадратные скобки 356 Двоеточия 356 Комбинирование объектов 265 Двойные кавычки 39 Комментарии Действия с предметами 257 Константа задание 262 47 48 Контрольная сумма с помощью клавиатуры 258 отключение проверки 161 проверка 161 Декорации добавление 159 Координаты загрузка 166 Кортеж 50, 203 Диапазон 151 76 Космическая станция 32 Замена предметов в помещении 78 исследование в 3D 121 Запуск игры 29 обустройство 155 Запятые 357 опасности 291 Звуки 357 отключение навигации по помещениям Звуковоесопровождение 315 176 Изображения 357 перемещение по 32 Инвентарь 231 подготовка оборудования для 129 выбор предмета 33 прибытие но 176 настройка 242 просмотр помещений 170 отображение 243 тестирование создание 101 составление словаря объектов 13 8 248 удаление предмета 33 управление с помощью клавиши ТоЬ 246 телепортоцияно 180 Круглые скобки 356 Локальная переменная 57 Ингредиент 267 Лужи ядовитой жижи 312 Индекс Метод 67 Индикатор уровнявоздухо 293 энергии 293 133 Настройка игры под себя 315 Обзор, раздел удаление 204 Использование объекта 33 Оболочка Pythoп 38 Исследование объекта 270 Одинарные кавычки Кадр 179 Окно ввода сценариев 42 380 ПРИЛОЖЕНИЕ Ж: 40
Опасности Посадочный модуль 140, 231 добовлениекодо 300 Предупреждения 221 запуск и остановка Программа 42 303 обеспечение движения 307 Прозрачная стена, 219 отображение в помещении 31 О Просмотр объектов с помощью кода 143 прохождение игрока сквозь 311 Прохождение игры 32 создание корты 306 Реквизит Орфография 357 добавление кода для 227 Осмотр предмета 32, 252 добавление но корту помещения 232 Отладка 11 О, 353 Рецепт Отображение текста 39 Система контроля уровня воздуха Отображение трехмерного изображения Скафандр 32, 56 265 292 установка двигателей но 59 92 Отсечения, область 212 Словарь 130 Отступы 354 значение Очистка игровой арены 211 извлечение информации из списка, по- мещенного в 136 Передвижение КОД ДЛЯ 182 и список 130 между помещениями 191 Переменная 130 ключ 130 проверка но ошибки 133 47 глобальная 57 размещение списков в 135 именование 48 Советы 221 локальная 57 Создание увеличение значения 58 выходов уменьшение значения 58 звездного небо 43 хранящая донные о прогрессе игрока игрового поля 43 картографических донных 261 Пикселы 118 корты опасностей 96 Поднятие предмета Подсказки 33, 239 76 ограждения по периметру поверхности но корте помещения 236 Полупрозрочноястено Помещение космического корабля 51 объектов 140 221 Поиск предметов 103 306 214 планеты планеты 162 51 помещений но основе донных 112 102 настройка доступа в 274 помещения 94 отображение названия 223 помещения простой формы 116 проектирование словаря планет 130 144 расположение но экране 215 собственных корт 125 рисование 209, 212 телепорто 283 ПРЕДМЕТНЫЙ УКАЗАТЕЛЬ 381
Сокрытие передней стены 213 Тестирование кода 11 О Сообщения при использовании объектов Уменьшение энергии игрока 302 Управление с клавиатуры 241 259 Список 63, 64 Уровень воздуха добавлениеэлемента 66 индикатор 293 доступ к элементу 67 сигнал тревоги замена элемента 68 система контроля 292 индекс функция уменьшения 294 67 и словарь 297 Фигурные скобки 356 130 квадратные скобки 68 Финальный эпизод игры 269 круглые скобки 68 Функция 39, 48 объединение нескольких 73 превращение диапазона в просмотр удалениеэлемента 69 удаление элемента 66 Строка 39 создание 198 Цветовое выделение частей инструкции Цикл 84 в цикле 87 Чувствительность к регистру 48, 355 Телепорт 313 создание 283 Тень 198 Цвета 203 70 отключение 235 передача данных рисующая объекты 206 65 размещение в словаре 135 списков вызов 151 202 добавление 214 Экран, раздел добавление 205 39
У каждого космонавта есть своя мис­ Шон сия, а у юного читателя этой книги тридцати самоучителей по програм­ миссия особенная - мированию для детей создать свою МакМанус автор более - и взрослых , первую игру на языке Python! В этом эксперт ему помогут подробные инструкции бизнеса. Журналист в таких извест­ от ных британских научно-популярных автора, яркие иллюстрации и в области и удобная навигация по книге . В кон­ изданиях, це World, lnternet Works, Start & Run каждой главы - практические как технологий Personal Computer навыков Your Business и других. Он сотруд­ программирования , а в конце руко­ ничает со множеством крупнейших водства - технологических упражнения для развития образцовые фрагменты компаний. Посе­ кода и советы по удалению ошибок тить его веб-сайт можно по адресу в игре . www.sean.co.uk. Там вы найдете не­ с помощью этого РУКОВОДСТВА ВЫ: которые главы из его книг и полез­ ные советы по созданию игр. • Создадите своего персонажа • Поселите его на космической станции • Выведите героя в открытый космос • И вперед - к новым приключениям в мире программирования и гейм­ дизайна! ISBN 978-5-04-159530-2 11111111111111111111 1111111111 9 785 041 595302 > -• о ~1 . " 11-1\ .:..---:. СОЗДАЙТЕ ЭТУ ИГРУ!