Змейка на c код: Консольная игра «Змейка»
Содержание
Змейка на C
Недавно уже давно (а нечего посты задерживать!) мне захотелось написать небольшую и очень простенькую игру — змейку.
Для юниксового терминала. На чистом C — даже без ncurses и прочих подобных финтифлюшек.
А сегодня я расскажу и покажу, что получилось.
Тем, кому не интересно читать пост, а интересно сразу увидеть код и/или историю коммитов, предлагаю пойти поглядеть на репозиторий.
Небольшой дисклеймер
Да, я в курсе, что можно добавить множество разных фич (в том числе мультиплеер и уровни с процедурно генерируемыми стенками) и много чего сделать лучше как в самой игре, так и в исходниках. Возможно, я когда-то это и сделаю. На данный момент ничего особо примечательного в коде нету, потому и stupid-snake
Но была поставлена цель сделать не максимально хорошо и качественно, а быстро и чтобы работало. Ну и ещё по возможности наглядно — идея написать пост всплыла с самого начала. Эта цель в полной мере достигнута.
Немного пояснений
C — довольно низкоуровневый язык, и уж никак не «кроссплатформенный по-умолчанию», как, например, Python. Тот факт, что я не использую ncurses, ещё больше усугубляет ситуацию.
Очень многое в игре реализовано путём функций, имеющихся лишь в POSIX-системах, а то и вообще лишь в Linux и glibc. Windows POSIX-системой не является, да и терминал там вряд ли настолько же функционален, потому собрать stupid-snake под Windows у вас, скорее всего, никак не получится. На OS X — не знаю, пусть кто-то попробует и доложит об успехах.
Может также и не на каждом дистрибутиве Linux собраться, в таком случае напишите мне — помогу и добавлю нужные флаги в мейкфайл.
Если своего линукса у вас нигде не завалялось, а испытать игру очень хочется, рекомендую использовать VirtualBox или иной софт для работы с виртуальными машинами, какой вы предпочитаете.
Если же вам интересен сам процесс и разъяснения к коду — вы совершенно ничего не теряете, читая этот пост хоть с Windows, хоть с Linux, хоть с OS/2.
Почему терминал? Почему даже без ncurses? Зачем так усложнять себе жизнь?
Во-первых потому, что мне так захотелось. Во-вторых потому, что ncurses — это очень большая, толстая и сложная либа, так что не факт, что было бы проще. Пояснять, скорее всего, стало бы лишь сложнее.
Я пойму пост, если не знаю C?
Понятия не имею. Если вы действительно не знаете ни C, ни преступно подобных ему языков (типа C++) — напишите, пожалуйста, поняли вы или нет. Мне интересно.
Пост планировался как понятный полным «чайникам», но получилось ли у меня — совсем другой вопрос.
Если я прочитаю пост, я выучу C?
Нет.
Если я прочитаю пост, я научусь писать игры?
Нет. Скорее всего.
Зачем тогда мне читать пост?
Откуда я знаю, зачем вам читать пост? Может, вам интересно смотреть, как другие люди мыслят и решают задачи. Может, вам интересно узнать, какой это он — C в консолечке юниксовой. Может, просто скучно.
Впрочем, я думаю, в моём посте можно будет отыскать некоторые интересные факты и подходы, до сих пор вам неизвестные.
Какъ собрать и запустить ваше игрище бѣсовское, сударь?
Для бояр, которые этого до сих пор никогда не делали, а гуглить лень, кратенькое пояснение:
$ git clone ‘https://gitlab.com/saxahoid/stupid-snake.git’
$ cd stupid-snake
$ make
$ ./snake
Управление?
Стрелки, выход — Ctrl+C или врубиться в стенку/себя.
А вот я вижу место, где как-то очень хитроподвывернуто сделано, нельзя ли проще?
Скорее всего, нет. Как я дальше поясню в посте, в процессе разработки вылезло несколько очень интересных особенностей работы с терминалом, и эти хитроподвывернутые костыли — единственный рабочий вариант.
Поехали
Давайте-ка сделаем игру
Что нужно, дабы сделать игру для терминала? Знать какой-нибудь язык программирования более-менее уверенно и уметь гуглить, чтобы разобраться с некоторыми особенностями этого самого терминала.
Знать кучу библиотек (ну разве только ncurses, если хочется более серьёзную игру, чем у меня, с минимумом геморроя), особенности работы вашей любимой оконной системы, да ещё заодно щедрую дозу компьютерной графики и уж тем более быть дизайнером-художником абсолютно не обязательно и даже вредно. Потому что так не интересно.
Текстовый ввод/вывод — один из самых простых и основных для любого современного ЯП. Если вы уверенно (да даже если криво сикось накось) знаете какой-нибудь, то вы знаете и как работать с текстом. Поэтому для создания игры, использующей текст для общения с пользователем, не нужно знать ничего специфичного. Поэтому же такие игры, даже если примитивные по сегодняшним меркам, становятся неплохими упражнениями для мозга.
Всякая разработка (не только игр) проходит в несколько этапов. Для простоты я исключу более творческие и сложно характеризуемые типа идеи (она уже есть) и сосредоточусь на технических (да к тому же применимых в данном случае; архитектуры, скажем, тут нету).
Дизайн
Прошу заметить, что имеется в виду не тот дизайн, которым занимается Артемий Лебедев, а технический дизайн. Оно же проектирование.
На этапе дизайна структура программы обдумывается «на бумажке», без написания рабочего исполняющегося кода (шаблоны делать можно). Такой подход кажется непродуктивным, но, если хорошо им овладеть, можно избавить себя от множества «Эврика!»-моментов, когда половину всех исходников приходится переписывать из-за неверно выбранного в самом начале решения. А потом ещё разок. Затраты на такое растут с размерами проекта очень нехило, так что частенько даже несколько месяцев чистого дизайна окупаются сполна.
В идеале садиться за код разработчик должен лишь тогда, когда уже полностью представляет себе программу.
Комплексная задача (игра «Змейка») разбивается на более мелкие:
• Обновление экрана;
Очевидно, для того, чтобы игра была динамической и отвечала на пользовательский ввод, нужно периодически обновлять экран. Так как «змейка» — не походовая забава, делать это необходимо по таймеру.
На нашем ламерском уровне более чем достаточно воспользоваться командой вроде sleep (приказать программе ничего не делать 1/fps часть секунды, где fps — желаемая кадровая частота), но это неинтересно и вообще я болею перфекционизмом, потому буду использовать часы реального времени.
• Визуализация змейки;
Терминал современного Linux поддерживает юникод — вот уж где простор для фантазии! Но по моему мнению реализация юникода в самом C не то чтобы очевидная, потому (для начала, во всяком случае) решаю использовать самый стандартный из всех стандартов — ASCII. >».
На том бы и остановиться, вот только неинтересно (и я болею перфекционизмом, помните?). Нужна ещё анимация. Как насчёт заставлять змейку закрывать рот каждый ход? Досточно будет заменить символ «головы» на соответствующий направлению символ «тела». Переход «<» ➛ «-» сносно создаёт иллюзию закрывшегося рта.
Так же стоит «выпрямлять» повороты тела змеи, когда они становятся последним сегментом (не бывает же у змей таких загнутых хвостов).
• Связь между элементами игры;
Змейка должна погибнуть, наткнувшись на стену, и вырасти, съев пищу. Для того, чтобы увязать меж собой положение стен, еды, головы, тела и хвоста змейки, необходимо использовать общую для них всех систему координат. И самый очевидный вариант — применить стандартные координаты терминала (0, 0 — верхний левый угол, и разрешение, разумеется, посимвольное — про пиксели терминал ничего знать не знает).
Дальше остаётся в общем два пути: 1) Создать все требующие учёта элементы как отдельные сущности, свойствами которых являются координаты; 2) Создать одну сущность — координатное поле — служащую отображением нынешнего состояния терминала, и разместить элементы-символы в ней.
Первый путь — очень громоздкий. Например, для учёта положения еды необходимо будет создать массив (а то и список) сущностей типа «еда», у каждой из которых будет свой набор координат. Чтобы проверить, не съела ли змейка еду, нужно будет каждый раз проходиться по всему этому массиву/списку. Аналогично при выводе на экран будет необходимо обойти каждую сущность-элемент, чтобы узнать нужные координаты для вывода.
Второй путь — намного более легковесный и удобный. Чтобы проверить, не наткнулась ли змея на что-то, достаточно перед её перемещением проверить «новую» позицию — нет ли там уже чего? С выводом на экран тоже легко: просто print каждый элемент из координатной сетки. Но есть и проблема у второго пути — как найти саму змею? Обходить всё координатное поле поиском?
Как часто бывает, идеальный вариант — комбинированный. Так как интересует меня в основном змея, а всё остальное — уже относительно неё, для удобства нахождения змейки в координатном поле изобразим её ещё и отдельной сущностью, хранящей в себе нынешние координаты.
И да, если вы подумали, что первый вариант — без координатного поля — бесполезен, то вы сильно ошибаетесь! У него есть свои применения. Например, если полное отображение координатного поля потребует десятка гибибайт памяти, тогда как сущностей создать нужно было бы всего десяток-другой. Но подобные расклады настолько же далеки от «змейки», насколько автор этого поста — от мирового господства.
• Передвижение змейки;
Чтобы передвигать змейку, необходимо знать нынешнюю позицию головы, следующую позицию головы, нынешнюю позицию хвоста и следующую позицию хвоста. При выполнении «хода» нарисовать голову на новой позиции, сегмент тела — на старой; старую позицию хвоста очистить. Новую позицию хвоста надо знать лишь для того, чтобы запомнить её на будущее.
Здесь возможны разнообразные варианты. С учётом уже имеющейся в дизайне отдельной сущности «змея», хранящей координаты её головы для быстрого нахождения, мне показалось самым разумным в эту же сущность добавить также координаты хвоста. Для определения новой позиции головы достаточно сохранить её (головы) направление (и инкрементировать/декрементировать координаты нынешней позиции в зависимости от направления, чтобы получить новую). Аналогично поступить с хвостом — хранить его направление.
Но тут возникает проблема: если с переменой направления головы всё ясно (игрок нажал влево — повернуть голову влево), то как быть с хвостом? Не желая хранить дополнительную информацию в больших объёмах исключительно для правильной обработки хвоста, я прибегаю к простому трюку: зная направление хвоста и следующий в этом направлении сегмент тела, новое направление определить очень легко.
Например, если хвост двигался ВПРАВО, и следующий справа сегмент — «-«, хвост продолжает двигаться ВПРАВО. Если наблюдается сегмент «\», хвост поворачивает ВНИЗ. Если «/» — ВВЕРХ. Если «|», то что-то сломалось; такого случиться никогда не должно.
• Генерация еды;
Генерируем, разумеется, случайным образом. Количество генерируемой еды зависит от размеров поля; скажем, 1 единица на каждые 512 пустых позиций.
• Обработка стенок;
Можно держать стенки внутри координатного поля (и проверять столкновение по принципу «Является ли следующая позиция головы занята символом стены?». Можно держать их снаружи (и проверять столкновение по принципу «Не вышла ли голова змеи за разрешённые пределы координат?»). Мне больше понравился второй способ. Он также кажется более гибким (позволяет легко заменить логику столкновения со стеной на логику «телепортации» на другой край поля — тоже интересный подход).
Если мне не изменяет память, именно перечисленные выше пункты были обдуманы мною на этапе дизайна. Выглядит сложно, но заняло это у меня всего-то пару часов не самого усердного размышления с блокнотом (и всего три страницы последнего).
Код
Откройте исходник, я буду ссылаться на номера строк (в квадратных скобках), функции и так далее.
Немного слов о файловой структуре кода. Она очень простая — один исходник (snake.c), один makefile. LICENSE и README.md не используются при сборке совершенно никак и не важны для понимания программы. По-хорошему функции надо было бы вынести в отдельные файлы и сгруппировать таким образом по их предназначению, но мне показалось более правильным использовать структуру попроще.
main() — строка 515
main — основная функция программы; сама программа, если пожелаете. Исполнение начинается отсюда. Так как мне нет нужды на данный момент принимать какие-либо аргументы из командной строки, main я объявляю без параметров (void).
В начале вызываются несколько инициализирующих функций (переключение терминала в нужный режим, установка обработчика сигнала INT (Ctrl+C) и инициализация генератора псевдослучайных чисел), затем объявляются нужные переменные, затем производится ещё несколько инициализаций — уже касающихся самой игры — и первый вывод картинки в терминал.
Наконец, получив значение начального момента времени, код входит в цикл обновления изображения.
Этот цикл имеет несколько интересных особенностей. Начнём с условия — while(run). run является [64] переменной особого типа sig_atomic_t, придуманного специально для использования в обработчиках сигналов. Свой кастомный обработчик [82] я устанавливаю на строке [517], и всё, что он делает — сбрасывает при вызове этот самый run в 0. Обрабатывать SIGINT нужно затем, чтобы корректно выйти из игры и вернуть терминал в адекватное состояние — стандартный предоставляемый системой обработчик просто завершает программу «здесь и сейчас».
Так как сигналы довольно похожи на прерывания, их обработчики также схожи.
Обработчик вызывается в тот момент, когда сигнал приходит, вне зависимости от исполняемого участка программы. После завершения обработчика, исполнение возвращается в старое место и всё продолжается, как ни в чём не бывало. И когда я говорю «вне зависимости от исполняемого участка», я это и имею в виду — обработчик может быть вызван даже сам из себя.
Ввиду такой специфики применения в обработчиках нельзя использовать большую часть функций (на самом деле, существует даже официальный список тех стандартных функций, которые таки можно с уверенностью применять в обработчиках. Он небольшой). Хотя «нельзя» в C и означает всегда «Можно, но мы вообще ничего не гарантируем и ты сам за всё отвечаешь», я настоятельно не советую это простое правило нарушать.
Следующий интересный момент — обновление экрана. Не мудрствуя лукаво, я использую здесь самый банальный подход: прямую зависимость скорости змеи от частоты кадров. Два кадра — один ход и одна анимация. При 10 fps получаем 5 ходов за секунду; при 200 fps — 100.
Работает это так: при каждой итерации цикла сохраняется момент времени [541], после чего рассчитывается разность [542] меж ним и моментом, когда в прошлый раз был отрисован кадр. Затем полученная разность сравнивается с кадровой задержкой (задержка = 1 секунда / fps), и если она превышает задержку — кадр обновляется, сохранив новый момент обновления.
FPS не является чётко заданным конкретным числом, а находится как «(уровень + 1) * 5». Иными словами, на первом уровне имеем частоту 10, на втором — 15, на третьем — 20, и так далее.
Кадров бывает два вида: полноценный и анимационный. Определяю я, какой из них надо рисовать, простым флагом animation_frame. В случае анимационного кадра отрисовывается исключительно анимация; в случае полноценного — перерисовывается всё игровое поле, а также генерируется пища, производится по необходимости левел-ап и всё такое.
set_terminal_mode — строка 90
reset_input_mode — строка 74
Здесь происходит терминальная магия. В первой функции устанавливается нужный режим, во второй возвращается старый. В чём дело?
Для работы с параметрами терминала используется набор функций termios. Происходит это так: вынуть нынешние параметры в структуру termios с помощью функции tcgetattr [105], отредактировать её, запихнуть обратно в терминал с помощью tcsetattr [109].
По-умолчанию юниксовый терминал ждёт ввода полной строки (нажатия Return) и позволяет перед вводом эту строку всячески редактировать. Это называется каноническим режимом. Чтобы нажатия передавались нашей игре сразу, без необходимости клацать Enter, необходимо переключить терминал в режим неканонический.
Также по-умолчанию юниксовый терминал сразу выводит любую нажатую клавишу на экран. Это называется эхо-режимом. Чтобы этого не происходило, надо выключить эхо.
Канонический режим и эхо-режим выключаю одновременно [106]. Это битовые флаги, и если вы не понимаете вообще, что там происходит, то советую почитать где-нибудь о побитовых операциях — тема объёмная и полезная.
Устанавливаю VMIN [107] и VTIME [108] в массиве параметров c_cc в нулевые значения.
VMIN отвечает за минимальное количество символов, которого будет дожидаться терминал в неканоническом режиме. Если там будет что-то кроме нуля, любая попытка читать с терминала заблокирует программу до получения VMIN символов. Мне это совершенно не нужно, блокироваться — плохая идея, надо кадры обновлять.
VTIME отвечает за то, сколько (при ненулевом VMIN) терминал будет ожидать символы.
Вообще говоря, достаточно установить в 0 один из этих параметров, но и оба сразу — не повредит.
И в set_terminal_mode, и в reset_terminal_mode имеются очень странные printf. Это — вывод в терминал особых последовательностей байтов, которые терминал, следующий стандарту ANSI, обязан понимать и интерпретировать. Как символы они записаны исключительно для удобства. Полное описание есть на википедии.
Не вдаваясь в подробности, «\x1b[?25l» означает «выключить отображение курсора», а «\x1b[?25h» — «включить».
Интересна также функция atexit [113]. Она регистрирует любую другую функцию в системе так, чтобы та была запущена при корректном выходе из программы. Ключевой момент тут — корректный выход; именно чтобы обеспечить его я перехватываю SIGINT.
Набор функций turn_<direction> — строки со 121 по 219
Эти четыре функции очень схожи между собой. Они устанавливают новое направление головы змеи и рисуют новый сегмент тела на месте головы, если определённый поворот возможен из нынешней позиции. Возвращают int, работающий в качестве булевой переменной (можно было бы обойтись char, но мне почему-то захотелось int).
Рассмотрим на примере turn_right [196]. Определяется нынешнее направление движения [201], и в зависимости от него определяются дальнейшие действия. Если змея двигалась вертикально (вверх [202] или вниз [205]), поворот вправо возможен — turn_right вернёт 1 и установит соответствующий повороту сегмент тела на поле. Если змея и так двигалась вправо или вообще получила неправильный аргумент [210], turn_right просто вернёт 1 и ничего больше не сделает (это нужно для эффекта ускорения при нажатой клавише). Если змея двигалась влево [208], поворот направо невозможен — turn_right вернёт 0.
Если держать нажатой стрелку в какую-то сторону, змейка должна двигаться в эту сторону резвее. Я реализую это поведение очень простым способом: экран обновляется (а соответственно, змейка движется раньше, чем ей положено по таймеру) при каждом успешном нажатии клавиши.
У этого подхода есть только один минус: ускорение змейки зависит от пользовательской настройки, определяющей частоту повторения символа при зажатой клавише. Игра поменять эту настройку не может (да и не должна). Но мы работаем с терминалом, лучшего способа просто нет.
process_key — строка 223
Эта функция считывает с терминала три байта [227] (именно тремя байтами кодируется нажатие клавиши-стрелки). Если эти три байта соответствуют одной из заранее определённых констант [55], вызывается функция соответствующего поворота.
Очень важно, что в данном случае переменная c инициализируется в 0: так четвёртый байт, который не считывается, но всё равно остаётся «болтаться», будет нулевым и не станет мешать. Вместо переменной int можно было использовать массив char, но сравнивать с трёхбайтными константами его намного сложнее, чем простым switch.
Возвращает функция 1 или 0 в зависимости от того, смогла ли сдвинуться змея в ответ на нажатие клавиши.
init_playfield — строка 251
Простенькая функция инициализации игрового поля. Просто забивает его всё пробелами.
init_snake — строка 259
Функция посложнее, инициализация змеи. Голова змеи устанавливается в условный центр поля и изначально смотрит вправо. Ровно в ту же позицию помещается хвост, длина змеи устанавливается в 1 и буфер длины — в 4. Таким образом, начальная длина змейки — 5.
Буфер длины нужен по той простой причине, что за один ход змея способна вырасти ровно на один сегмент, и никак не больше. То есть (теоретический) бонус, сразу дающий 5 очков длины, возможно реализовать только путём буфера, где эта «уже съеденная, но ещё не обработанная» еда будет храниться и «отращиваться» в хвост по одной каждый ход. Пронаблюдать, как именно это работает, можно в функции move_snake.
redraw_all — строка 278
Функция отрисовки игрового поля.
Вначале курсор помещается в стартовую позицию 0, 0 (верхний левый угол) с помощью ещё одного управляющего спец-символа [279]. Далее за несколько циклов выводятся сначала верхняя стенка [281], затем боковые и само поле [286], затем нижняя стенка [295] и немного информации: длина змеи, уровень [300]. Ничего интересного здесь нет; такое писал каждый, кто когда-либо выводил матрицы на экран.
redraw_animation — строка 305
Немного более интересная функция, которая отвечает за анимацию.
С использованием, опять же, спец-символа (но в этот раз динамически сгенерированного) курсор устанавливается на координаты головы змеи [307] (поправки +2 нужны из-за того, что в змее хранятся координаты относительно игрового поля-матрицы, а нужны координаты относительно экрана). Далее вместо головы рисуется «закрытый рот» (просто сегмент тела, соответствующий направлению) [309].
Затем курсор путём всё той же манипуляции со спец-символом устанавливается на позицию змеиного хвоста [317] и с хвостом происходит анимация выпрямления (при условии, что он ещё не прямой), в общем-то идентичная таковой для головы.
move_snake — строка 338
Самая сложная, интересная и важная функция. Перемещает змею в игровом поле и вообще обновляет её состояние.
Если направление змеи не менялось (поля dir и new_dir одинаковы), значит, была нажата клавиша того же направления, либо не была нажата никакая. В обеих этих случаях функции turn_<direction> ничего не рисуют, потому необходимо нарисовать новый сегмент тела [345-350]. Не рисуют turn_* именно потому, что возможных случая тут два (нажата та же, либо не нажата никакая), и во втором случае turn_* вызваны не будут. Получается, для этого второго случая отрисовку внутри move_snake предусматривать всё равно придётся, а раз она тут уже нужна, зачем повторно в turn_*?
Если же направление поменялось, new_dir сохраняется как dir [352].
Далее определяется символ для отображения головы и обновляются её координаты («голова сдвинулась на новое место»). Это зависит исключительно от направления головы и обрабатывается простым switch [357].
Затем новая позиция головы анализируется. Если там на данный момент стена [379], еда [392] или что угодно ещё кроме пробела (это может быть только тело змеи во всех его разнообразных вариациях) [397], выполняются соответствующие действия (game over или съедение еды). Встреча со стеной определяется как выход за рамки позволенных координат (это позволяет, например, очень легко не рисовать стены или заменить символ стены на какой-то ещё, да к тому же позволяет не занимать память зазря символами стен, никогда не меняющимися).
Наконец, после того как все возможные проблемы с новой позицией улажены (и game over не наступил), туда помещается голова змеи [407].
И теперь обрабатывается хвост [411]. Если у змеи не пустой буфер длины, она должна вырасти; в этом случае с хвостом ничего не происходит и он остаётся на старом месте [412]. Если же буфер пустой, всё намного интереснее.
Первым делом хвост заменяется пробелом [415]. Затем координаты хвоста обновляются, чтобы он занял новую позицию: следующий после старого хвоста сегмент тела [418]. Этот switch очень похож на таковой для головы [357], но ничего не рисуется.
Дальнейшая задача — определить новое направление хвоста. Сделать это можно, проанализировав сегмент на новой позиции. Если он прямой, направление хвоста остаётся прежним [437-439]. Если он «/», новое направление определяется в зависимости от старого [441]. Если он «\», направление определяется аналогично, но с другими значениями [359].
Например, если хвост двигался вверх, но выглядит как «/», то дальше ему нужно двигаться вправо.
gen_food — строка 483
Функция генерации пищи. Она управляется константой FOOD_RARITY, определяющей частоту еды на поле как «на FOOD_RARITY доступного пространства должен приходиться 1 юнит пищи». Соответственно, необходимое кол-во еды определяется простым делением [487]. +1 там нужно для округления вверх (то есть чтобы, например, при установленной FOOD_RARITY 512 и размере поля 600, на нём был не 1, а 2 куска еды).
Если еды на поле недостаточно (следить за её количеством можно через переменную food_cnt), генерируется новый кусок [490]. Координаты еды определяются генератором случайных чисел и подгоняются в заданные координатные рамки путём операции суммы по модулю (%), или же остатка. Полученная позиция проверяется, и если она пуста — туда помещается пища, а если занята — генерация выполняется вновь.
Из-за того, что генерироваться еда будет «до упора», в случае, близком к победе (всё поле занято змеёй), игра может начать тормозить, а когда места для необходимого кол-ва еды просто не останется, банально уйдёт в бесконечный цикл. Если вы когда-либо добьётесь такой ситуации — вы победили! И удачи с выключением игры, Ctrl+C не сработает — он перехвачен.
level_up — строка 502
Довольно простая функция, повышающая игровой уровень (а значит — скорость) по достижению змеи очередной LVLUP_LENGTH. Например, в моём случае LVLUP_LENGTH установлена в 50 — так левелап будет происходить каждые 50 очков длины.
Исползуется static (сохраняющая своё значение меж вызовами функции) переменная upped_already, чтобы избежать проблем при нескольких вызовах level_up за те хода, пока длина змеи сохраняется кратной LVLUP_LENGTH. Она устанавливается в 1 после повышения уровня, и в 0, когда длина змеи не соответствует нужному для левел-апа значению. Так как длина может лишь расти, это адекватный подход.
Послесловие
Надеюсь, кому-то было интересно прочитать про то, как можно упорото и хардкорно написать змейку.
Я для себя выношу одну истину: писать посты про свой код очень сложно. Мне было физически тяжело возвращаться к этому посту и пытаться выразить в словах то, что кажется очевидным из структуры программы. Возможно, в другой раз я попробую какой-нибудь иной формат.
Создание игры «Змейка» на C++/Qt5 | Уроки по Qt5
#include <QPainter>
#include <QTime>
#include «Snake.h»
Snake::Snake(QWidget *parent) : QWidget(parent) {
setStyleSheet(«background-color:black;»);
leftDirection = false;
rightDirection = true;
upDirection = false;
downDirection = false;
inGame = true;
resize(B_WIDTH, B_HEIGHT);
loadImages();
initGame();
}
void Snake::loadImages() {
dot.load(«dot.png»);
head.load(«head.png»);
apple.load(«apple.png»);
}
void Snake::initGame() {
dots = 3;
for (int z = 0; z < dots; z++) {
x[z] = 50 — z * 10;
y[z] = 50;
}
locateApple();
timerId = startTimer(DELAY);
}
void Snake::paintEvent(QPaintEvent *e) {
Q_UNUSED(e);
doDrawing();
}
void Snake::doDrawing() {
QPainter qp(this);
if (inGame) {
qp. drawImage(apple_x, apple_y, apple);
for (int z = 0; z < dots; z++) {
if (z == 0) {
qp.drawImage(x[z], y[z], head);
} else {
qp.drawImage(x[z], y[z], dot);
}
}
} else {
gameOver(qp);
}
}
void Snake::gameOver(QPainter &qp) {
QString message = «Game over»;
QFont font(«Courier», 15, QFont::DemiBold);
QFontMetrics fm(font);
int textWidth = fm.width(message);
qp.setPen(QColor(Qt::white));
qp.setFont(font);
int h = height();
int w = width();
qp.translate(QPoint(w/2, h/2));
qp.drawText(-textWidth/2, 0, message);
}
void Snake::checkApple() {
if ((x[0] == apple_x) && (y[0] == apple_y)) {
dots++;
locateApple();
}
}
void Snake::move() {
for (int z = dots; z > 0; z—) {
x[z] = x[(z — 1)];
y[z] = y[(z — 1)];
}
if (leftDirection) {
x[0] -= DOT_SIZE;
}
if (rightDirection) {
x[0] += DOT_SIZE;
}
if (upDirection) {
y[0] -= DOT_SIZE;
}
if (downDirection) {
y[0] += DOT_SIZE;
}
}
void Snake::checkCollision() {
for (int z = dots; z > 0; z—) {
if ((z > 4) && (x[0] == x[z]) && (y[0] == y[z])) {
inGame = false;
}
}
if (y[0] >= B_HEIGHT) {
inGame = false;
}
if (y[0] < 0) {
inGame = false;
}
if (x[0] >= B_WIDTH) {
inGame = false;
}
if (x[0] < 0) {
inGame = false;
}
if(!inGame) {
killTimer(timerId);
}
}
void Snake::locateApple() {
QTime time = QTime::currentTime();
qsrand((uint) time. msec());
int r = qrand() % RAND_POS;
apple_x = (r * DOT_SIZE);
r = qrand() % RAND_POS;
apple_y = (r * DOT_SIZE);
}
void Snake::timerEvent(QTimerEvent *e) {
Q_UNUSED(e);
if (inGame) {
checkApple();
checkCollision();
move();
}
repaint();
}
void Snake::keyPressEvent(QKeyEvent *e) {
int key = e->key();
if ((key == Qt::Key_Left) && (!rightDirection)) {
leftDirection = true;
upDirection = false;
downDirection = false;
}
if ((key == Qt::Key_Right) && (!leftDirection)) {
rightDirection = true;
upDirection = false;
downDirection = false;
}
if ((key == Qt::Key_Up) && (!downDirection)) {
upDirection = true;
rightDirection = false;
leftDirection = false;
}
if ((key == Qt::Key_Down) && (!upDirection)) {
downDirection = true;
rightDirection = false;
leftDirection = false;
}
QWidget::keyPressEvent(e);
}
Это всем знакомая игра «Змейка». Задача игры: как можно больше собрать очков, поедая еду, при этом змейка растёт в длину, что усложняет подход к еде. Если голова змейки врежется в одну из своих частей — игра заканчивает или начинается заново. Игра кроссплатформенная: Windows/Linux с предустановленным GUI GTK+. Алгоритм игры: 1. С помощью таймера вызываем функцию обновления положения змейки. В случае, если координаты головы змейки совпадут с координатами еды — наращиваем змейку и изменяем позицию еды. #include <stdlib. h> #include <gtk/gtk.h> #include <gdk/gdkkeysyms.h> #include <time.h> typedef struct Snake Snake; typedef struct SnakeEat SnakeEat; typedef struct Game Game; #define SCALE 20 //Масштаб игры #define TIME 200 //Время обновления движения змейки - скорость движения /* Каждая часть змейки представляет из себя структуру: хранит свои координаты, указатель на спрайт, и указатели на соседние две части змейки */ struct Snake { int posx, posy; Snake *next, *prev; GdkPixbuf *pSnakeTexture; }; /* Структура, содержащая информацию о еде змейки: координаты расположения и указатель на спрайт */ struct SnakeEat { int posx, posy; GdkPixbuf *pEatTexture; }; /* Основная структура, содержит всю информацию для игры */ struct Game { GtkWidget *window; //окно, где играем GdkPixbuf *BackGroundTexture; //спрайт основного фона GdkFont *ScoreFont; //Шрифт для отображения очков Snake *SnakeHead; //Указатель на голову змейки Snake *SnakeTail; //На последнюю часть змейки SnakeEat *SnakeEat; //На еду змейки unsigned int Score; //Переменная для очков char StateHor, StateVer; //переменные, отвечающие за направление движения змейки char ScoreString[10]; //Строка, для вывода очков на экран gboolean KeyState; //Переменная, блокируящая многоразовое изменение направления движения змейки за одну итерацию, чтобы избежать глюков при движении змейки }; //--- Функия инициализации игры: загружает необходимые изображения, устанавливает начальные данные ---// void Init_Game(GtkWidget *wid, Game *game) { game->window = wid; game->BackGroundTexture = gdk_pixbuf_new_from_file("img/BackGround. png", NULL); game->StateHor = 0; game->StateVer = -SCALE; game->Score = 0; game->SnakeEat = (SnakeEat *) malloc(sizeof(SnakeEat)); game->SnakeEat->pEatTexture = gdk_pixbuf_new_from_file("img/eat.png", NULL); Snake *hSnake = (Snake *) malloc(sizeof(Snake)); hSnake->pSnakeTexture = gdk_pixbuf_new_from_file("img/head.png", NULL); hSnake->prev = NULL; hSnake->posx = 260; hSnake->posy = 260; game->SnakeHead = game->SnakeTail = hSnake; AddNewPartToSnake( game ); AddNewPartToSnake( game ); SetSnakeEatPos( game ); } //--- Функция, удаляющая всю змейку змейки ---// void DestroySnake(Game *cgame) { if( cgame->SnakeHead ) { Snake *cur, *temp; cur = cgame->SnakeHead; while( cur ) { temp = cur->next; free( cur ); cur = temp; } } } //--- Функция, удаляющая све данные игры ---// void DestroyGame(Game *cgame) { if( cgame ) { DestroySnake ( cgame ); DestroySnakeEat( cgame ); free( cgame ); } } //--- Функция, удаляющая объект еды--// void DestroySnakeEat(Game *cgame) { if( cgame->SnakeEat ) { free( cgame->SnakeEat ); } } //--- Фунция, добавляющая новую часть к змейке ---// void AddNewPartToSnake(Game *cgame) { Snake *NewPart = (Snake *) malloc(sizeof(Snake)); NewPart->pSnakeTexture = gdk_pixbuf_new_from_file("img/part. png", NULL); NewPart->prev = cgame->SnakeTail; NewPart->posx = NewPart->prev->posx; NewPart->posy = NewPart->prev->posy; NewPart->next = NULL; cgame->SnakeTail->next = NewPart; cgame->SnakeTail = NewPart; } //--- Установка новой позиции еды змейки void SetSnakeEatPos(Game *cgame) { cgame->SnakeEat->posx = rand() % 25 * SCALE; //рендомно определяем координаты cgame->SnakeEat->posy = rand() % 25 * SCALE; //--- Ниже циклом проверяем, чтобы еда не создалась там, где сейчас находится змейка, в случае если находится - вызываем функцию опять ---// Snake *cSnake = cgame->SnakeHead; while( cSnake ) { if( ( cgame->SnakeEat->posx == cSnake->posx && cgame->SnakeEat->posy == cSnake->posy ) || cgame->SnakeEat->posy < 20 ) { SetSnakeEatPos( cgame ); break; } cSnake = cSnake->next; } } //--- Функция коллизии, проверяет на столконовение головы со своими частями тела ---// //--- Возвращает TRUE - если врезалась, FALSE - если нет gboolean Collision(Game *cgame) { Snake *CurSnake = cgame->SnakeHead->next; while( CurSnake ) { if( CurSnake->posx == cgame->SnakeHead->posx && CurSnake->posy == cgame->SnakeHead->posy ) return TRUE; CurSnake = CurSnake->next; } return FALSE; } //--- Функция прорисовки текста, в данном случае очков, на определённых координатах x, y ---// void DrawScoreText(Game *game, int x, int y) { GdkColor Color; //структура, содержит в себе описание цвета (RGBA) /* Ниже создаём указатель на текущий стиль виджета с целью его преобразования для вывода очков на экран */ GdkGC *GC = game->window->style->fg_gc[ GTK_WIDGET_STATE (game->window) ]; gdk_color_parse("#FF6600", &Color); //Закидываем RGB-Цвет в структуру Color gdk_gc_set_rgb_fg_color(GC, &Color); //Устанавливаем новый цвет в наш стиль sprintf(game->ScoreString, "SCORE: %d", game->Score ); //Формируем в строку текст с нашими очками gdk_draw_string(game->window->window, game->ScoreFont, GC, x, y, game->ScoreString); //выводим на экран в опр. координатах с выше полученным стилей строку с очками } //--- Функция, в которой идёт прорисовка всей графической части игры gboolean Draw_Field(GtkWidget *win, GdkEventExpose *event, gpointer data) { Game *game = (Game *) data; Snake *cSnake = game->SnakeTail; gdk_draw_pixbuf(win->window, //Задний фон рисуем win->style->black_gc, game->BackGroundTexture, 0, 0, 0, 0, 500, 500, GDK_RGB_DITHER_NONE, 0, 0); gdk_draw_pixbuf(win->window, //еда win->style->black_gc, game->SnakeEat->pEatTexture, 0, 0, game->SnakeEat->posx, game->SnakeEat->posy, 20, 20, GDK_RGB_DITHER_NONE, 0, 0); while( cSnake ) { //Все части змейки gdk_draw_pixbuf(win->window, win->style->black_gc, cSnake->pSnakeTexture, 0, 0, cSnake->posx, cSnake->posy, 20, 20, GDK_RGB_DITHER_NONE, 0, 0); cSnake = cSnake->prev; } DrawScoreText(game, 10, 16); //Вывод очков gtk_widget_queue_draw(game->window); //Стираем то, что выводили выше для обновления return TRUE; //TRUE - продолжать обрабатывать события прорисовки виджета FALSE - закончить } //--- ---// //--- Функция, обрабатывающая нажатие клавиш gboolean KeyPress(GtkWidget *wid, GdkEventKey *event, gpointer data) { Game *cgame = (Game *) data; if( cgame->KeyState ) //Если клавишу уже нажали многократно, то просто выходим return TRUE; switch( event->keyval ) { //Проверяем что за клавиша была нажата case GDK_Right: //Вправо if( !cgame->StateHor ) { cgame->StateHor = SCALE; cgame->StateVer = 0; } break; case GDK_Left: //Влево if( !cgame->StateHor ) { cgame->StateHor = -SCALE; cgame->StateVer = 0; } break; case GDK_Up: //Вверх if( !cgame->StateVer ) { cgame->StateVer = -SCALE; cgame->StateHor = 0; } break; case GDK_Down: //Вниз if( !cgame->StateVer ) { cgame->StateVer = SCALE; cgame->StateHor = 0; } break; case GDK_Return: //И ентер, чтобы начать играть заного DestroyGame(cgame); //Удаляем предыдущую игры, чтобы не было утечки памяти Init_Game(wid, cgame); //Создаём новую break; } cgame->KeyState = TRUE; //Устанавливаем флаг, что клавишу уже нажимали return TRUE; } //--- Функция обрабатывает все действия змейки, обрабатывает устанавку позиции еды gboolean Engine(gpointer data) { Game *cgame = (Game *) data; Snake *CurSnake = cgame->SnakeTail; while( CurSnake != cgame->SnakeHead ) { //Изменяет позицию каждой части змейки начиная с хвоста, на место элемента который перед текущим CurSnake->posx = CurSnake->prev->posx; CurSnake->posy = CurSnake->prev->posy; CurSnake = CurSnake->prev; } cgame->KeyState = FALSE; //Обнуляем флаг нажатия клавиши //--- Изменяем позицию головы змейки, исходя из параметров, полученных при нажатии клавиши ---// CurSnake->posx += cgame->StateHor; CurSnake->posy += cgame->StateVer; //--- Здесь проверка на границы окна, чтобы она перемещалась с одного конца на другой ---// if( CurSnake->posx < 0 ) CurSnake->posx = 480; if( CurSnake->posx > 480 ) CurSnake->posx = 0; if( CurSnake->posy > 480 ) CurSnake->posy = 20; if( CurSnake->posy < 20 ) CurSnake->posy = 480; //--- Проверяем если змейка врезалась в свою часть - начинаем заного игру ---// if( Collision( cgame ) ) { DestroyGame(cgame); Init_Game( cgame->window, cgame ); } //--- Проверка, если голова змеи на тех же координатах, что и еда, то меняем позицию еды и наращиваем змейку, увеличиваем очки ---// if(CurSnake->posx == cgame->SnakeEat->posx && CurSnake->posy == cgame->SnakeEat->posy) { AddNewPartToSnake( cgame ); SetSnakeEatPos( cgame ); //!! cgame->Score++; } return TRUE; //TRUE - продолжить работу таймеры по вызову функции Engine(), FALSE - удалить таймер } int main (int argc, char *argv[]) { srand( time( 0 ) ); //запускаем генератор случайных чисел GtkWidget *win = NULL; //Создаём виджет, в котором будет хранится наше окно Game *cgame = (Game *) malloc(sizeof( Game )); //выделяем динамически gfvznm под структуру игры gtk_init (&argc, &argv); //Инициализация GTK //--- Создание окна ---// win = gtk_window_new (GTK_WINDOW_TOPLEVEL); //создаём окно поверх всех запущенных окон, за это отвечает макрос GTK_WINDOW_TOPLEVEL gtk_window_set_title (GTK_WINDOW (win), "Snake"); //Выставляем заголов окна на "Snake" gtk_widget_set_size_request (win, 500, 500); //устанавливаем фиксированные размеры окна gtk_window_set_position (GTK_WINDOW (win), GTK_WIN_POS_CENTER); //устанавливаем окно на мониторе по центру, опять-таки макрос GTK_WIN_POS_CENTER за это отвечает //--- Функция выставляет свойство окна, при которых, окно можно либо растягивать, TRUE - можно растягивать, FALSE - нет ---// gtk_window_set_resizable (GTK_WINDOW(win), FALSE); //--- В Linux и Windows разные принципы загрузки шрифтов, и чтобы игра могла скомпилироваться без ошибок на обоих системах - использую препроцессорные команды для проверки ОС ---// #if defined _WIN32 cgame->ScoreFont = gdk_font_load ("Arial"); #else cgame->ScoreFont = gdk_font_load ("-*-helvetica-bold-r-*-*-*-160-*-*-*-*-*-*"); #endif //конец команды препроцессора Init_Game(win, cgame); //Инициализируем игру, функция расписана выше //--- Создание событий обработки окна, перерисовки окна, нажатия клавиш ---// g_signal_connect_data (win, "destroy", gtk_main_quit, NULL, NULL, 0); g_signal_connect_data (win, "expose_event", Draw_Field, (gpointer)cgame, NULL, 0); g_signal_connect_data (win, "key_press_event", KeyPress, (gpointer)cgame, NULL, 0); //--- Создаём таймер для вызова функции обновления позиций змейки, еды ---// g_timeout_add( TIME, Engine, (gpointer) cgame ); gtk_widget_realize (win); //Уже расписывал фунцию gtk_widget_show_all (win); //Отображает все виджеты на экран //--- Запускаем бесконечный цикл GTK, который будет обрабатывать все описанные выше события, и отображать виджет окна и всё что в нём gtk_main (); //--- После завершения бесконечного цикла, в результате выполнения события destroy, очищаем оперативную память от структур игры DestroyGame ( cgame ); // --- // return 0; } Ключевые слова: игра змейка, gtk+, змейка на си, c/c++
|
Змейка на Python
Я точно знаю, что в детстве вы все играли в игру «Змейка» и, конечно, всегда хотели выиграть. Будучи детьми, мы все любили искать баги в игре, чтобы никогда не видеть сообщение Game over, но сейчас, будучи технарями, нам наверняка хочется сделать игру так, чтобы комар носа не подточил. Именно об этом и пойдет речь в нашей статье.
Перед тем как двигаться дальше, давайте посмотрим на основные разделы нашей статьи:
- Установка Pygame.
- Создание экрана.
- Создание змейки.
- Движение змейки.
- «Game over» при достижении змейкой границы.
- Добавление еды.
- Увеличение длины змейки.
- Вывод счета на экран.
Установка Pygame
Первое, что нам надо сделать, это установить библиотеку Pygame. Это можно сделать, просто выполнив следующую команду:
pip install pygame
Выполнив это, просто импортируем эту библиотеку и приступим к разработке игры. Но перед этим давайте взглянем на основные функции данной библиотеки, которые мы будем использовать при создании игры.
Функция | Описание |
init() | Инициализирует все модули Pygame (возвращает кортеж в случае успеха или неудачи). |
display.set_mode() | Для создания поверхности принимает в качестве параметра либо список либо кортеж (кортеж предпочтительней). |
update() | Обновляет экран. |
quit() | Используется для деинициализации всех модулей. |
set_caption() | Устанавливает текст заголовка в верхней части экрана |
event.get() | Возвращает список всех событий. |
Surface.fill() | Заполняет пространство сплошным цветом. |
time.Clock() | Отслеживание времени |
font. SysFont() | Задает шрифт Pygame, используя системные ресурсы. |
Создание экрана
Для создания экрана при помощи Pygame нужно воспользоваться функцией display.set_mode()
. Также необходимо пользоваться методом init()
для инициализации экрана в начале кода и методом quit()
для его закрытия в конце. Метод update()
используется для применения каких-либо изменений на экране. Еще существует метод flip()
, который работает похожим с update()
образом. Разница заключается лишь в том, что метод flip()
переписывает весь экран целиком, а метод update()
применяет именно изменения (хотя если его использовать без параметров, то он тоже переписывает весь экран) .
import pygame pygame. init() dis=pygame.display.set_mode((400,300)) pygame.display.update() pygame.quit() quit()
Результат:
Однако, когда вы запустите данный код, экран появится лишь на мгновение, а затем исчезнет. Чтобы исправить эту ошибку, мы воспользуемся циклом while
, который будет работать до окончания игры:
import pygame pygame.init() dis=pygame.display.set_mode((400,300)) pygame.display.update() pygame.display.set_caption('Snake game by Pythonist') game_over=False while not game_over: for event in pygame.event.get(): print(event) # выводит на экран все действия игры pygame.quit() quit()
Теперь, запустив этот код, вы увидите, что экран не пропадает, как раньше. На нем будут отображаться все действия игры. Мы этого добились благодаря функции event. get()
. Также, при помощи функции display.set_caption()
, мы вывели заголовок нашего экрана — ‘Snake game by Pythonist’.
Теперь у нас есть экран для игры, но когда вы кликнете по кнопке close, экран не закроется. Это потому, что мы не предусмотрели такого поведения. Для решения этой задачи в Pygame предусмотрено событие «QIUT», которое мы используем слеудющим образом:
import pygame pygame.init() dis=pygame.display.set_mode((400,300)) pygame.display.update() pygame.display.set_caption('Snake game by Edureka') game_over=False while not game_over: for event in pygame.event.get(): if event.type==pygame.QUIT: game_over=True pygame.quit() quit()
Теперь, когда наш экран полностью подготовлен, нам предстоит нарисовать на нем змейку. Этому посвящен следующий раздел.
Создание змейки
Перед тем как создать змейку, мы инициируем несколько цветовых переменных для раскрашивания самой змейки, еды и экрана. В Pygame используется цветовая схема RGB (RED, GREEN, BLUE). Установка всех цветов в 0
соответствует черному цвету, а в 255
— соответственно, белому.
Фактически, наша змейка является прямоугольником. Чтобы нарисовать прямоугольник в Pygame, можно воспользоваться функцией draw.rect()
, которая нарисует нам прямоугольник заданного цвета и размера.
import pygame pygame.init() dis=pygame.display.set_mode((400,300)) pygame.display.set_caption('Snake game by Pythonist') blue=(0,0,255) red=(255,0,0) game_over=False while not game_over: for event in pygame. event.get(): if event.type==pygame.QUIT: game_over=True pygame.draw.rect(dis,blue,[200,150,10,10]) pygame.display.update() pygame.quit() quit()
Результат:
Как можно увидеть, змейка создана в виде голубого прямоугольника. Теперь нам нужно научить ее двигаться.
Движение змейки
Чтобы передвигать змейку, мы будем использовать ключевые события из класса KEYDOWN
библиотеки Pygame. События K_UP
, K_DOWN
, K_LEFT
, и K_RIGHT
заставят змейку двигаться вверх, вниз, влево и вправо соответственно. Также, цвет дисплея меняется от черного (по умолчанию) до белого при помощи метода fill()
.
Для сохранения изменений координат x
и y
мы создали две новых переменные: x1_change
и y1_change
.
import pygame pygame. init() white = (255, 255, 255) black = (0, 0, 0) red = (255, 0, 0) dis = pygame.display.set_mode((800, 600)) pygame.display.set_caption('Snake game by Pythonist') game_over = False x1 = 300 y1 = 300 x1_change = 0 y1_change = 0 clock = pygame.time.Clock() while not game_over: for event in pygame.event.get(): if event.type == pygame.QUIT: game_over = True if event.type == pygame.KEYDOWN: if event.key == pygame.K_LEFT: x1_change = -10 y1_change = 0 elif event.key == pygame.K_RIGHT: x1_change = 10 y1_change = 0 elif event.key == pygame.K_UP: y1_change = -10 x1_change = 0 elif event.key == pygame.K_DOWN: y1_change = 10 x1_change = 0 x1 += x1_change y1 += y1_change dis.fill(white) pygame.draw.rect(dis, black, [x1, y1, 10, 10]) pygame.display.update() clock.tick(30) pygame.quit() quit()
Результат:
«Game over» при достижении змейкой границы
В игре змейка игрок проигрывает, если касается границы экрана. Чтобы задать такое поведение, мы должны использовать инструкцию if
, которая будет следить за тем, чтобы координаты x и y были меньше размеров экрана. Мы будем использовать для этого переменные, чтобы вы могли потом, при случае, легко внести какие-либо изменения в игру.
import pygame import time pygame.init() white = (255, 255, 255) black = (0, 0, 0) red = (255, 0, 0) dis_width = 800 dis_height = 600 dis = pygame.display.set_mode((dis_width, dis_width)) pygame.display.set_caption('Snake game by Pythonist') game_over = False x1 = dis_width/2 y1 = dis_height/2 snake_block=10 x1_change = 0 y1_change = 0 clock = pygame.time.Clock() snake_speed=30 font_style = pygame.font.SysFont(None, 50) def message(msg,color): mesg = font_style.render(msg, True, color) dis.blit(mesg, [dis_width/2, dis_height/2]) while not game_over: for event in pygame.event.get(): if event.type == pygame.QUIT: game_over = True if event.type == pygame.KEYDOWN: if event.key == pygame.K_LEFT: x1_change = -snake_block y1_change = 0 elif event.key == pygame.K_RIGHT: x1_change = snake_block y1_change = 0 elif event.key == pygame.K_UP: y1_change = -snake_block x1_change = 0 elif event.key == pygame.K_DOWN: y1_change = snake_block x1_change = 0 if x1 >= dis_width or x1 < 0 or y1 >= dis_height or y1 < 0: game_over = True x1 += x1_change y1 += y1_change dis.fill(white) pygame.draw.rect(dis, black, [x1, y1, snake_block, snake_block]) pygame.display.update() clock.tick(snake_speed) message("You lost",red) pygame.display.update() time.sleep(2) pygame.quit() quit()
Результат:
Добавление еды
Теперь мы добавим немного еды для змейки, и когда она будет ее пересекать, мы будем выводить сообщение «Yummy!!”. Помимо этого, мы внесем небольшие изменения, которые дадут возможность игроку прекращать игру, а также начинать ее заново в случае поражения.
import pygame import time import random pygame.init() white = (255, 255, 255) black = (0, 0, 0) red = (255, 0, 0) blue = (0, 0, 255) dis_width = 800 dis_height = 600 dis = pygame.display.set_mode((dis_width, dis_height)) pygame.display.set_caption('Snake Game by Edureka') clock = pygame.time.Clock() snake_block = 10 snake_speed = 30 font_style = pygame.font.SysFont(None, 30) def message(msg, color): mesg = font_style.render(msg, True, color) dis.blit(mesg, [dis_width/3, dis_height/3]) def gameLoop(): # creating a function game_over = False game_close = False x1 = dis_width / 2 y1 = dis_height / 2 x1_change = 0 y1_change = 0 foodx = round(random.randrange(0, dis_width - snake_block) / 10.0) * 10.0 foody = round(random.randrange(0, dis_width - snake_block) / 10.0) * 10.0 while not game_over: while game_close == True: dis.fill(white) message("You Lost! Press Q-Quit or C-Play Again", red) pygame.display.update() for event in pygame.event.get(): if event.type == pygame.KEYDOWN: if event.key == pygame.K_q: game_over = True game_close = False if event.key == pygame.K_c: gameLoop() for event in pygame.event.get(): if event.type == pygame.QUIT: game_over = True if event.type == pygame.KEYDOWN: if event.key == pygame.K_LEFT: x1_change = -snake_block y1_change = 0 elif event.key == pygame.K_RIGHT: x1_change = snake_block y1_change = 0 elif event.key == pygame.K_UP: y1_change = -snake_block x1_change = 0 elif event.key == pygame.K_DOWN: y1_change = snake_block x1_change = 0 if x1 >= dis_width or x1 < 0 or y1 >= dis_height or y1 < 0: game_close = True x1 += x1_change y1 += y1_change dis.fill(white) pygame.draw.rect(dis, blue, [foodx, foody, snake_block, snake_block]) pygame.draw.rect(dis, black, [x1, y1, snake_block, snake_block]) pygame.display.update() if x1 == foodx and y1 == foody: print("Yummy!!") clock.tick(snake_speed) pygame.quit() quit() gameLoop()
Результат:
Terminal:
Увеличение длины змейки
Следующий код будет увеличивать длину змейки после поглощения ею еды. Также, если змейка сталкивается с собственным хвостом, игра заканчивается и выводится сообщение: “You Lost! Press Q-Quit or C-Play Again“. Длина змейки хранится в списке, а базовые значения заданы в следующем коде.
import pygame import time import random pygame.init() white = (255, 255, 255) yellow = (255, 255, 102) black = (0, 0, 0) red = (213, 50, 80) green = (0, 255, 0) blue = (50, 153, 213) dis_width = 600 dis_height = 400 dis = pygame.display.set_mode((dis_width, dis_height)) pygame.display.set_caption('Snake Game by Pythonist') clock = pygame.time.Clock() snake_block = 10 snake_speed = 15 font_style = pygame.font.SysFont("bahnschrift", 25) score_font = pygame.font.SysFont("comicsansms", 35) def our_snake(snake_block, snake_list): for x in snake_list: pygame.draw.rect(dis, black, [x[0], x[1], snake_block, snake_block]) def message(msg, color): mesg = font_style.render(msg, True, color) dis.blit(mesg, [dis_width / 6, dis_height / 3]) def gameLoop(): game_over = False game_close = False x1 = dis_width / 2 y1 = dis_height / 2 x1_change = 0 y1_change = 0 snake_List = [] Length_of_snake = 1 foodx = round(random.randrange(0, dis_width - snake_block) / 10.0) * 10.0 foody = round(random.randrange(0, dis_height - snake_block) / 10.0) * 10.0 while not game_over: while game_close == True: dis.fill(blue) message("You Lost! Press C-Play Again or Q-Quit", red) pygame.display.update() for event in pygame.event.get(): if event.type == pygame.KEYDOWN: if event.key == pygame.K_q: game_over = True game_close = False if event.key == pygame.K_c: gameLoop() for event in pygame.event.get(): if event.type == pygame.QUIT: game_over = True if event.type == pygame.KEYDOWN: if event.key == pygame.K_LEFT: x1_change = -snake_block y1_change = 0 elif event.key == pygame.K_RIGHT: x1_change = snake_block y1_change = 0 elif event.key == pygame.K_UP: y1_change = -snake_block x1_change = 0 elif event.key == pygame.K_DOWN: y1_change = snake_block x1_change = 0 if x1 >= dis_width or x1 < 0 or y1 >= dis_height or y1 < 0: game_close = True x1 += x1_change y1 += y1_change dis.fill(blue) pygame.draw.rect(dis, green, [foodx, foody, snake_block, snake_block]) snake_Head = [] snake_Head.append(x1) snake_Head.append(y1) snake_List.append(snake_Head) if len(snake_List) > Length_of_snake: del snake_List[0] for x in snake_List[:-1]: if x == snake_Head: game_close = True our_snake(snake_block, snake_List) pygame.display.update() if x1 == foodx and y1 == foody: foodx = round(random.randrange(0, dis_width - snake_block) / 10.0) * 10.0 foody = round(random.randrange(0, dis_height - snake_block) / 10.0) * 10.0 Length_of_snake += 1 clock.tick(snake_speed) pygame.quit() quit() gameLoop()
Результат:
Вывод счета на экран
И последнее, но весьма важное дело: вам нужно отображать счет игрока. Для этого мы создали функцию Your_score
. Это функция будет показывать размер змейки за вычетом 1
(так как это начальный размер змейки).
import pygame import time import random pygame.init() white = (255, 255, 255) yellow = (255, 255, 102) black = (0, 0, 0) red = (213, 50, 80) green = (0, 255, 0) blue = (50, 153, 213) dis_width = 600 dis_height = 400 dis = pygame.display.set_mode((dis_width, dis_height)) pygame.display.set_caption('Snake Game by Pythonist') clock = pygame.time.Clock() snake_block = 10 snake_speed = 15 font_style = pygame.font.SysFont("bahnschrift", 25) score_font = pygame.font.SysFont("comicsansms", 35) def Your_score(score): value = score_font.render("Your Score: " + str(score), True, yellow) dis.blit(value, [0, 0]) def our_snake(snake_block, snake_list): for x in snake_list: pygame.draw.rect(dis, black, [x[0], x[1], snake_block, snake_block]) def message(msg, color): mesg = font_style.render(msg, True, color) dis.blit(mesg, [dis_width / 6, dis_height / 3]) def gameLoop(): game_over = False game_close = False x1 = dis_width / 2 y1 = dis_height / 2 x1_change = 0 y1_change = 0 snake_List = [] Length_of_snake = 1 foodx = round(random.randrange(0, dis_width - snake_block) / 10.0) * 10.0 foody = round(random.randrange(0, dis_height - snake_block) / 10.0) * 10.0 while not game_over: while game_close == True: dis.fill(blue) message("You Lost! Press C-Play Again or Q-Quit", red) Your_score(Length_of_snake - 1) pygame.display.update() for event in pygame.event.get(): if event.type == pygame.KEYDOWN: if event.key == pygame.K_q: game_over = True game_close = False if event.key == pygame.K_c: gameLoop() for event in pygame.event.get(): if event.type == pygame.QUIT: game_over = True if event.type == pygame.KEYDOWN: if event.key == pygame.K_LEFT: x1_change = -snake_block y1_change = 0 elif event.key == pygame.K_RIGHT: x1_change = snake_block y1_change = 0 elif event.key == pygame.K_UP: y1_change = -snake_block x1_change = 0 elif event.key == pygame.K_DOWN: y1_change = snake_block x1_change = 0 if x1 >= dis_width or x1 < 0 or y1 >= dis_height or y1 < 0: game_close = True x1 += x1_change y1 += y1_change dis.fill(blue) pygame.draw.rect(dis, green, [foodx, foody, snake_block, snake_block]) snake_Head = [] snake_Head.append(x1) snake_Head.append(y1) snake_List.append(snake_Head) if len(snake_List) > Length_of_snake: del snake_List[0] for x in snake_List[:-1]: if x == snake_Head: game_close = True our_snake(snake_block, snake_List) Your_score(Length_of_snake - 1) pygame.display.update() if x1 == foodx and y1 == foody: foodx = round(random.randrange(0, dis_width - snake_block) / 10.0) * 10.0 foody = round(random.randrange(0, dis_height - snake_block) / 10.0) * 10.0 Length_of_snake += 1 clock.tick(snake_speed) pygame.quit() quit() gameLoop()
Результат:
Вот мы и дошли до конца нашей статьи. Мы надеемся, что она вам понравилась и все было понятно. Самое главное, не забывайте как можно больше практиковаться и использовать ваши знания на практике.
Хотите решать больше задач по Python
Подписывайтесь на нас в Телеграм
Подписаться
×
Змейка — Python documentation
import pygame import random import sys # Размеры окна в пикселях WINDOW_WIDTH = 640 WINDOW_HEIGHT = 480 CELL_SIZE = 20 # Размеры сетки в ячейках WIDTH = int(WINDOW_WIDTH / CELL_SIZE) HEIGHT = int(WINDOW_HEIGHT / CELL_SIZE) # Цвета BG_COLOR = (0, 0, 0) GRID_COLOR = (40, 40, 40) APPLE_COLOR = (255, 0, 0) APPLE_OUTER_COLOR = (155, 0, 0) SNAKE_COLOR = (0, 255, 0) SNAKE_OUTER_COLOR = (0, 155, 0) UP = 'up' DOWN = 'down' LEFT = 'left' RIGHT = 'right' HEAD = 0 FPS = 15 class Cell: def __init__(self, x, y): self.x = x self.y = y def main(): global FPS_CLOCK global DISPLAY pygame.init() FPS_CLOCK = pygame.time.Clock() DISPLAY = pygame.display.set_mode((WINDOW_WIDTH, WINDOW_HEIGHT)) pygame.display.set_caption('Wormy') while True: # Мы всегда будем начинать игру с начала. После проигрыша сразу # начинается следующая. run_game() def run_game(): # TODO(2.1): создать яблоко в позиции (20, 10) # TODO(3.1): создать змейку. Пусть она состоит из трех ячеек # в строке 10 и столбцах 3, 4, 5. # Какой тип данных удобен для представления змейки? # TODO(4.1): задать исходное направление движения змейки. while True: for event in pygame.event.get(): if event.type == pygame.QUIT: terminate() # TODO(7.1): обработайте событие pygame.KEYDOWN # и при необходимости измените направление движения змейки. # TODO(5.1): если змейка достигла границы окна, завершить игру. # Для проверки воспользуйтесь функцией snake_hit_edge. # TODO(8.1): если змейка задела свой хвост, завершить игру. # Для проверки восппользуйтесь функцией snake_hit_self. # TODO(6.1): обработайте ситуацию столкновения змейки с яблоком. # В этом случае нужно: # * Увеличить размер змейки # * Создать новое яблоко. # TODO(4.2): сдвинуть змейку в заданном направлении # TODO(2.2): передать яблоко в функцию отрисовки кадра # TODO(3.2): передать змейку в функцию отрисовки кадра draw_frame(None, None) FPS_CLOCK.tick(FPS) def draw_frame(snake, apple): DISPLAY.fill(BG_COLOR) draw_grid() draw_snake(snake) draw_apple(apple) pygame.display.update() def draw_grid(): # TODO(1.2): нарисовать сетку. # Шаг CELL_SIZE # Цвет GRID_COLOR # https://www.pygame.org/docs/ref/draw.html#pygame.draw.line ... def draw_apple(apple): # TODO(2.3): нарисовать яблоко. ... def draw_snake(snake): # TODO(3.3): нарисовать змейку. ... def draw_cell(cell, outer_color, inner_color): # TODO(2.4): вспомогательная функция для рисования ячейки. # Ячейка будет состоять из двух квадратов разных цветов: # Больший квадрат закрашивается цветом outer_color, # меньший - inner_color. # Расстояние между внешним и внутренним квадратом # принять за 4 пикселя. ... def move_snake(): # TODO(4.3): реализовать перемещение змейки на одну ячейку # в заданном направлении. # * Какие параметры будет принимать функция? # * Из каких действий будет состоять перемещение змейки? ... def get_snake_new_head(): # TODO(4.4): реализовать функцию определения нового # положения головы змейки. # * Какие параметры будет принимать функция? # * Что функция будет возвращать? ... def snake_hit_edge(): # TODO(5.2): функция возвращает True, # если змейка пересекла одну из границ окна. ... def snake_hit_apple(): # TODO(6.2): функция возвращает True, если голова # змейки находится в той же ячейке, что и яблоко. ... def snake_grow(): # TODO(6.3): предложите максимально простой # способ увеличения длины змейки. ... def new_apple(): # TODO(6.4): функция возвращает случайную ячейку, # в которой будет располагаться яблоко. # Для генерации случайной координаты воспользуйтесь # функцией random.randint(a, b) ... def get_direction(): # TODO(7.3): функция возвращает направление движения # в зависимости от нажатой клавиши: # * pygame.K_LEFT # * pygame.K_RIGHT # * pygame.K_UP # * pygame.K_DOWN # Если нажата клавиша противоположного направления движения, # то не менять направление. ... def snake_hit_self(): # TODO(8.2): функция возвращает True, если голова змеи # пересеклась хотя бы с одним блоком хвоста. ... def terminate(): pygame.quit() sys.exit() if __name__ == '__main__': main()
Змейка на С++ : создаем первую игру.ЧАСТЬ 1. | PORTALITY
Давайте подробно разберем создание простой консольной игры языке на Visual Studio 2019.
ВВЕДЕНИЕ
Учиться легче, когда вы имеете дело с играми. Создав несложную игру на любом языке программирования, вы получите отличный опыт, который пригодится вам в будущем, если вы собираетесь стать программистом.
Для создания вам необходимы базовые знания языка. Однако, даже если вы имеете мало опыта программирования на C++, я постараюсь подробно описать основные моменты разработки.
Для работы с C++ вам нужно скачать Visual Studio — это среда , разработанная компанией Microsoft.
После скачивания вы создаете пустой проект и начинаете писать программный код , в котором вы заложите отрисовку , логику, управление нашей Змейки.
НАЧАЛО.
Сразу скажу, что на создание даже такой простой игры как Змейка у малоопытного программиста может уйти немало времени, но по завершении создания вы сможете неплохо продвинуться в этой сфере и , возможно , полюбить программирование. Сам процесс создания я буду излагать в нескольких статьях на моем канале.
ЧАСТЬ 1:
Окей, для начала подключим к нашей программе основную библиотеку для ввода и вывода ( Вывод осуществляется, как правило, с помощью перегруженного оператора сдвига влево (<<), а ввод – с помощью оператора сдвига вправо (>>)) Это основная библиотека, только благодаря в дальнейшем мы сможем отрисовать карту для игры, саму змейку и фрукты, появляющиеся на карте.
Дальше мы создаем основные функции. Отдельные функции будут отвечать за логику, отрисовку и управление игры. Название каждой функции будет говорить за ее назначение в программе.
Так, функция Setup (англ.установить) будет содержать в себе переменные, благодаря которым мы зададим змейке начальное положение и настроим случайное появление фрукта на карте.
Функция Draw (англ.рисовать) будет отвечать за отрисовку карты, змейки и фрукта в консоли с ее постоянным обновлением(анимацией).
Функция Input (англ.ввод) будет отвечать за управление нашей змейкой, которое заключается в назначении клавиш, отвечающих за перемещение.
Функция Logic (англ.логика) будет содержать переменные, описывающие логику игры (конец игры, если змейка врежется в стенку, увеличение длины тела при поедании фрукта и т.п.)
Ну и основная функция main будет включать в себя все функции для отображения игры на консоли до тех пор пока игра не завершена.
Как вы уже заметили мы ввели логическую переменную GameOver (конец игры), пока она принимает значения False(ложь) игра будет продолжаться, поэтому все функции будут выполняться.
Теперь нам надо заполнить каждую функцию, чтобы они имели смысл. Но перед тем, как заполнять одну из функций введем глобальные переменные:
const int width (ширина ) и (height) можно поставить любые значения, эти константы зададут размер игрового поля для змейки. При тесте игры мы сможем в любой момент изменить эти параметры не меняя логику и целостность игры.
Параметры x,y будут отвечать за положение змейки на горизонтали и вертикали ( т.к. змейка в двухмерном пространстве(на плоскости)) , аналогично параметры fruitX и fruitY будут отвечать за положение фрукта.
Дальше идет перечисление enum (англ. enumeration) / с помощью перечислений мы присвоим параметрам Stop, LEFT,RIGHT,UP,DOWN целые значения( первому параметру Stop присвоится 0, второму LEFT — 1, и т.д. DOWN — 4)
проверяем что DOWN действительно = 4
Переходим в функцию Setup.
Изначально игра не окончена, поэтому GameOver = false; , иначе при запуске консоли программа завершит процесс выполнения. eDirection или dir (направление при запуске игры будет 0, то есть змейка будет стоять на месте, 0 в перечислениях принимал параметр Stop. x,y отвечают за положение змейки, при делении высоты и ширины пополам мы получим, что змейка будет находиться в центре игрового поля при запуске. Положение фрукта же случайно, поэтому приплетаем функцию rand() с остатком от деления на высоту вертикальной составляющей и с остатком от деления на ширину горизонтальной составляющей, так фрукт будет появляться в случайном месте на карте. Score( счетчик очков ) изначально равен нулю. Тут все ясно.
Поставили змейку в центр, фрукт в случайное место.
Сразу перейдем к функции Draw для отрисовки карты и объектов на консоли. Пожалуй, это самая сложная и интересная функция. Для того, чтобы картинка стала обновляться и создалась анимация движения нам необходимо прописать команду system(«cls»); Дальше заполняем первую верхнюю строчку(верхний край) нашего игрового поля. Это можно реализовать с помощью цикла for. Верхний край заполнится дефисами и будет символизировать стенку шириной width. По окончании цикла переходим на вторую строку консоли с помощью cout<<endl;
Дальше нам необходимо отрисовать остальную часть поля, использую уже вложенный цикл (for вложен в for) вложенный цикл отрисует змейку( решетка), фрукт (F), и левую и правую стенку ( | ). Суть в том, что в каждой строке левая стенка находится на первой позиции(нулевой элемент строки), а правая на позиции ширина — 1(элемент равный ширине строки).
На позиции x,y находится змейка, рисуем любой элемент, я использовал решетку, на позиции fruitX,fruitY рисуем фрукт, я использовал букву F. Строки №40-45 отрисуют нижнюю границу поля, аналогично верхней, только ниже на расстоянии height (высоты).
На этом этапе, если вы все правильно усвоили и безошибочно написали вы увидите следующую картинку в консоли:
Теперь у нас имеется шаблон змейки, остается настроить управление и логику игры. О следующих функциях я расскажу во второй части 😉
Полный код игры я выложу в последней статье, вы сможете скопировать его, но я все же советую вам прописывать все самостоятельно, так процесс вашего обучения будет в разы продуктивнее.
И помните, ваши лайки — моя мотивация =) <3
Создание игры с нуля на Tkinter Python
import sys
import random
from PIL import Image, ImageTk
from tkinter import Tk, Frame, Canvas, ALL, NW
class Cons:
BOARD_WIDTH = 300
BOARD_HEIGHT = 300
DELAY = 100
DOT_SIZE = 10
MAX_RAND_POS = 27
class Board(Canvas):
def __init__(self):
super().__init__(
width=Cons.BOARD_WIDTH, height=Cons.BOARD_HEIGHT,
background=»blue», highlightthickness=0
)
self.initGame()
self.pack()
def initGame(self):
«»»
Инициализация игры.
«»»
self.inGame = True
self.dots = 3
self.score = 0
# Переменные для передвижения замеи.
self.moveX = Cons.DOT_SIZE
self.moveY = 0
# Изначальные стартовые координаты яблоки.
self.appleX = 100
self.appleY = 190
self.loadImages()
self.createObjects()
self.locateApple()
self.bind_all(«<Key>», self.onKeyPressed)
self.after(Cons.DELAY, self.onTimer)
def loadImages(self):
«»»
Подгружаем нужные изображения для игры.
«»»
try:
self.idot = Image.open(«dot.png»)
self.dot = ImageTk.PhotoImage(self.idot)
self.ihead = Image.open(«head.png»)
self.head = ImageTk.PhotoImage(self.ihead)
self.iapple = Image.open(«apple.png»)
self.apple = ImageTk.PhotoImage(self.iapple)
except IOError as e:
print(e)
sys.exit(1)
def createObjects(self):
«»»
Создание объектов на холсте.
«»»
self.create_text(
30, 10, text=»Счет: {0}».format(self.score),
tag=»score», fill=»white»
)
self.create_image(
self.appleX, self.appleY, image=self.apple,
anchor=NW, tag=»apple»
)
self.create_image(50, 50, image=self.head, anchor=NW, tag=»head»)
self.create_image(30, 50, image=self.dot, anchor=NW, tag=»dot»)
self.create_image(40, 50, image=self.dot, anchor=NW, tag=»dot»)
def checkAppleCollision(self):
«»»
Проверяем, не столкнулась ли голова змеи с яблоком.
«»»
apple = self.find_withtag(«apple»)
head = self.find_withtag(«head»)
x1, y1, x2, y2 = self.bbox(head)
overlap = self.find_overlapping(x1, y1, x2, y2)
for ovr in overlap:
if apple[0] == ovr:
self.score += 1
x, y = self.coords(apple)
self.create_image(x, y, image=self.dot, anchor=NW, tag=»dot»)
self.locateApple()
def moveSnake(self):
«»»
Меняем положение змеи на холсте.
«»»
dots = self.find_withtag(«dot»)
head = self.find_withtag(«head»)
items = dots + head
z = 0
while z < len(items)-1:
c1 = self.coords(items[z])
c2 = self.coords(items[z+1])
self.move(items[z], c2[0]-c1[0], c2[1]-c1[1])
z += 1
self.move(head, self.moveX, self.moveY)
def checkCollisions(self):
«»»
Проверка на столкновение змеи с другими объектами.
«»»
dots = self.find_withtag(«dot»)
head = self.find_withtag(«head»)
x1, y1, x2, y2 = self.bbox(head)
overlap = self.find_overlapping(x1, y1, x2, y2)
for dot in dots:
for over in overlap:
if over == dot:
self.inGame = False
if x1 < 0:
self.inGame = False
if x1 > Cons.BOARD_WIDTH — Cons.DOT_SIZE:
self.inGame = False
if y1 < 0:
self.inGame = False
if y1 > Cons.BOARD_HEIGHT — Cons.DOT_SIZE:
self.inGame = False
def locateApple(self):
«»»
Распределяем яблоки по холсту (canvas).
«»»
apple = self.find_withtag(«apple»)
self.delete(apple[0])
r = random.randint(0, Cons.MAX_RAND_POS)
self.appleX = r * Cons.DOT_SIZE
r = random.randint(0, Cons.MAX_RAND_POS)
self.appleY = r * Cons.DOT_SIZE
self.create_image(
self.appleX, self.appleY, anchor=NW,
image=self.apple, tag=»apple»
)
def onKeyPressed(self, e):
«»»
Управляем змеей через стрелки клавиатуры.
«»»
key = e.keysym
LEFT_CURSOR_KEY = «Left»
if key == LEFT_CURSOR_KEY and self.moveX <= 0:
self.moveX = -Cons.DOT_SIZE
self.moveY = 0
RIGHT_CURSOR_KEY = «Right»
if key == RIGHT_CURSOR_KEY and self.moveX >= 0:
self.moveX = Cons.DOT_SIZE
self.moveY = 0
RIGHT_CURSOR_KEY = «Up»
if key == RIGHT_CURSOR_KEY and self.moveY <= 0:
self.moveX = 0
self.moveY = -Cons.DOT_SIZE
DOWN_CURSOR_KEY = «Down»
if key == DOWN_CURSOR_KEY and self.moveY >= 0:
self.moveX = 0
self.moveY = Cons.DOT_SIZE
def onTimer(self):
«»»
Создает игровой цикл для каждого события таймера
«»»
self.drawScore()
self.checkCollisions()
if self.inGame:
self.checkAppleCollision()
self.moveSnake()
self.after(Cons.DELAY, self.onTimer)
else:
self.gameOver()
def drawScore(self):
«»»
Рисуем счет игры
«»»
score = self.find_withtag(«score»)
self.itemconfigure(score, text=»Счет: {0}».format(self.score))
def gameOver(self):
«»»
Удаляем все объекты и выводим сообщение об окончании игры.
«»»
self.delete(ALL)
self.create_text(self.winfo_width() / 2, self.winfo_height()/2,
text=»Игра закончилась со счетом {0}».format(self.score), fill=»white»)
class Snake(Frame):
def __init__(self):
super().__init__()
self.master.title(‘Змейка’)
self.board = Board()
self.pack()
def main():
root = Tk()
nib = Snake()
root.mainloop()
if __name__ == ‘__main__’:
main()
новичок — Змейка на C ++
Если вы не знаете, как долго будет длиться игровой цикл на каждом компьютере, делать ваш спящим
постоянным, как правило, плохой практикой. Если вы знаете, что хотите 2 кадра в секунду, хороший способ удержать его в очереди — получить время в начале игрового цикла, а затем в конце выяснить разницу и использовать это для расчета количества времени, необходимого для сна. чтобы шаг оставался прежним. например, если цикл занимает 0,1 с, а вы хотите 2 кадра в секунду, переведите в спящий режим 0,4 с.
Помимо этого, я бы сказал, что вам нужна еще одна переменная рядом с food
, которая равна snakeLength
или что-то в этом роде. Я не знаю, печатаете ли вы счет на экране, но если вы отслеживаете счет, я могу предположить, что вы хотите, чтобы он начинался с 0, а не с 3, и еще 1 int isn Это не имеет большого значения, когда вы улучшаете читаемость.
Возможно, подумайте о том, чтобы сделать direction
перечислением с UP, DOWN, LEFT и RIGHT, потому что сейчас это немного сложно выполнить, и вам не нужно менять слишком много логики, поскольку перечисления int
s с некоторыми дополнительными вещами, так что вы можете легко сравнить то, что вы делаете сейчас.Сказав это, я не уверен, что следую тому, к чему относятся ваши значения направления , поскольку я нигде не вижу, чтобы направление
было установлено на 5, поэтому эта проверка кажется ненужной.
В вашей функции generateFood
вы напрямую получаете доступ к map
, где вы создали функцию, которая выполняет эту работу точно в getMapValue
, поэтому вы можете рассмотреть возможность использования этого, поскольку в какой-то момент в будущем вы можете решите сделать его классом Map
, и тогда вы столкнетесь с ошибками при доступе к частным переменным (я надеюсь!).
В остальном все выглядит неплохо, так что я собираюсь начать придирки: P. Я бы просто предложил такие мелочи, как упорядочивание ваших #include
s и прототипов функций. Это не такая уж большая проблема, поскольку у вас их 2, но о чем нужно помнить. Кроме того, ваши clearScreen ()
и printMap ()
очень похожи на Draw ()
, поэтому вы могли бы обернуть их обе в этой функции и просто вызвать init
, update
, нарисуйте
и , очистите
(когда вы выполняете загрузку объектов и используете указатели и тому подобное), поскольку вам кажется, что почти , следуя шаблону игрового цикла (в стороне, если вы планируете делать больше игр, прочтите Вся эта книга прекрасна), и чтение этой статьи лучше объясняет мою точку зрения о сна
.
В программировании на C создайте игру «Змейка», используя это ...
- инженерное дело
- информатика
- вопросы и ответы по информатике
- В программировании на C создайте игру «Змейка», используя эту рубрику: (Я использую C 550 IDE как мой ...
Показать транскрибированный текст изображения
Ответ эксперта
Предыдущий вопрос Следующий вопрос
в программировании на C, создайте игру змейку, используя эту рубрику: (я использую C 550 IDE в качестве движка кодирования) 30 баллов 0 баллов Программа не компилируется.Программа 40 баллов Корректность Программа работает? Программа всегда Незначительные детали работы правильно программа и соответствует спецификации - это спецификации, некорректно нарушенные функции программы на некоторых входах Программа не дает сбоев 20 баллов 10 баллов Умеренные детали. Значительные детали спецификации нарушены. нарушено, или Программа может часто аварийно завершать работу, редко при выставлении какого-либо ввода. некорректное поведение. Программа может часто давать сбой. Удобочитаемость. Читается ли код? 25 баллов Код чистый, хорошо организованный 18 баллов Незначительные проблемы, такие как несогласованные отступы, общая организация имен переменных 9 баллов Мнения По крайней мере одна серьезная Несколько серьезных проблем, которые затрудняют чтение.трудно читать. Документация. Задокументированы ли причины, по которым код делает то, что он делает, в комментариях? Объясняется ли запутанная логика или использование переменных? 10 баллов Код хорошо прокомментирован. Замечания Нет комментариев. 7 баллов. 4 балла. Несколько мест. Существенный недостаток комментариев, которые могут быть полезны при написании комментариев, или сложность кода - это чрезмерное понимание закомментированного кода. Элегантность кода Была ли усовершенствована реализация или это подход грубой силы? 10 баллов Есть свидетельства того, что код подвергался последовательным уточнениям.Дублирование работы сведено к минимуму, а решение демонстрирует творческое мышление и / или изобретательность. 7 баллов Код использует механизмы для минимизации повторяющегося кода: минимальное жесткое кодирование 3 балла Для решения проблемы используется прямой или грубый подход. Возможности Это реализация Bure Bones или были добавлены дополнительные возможности? баллов Программа предоставляет больше функций, чем ожидалось. баллы Программа показывает, что были продуманы типы функций, которые сделают такую программу простой или приятной в использовании баллов. Был предоставлен минимум функциональных возможностей, которые будут соответствовать цели задания.
Игра Змейка в программе C 2020 с бесплатным исходным кодом
Введение
Snake game project - игровой проект на языке программирования c. Эта игра выполняется с помощью интерфейса командной строки. Это проект для новичков.
Snake game - игровой проект. Этот проект хорош для изучения и понимания программирования на языке Си. Он написан на языке программирования c. Несмотря на то, что у него есть интерфейс командной строки, его очень легко использовать и реализовать. Это очень полезный проект.Этот проект имеет простой макет, как и другие мобильные игры. Вам необходимо скомпилировать и выполнить проект.
Исходный код этого проекта представлен в этом блоге. Когда вы запустите проект, вы увидите на экране приветственный текст. Вы можете нажать любую клавишу, чтобы начать игру. При запуске игры вы можете увидеть потрясающую анимацию загрузки. После этого вы можете увидеть большую коробку, в которой разыгрывается игра в змейку. В левом верхнем углу отображается счет, а в правом верхнем углу - жизнь змеи.Смотрите скриншоты ниже.
Змейка игровой проект
Каждый раз, когда змея ест пищу, счет увеличивается. Если змея коснется коробки, жизнь уменьшится или змея умрет. Вы получите три жизни. После этого, когда игра закончится, он спросит ваше имя и позволит вам сохранить игру. Счет будет сохранен в текстовом файле, который включает имя игрока, счет, дату и время. Это также позволяет вам увидеть свои прошлые записи.
Так работает система.Код проекта тоже легко понять. При использовании этого кода проекта я настоятельно рекомендую вам использовать редактор кода Dev C ++. Он может как компилировать, так и выполнять, а также легко обнаруживать ошибку. Скачайте проект и убедитесь сами.
Как пользоваться этим проектом?
- Скачать проект.
- Загрузите программу Dev C ++ или любую другую.
- Настройте код в редакторе.
- Скомпилируйте и выполните код.
- Играйте и набирайте очки.
- Наслаждайтесь и делитесь.
Нажмите кнопку ниже, чтобы получить исходный код этого проекта.
Змейка на Python | Программа Snake Game с использованием Pygame
Да, я знаю, что вы все играли в Snake Game и определенно никогда не хотели проигрывать. В детстве мы все любили искать читы, чтобы никогда не увидеть сообщение «Игра окончена», но, как технари, я знаю, что вы хотели бы заставить эту «Змейку» танцевать под свои ритмы. Это то, что я покажу вам в этой статье о Snake Game на Python.
Прежде чем двигаться дальше, давайте кратко рассмотрим все подбиты, которые создают игру «Змейка» на Python:
- Установка Pygame
- Создание экрана
- Создание змейки
- Перемещение змеи
- Игра окончена, когда Змейка выходит за границы
- Добавление еды
- Увеличение длины змеи
- Отображение результатов
Установка Pygame:
Первое, что вам нужно сделать для создания игр с использованием Pygame, - это установить его в ваших системах.Для этого вы можете просто использовать следующую команду:
pip install pygame
Как только это будет сделано, просто импортируйте Pygame и начните разработку игры. Прежде чем двигаться дальше, взгляните на функции Pygame, которые использовались в этой игре-змейке, вместе с их описаниями.
Функция | Описание |
init () | Инициализирует все импортированные модули Pygame (возвращает кортеж, указывающий на успешность и неудачу инициализации) |
display.set_mode () | Принимает кортеж или список в качестве параметра для создания поверхности (предпочтительно кортеж) |
update () | Обновляет экран |
quit () | Используется для неинициализировать все |
set_caption () | Устанавливает текст заголовка в верхней части экрана дисплея |
event.get () | Возвращает список всех событий |
Заливает поверхность сплошным цветом | |
time.Clock () | Помогает отслеживать время времени |
font.SysFont () | Создает шрифт Pygame из ресурсы системных шрифтов |
Создание экрана:
Чтобы создать экран с помощью Pygame, вам нужно будет использовать функцию display.set_mode () . Кроме того, вам придется использовать методы init () и quit () для инициализации и деинициализации всего в начале и в конце кода.Метод update () используется для обновления любых изменений, внесенных на экран. Существует еще один метод, например, flip () , который работает аналогично функции update (). Разница в том, что метод update () обновляет только внесенные изменения (однако, если параметры не переданы, обновляет весь экран), а метод flip () повторно воспроизводит весь экран.
КОД:
импорт pygame pygame.init () dis = pygame.display.set_mode ((400,300)) pygame.display.update () pygame.quit () quit ()
ВЫХОД:
Но когда вы запустите этот код, экран появится, но он сразу же закроется. Чтобы исправить это, вы должны использовать игровой цикл с помощью цикла while, прежде чем я выйду из игры следующим образом:
import pygame pygame.init () dis = pygame.display.set_mode ((400,300)) pygame.display.update () pygame.display.set_caption ('Змейка от Edureka') game_over = Ложь пока не game_over: для события в pygame.event.get (): print (event) # распечатывает все действия, которые происходят на экране pygame.quit () quit ()
Когда вы запустите этот код, вы увидите, что экран, который вы видели ранее, не закрывается, а также возвращает все действия, которые происходят над ним. Я сделал это с помощью функции event.get () . Кроме того, я назвал экран «Snake Game by Edureka», используя функцию display.set_caption () .
ВЫХОД:
Теперь у вас есть экран для игры в Snake Game, но когда вы пытаетесь нажать кнопку закрытия, экран не закрывается.Это потому, что вы не указали, что ваш экран должен выходить, когда вы нажимаете эту кнопку закрытия. Для этого Pygame предоставляет событие под названием «QUIT», и его следует использовать следующим образом:
import pygame pygame.init () dis = pygame.display.set_mode ((400,300)) pygame.display.update () pygame.display.set_caption ('Змейка от Edureka') game_over = Ложь пока не game_over: для события в pygame.event.get (): если event.type == pygame.QUIT: game_over = Верно pygame.quit () quit ()
Итак, ваш экран готов.Следующая часть - нарисовать нашу змею на экране, о чем мы поговорим в следующем разделе.
Создание змеи:
Чтобы создать змею, я сначала инициализирую несколько цветовых переменных, чтобы раскрасить змею, еду, экран и т. Д. Цветовая схема, используемая в Pygame, - это RGB, т.е. . Если вы установите все эти значения на 0, цвет будет черным, а все 255 будут белыми. Так что наша змейка на самом деле будет прямоугольником. Чтобы рисовать прямоугольники в Pygame, вы можете использовать функцию draw.rect () , который поможет нарисовать прямоугольник желаемого цвета и размера.
импорт pygame pygame.init () dis = pygame.display.set_mode ((400,300)) pygame.display.set_caption ('Змейка от Edureka') синий = (0,0,255) красный = (255,0,0) game_over = Ложь пока не game_over: для события в pygame.event.get (): если event.type == pygame.QUIT: game_over = Верно pygame.draw.rect (дис, синий, [200,150,10,10]) pygame.display.update () pygame.quit () quit ()
ВЫХОД:
Как видите, змееголовый создан в виде синего прямоугольника.Следующий шаг - заставить вашу змею двигаться.
Перемещение змейки:
Чтобы переместить змейку, вам нужно будет использовать ключевые события, присутствующие в классе KEYDOWN Pygame. Здесь используются события K_UP, K_DOWN, K_LEFT и K_RIGHT, чтобы змейка двигалась вверх, вниз, влево и вправо соответственно. Кроме того, экран дисплея изменяется с черного по умолчанию на белый с помощью метода fill () .
Я создал новые переменные x1_change и y1_change для хранения обновляемых значений координат x и y.
импорт pygame pygame.init () белый = (255, 255, 255) черный = (0, 0, 0) красный = (255, 0, 0) dis = pygame.display.set_mode ((800, 600)) pygame.display.set_caption ('Змейка от Edureka') game_over = Ложь х1 = 300 y1 = 300 x1_change = 0 y1_change = 0 часы = pygame.time.Clock () пока не game_over: для события в pygame.event.get (): если event.type == pygame.QUIT: game_over = Верно если event.type == pygame.KEYDOWN: если event.key == pygame.K_LEFT: x1_change = -10 y1_change = 0 elif event.key == pygame.K_RIGHT: x1_change = 10 y1_change = 0 elif event.key == pygame.K_UP: y1_change = -10 x1_change = 0 elif event.key == pygame.K_DOWN: y1_change = 10 x1_change = 0 x1 + = x1_change y1 + = y1_change dis.fill (белый) pygame.draw.rect (dis, черный, [x1, y1, 10, 10]) pygame.display.update () часы.tick (30) pygame.quit () quit ()
ВЫХОД:
Игра заканчивается, когда Змея достигает границ:
В этой игре со змеями, если игрок попадает в границы экрана, он проигрывает. Чтобы указать это, я использовал оператор «if», который определяет пределы для координат x и y змеи, которые должны быть меньше или равны координатам экрана. Кроме того, не забудьте, что я удалил жесткие коды и использовал вместо них переменные, чтобы было проще, если вы захотите внести какие-либо изменения в игру позже.
импорт pygame время импорта pygame.init () белый = (255, 255, 255) черный = (0, 0, 0) красный = (255, 0, 0) dis_width = 800 dis_height = 600 dis = pygame.display.set_mode ((dis_width, dis_width)) pygame.display.set_caption ('Змейка от Edureka') game_over = Ложь x1 = dis_width / 2 y1 = dis_height / 2 snake_block = 10 x1_change = 0 y1_change = 0 часы = pygame.time.Clock () snake_speed = 30 font_style = pygame.font.SysFont (Нет, 50) сообщение def (сообщение, цвет): mesg = font_style.render (сообщение, True, цвет) дис.blit (mesg, [dis_width / 2, dis_height / 2]) пока не game_over: для события в pygame.event.get (): если event.type == pygame.QUIT: game_over = Верно если event.type == pygame.KEYDOWN: если event.key == pygame.K_LEFT: x1_change = -snake_block y1_change = 0 elif event.key == pygame.K_RIGHT: x1_change = змеиный_блок y1_change = 0 elif event.key == pygame.K_UP: y1_change = -snake_block x1_change = 0 elif событие.ключ == pygame.K_DOWN: y1_change = змеиный_блок x1_change = 0 если x1> = dis_width или x1 <0 или y1> = dis_height или y1 <0: game_over = Верно x1 + = x1_change y1 + = y1_change dis.fill (белый) pygame.draw.rect (dis, черный, [x1, y1, snake_block, snake_block]) pygame.display.update () часы.tick (snake_speed) сообщение («Ты проиграл», красный) pygame.display.update () время сна (2) pygame.quit () quit ()
ВЫХОД:
Добавление еды:
Здесь я добавлю немного еды для змеи, и когда змея пересечет эту еду, у меня будет сообщение «Вкусно !!» .Кроме того, я внесу небольшое изменение, в которое добавлю возможность выйти из игры или снова сыграть, когда игрок проиграет.
импорт pygame время импорта случайный импорт pygame.init () белый = (255, 255, 255) черный = (0, 0, 0) красный = (255, 0, 0) синий = (0, 0, 255) dis_width = 800 dis_height = 600 dis = pygame.display.set_mode ((dis_width, dis_height)) pygame.display.set_caption ('Змейка от Edureka') часы = pygame.time.Clock () snake_block = 10 snake_speed = 30 font_style = pygame.font.SysFont (Нет, 30) сообщение def (сообщение, цвет): mesg = font_style.render (сообщение, True, цвет) dis.blit (mesg, [dis_width / 3, dis_height / 3]) def gameLoop (): # создание функции game_over = Ложь game_close = Ложь x1 = dis_width / 2 y1 = dis_height / 2 x1_change = 0 y1_change = 0 foodx = round (random.randrange (0, dis_width - snake_block) / 10.0) * 10.0 foody = round (random.randrange (0, dis_width - snake_block) / 10.0) * 10.0 пока не game_over: в то время как game_close == True: дис.заливка (белый) сообщение («Вы проиграли! Нажмите Q-Quit или C-Play снова», красный) pygame.display.update () для события в pygame.event.get (): если event.type == pygame.KEYDOWN: если event.key == pygame.K_q: game_over = Верно game_close = Ложь если event.key == pygame.K_c: gameLoop () для события в pygame.event.get (): если event.type == pygame.ПОКИДАТЬ: game_over = Верно если event.type == pygame.KEYDOWN: если event.key == pygame.K_LEFT: x1_change = -snake_block y1_change = 0 elif event.key == pygame.K_RIGHT: x1_change = змеиный_блок y1_change = 0 elif event.key == pygame.K_UP: y1_change = -snake_block x1_change = 0 elif event.key == pygame.K_DOWN: y1_change = змеиный_блок x1_change = 0 если x1> = dis_width или x1 <0 или y1> = dis_height или y1 <0: game_close = Верно x1 + = x1_change y1 + = y1_change dis.fill (белый) pygame.draw.rect (дис, синий, [foodx, foody, snake_block, snake_block]) pygame.draw.rect (dis, черный, [x1, y1, snake_block, snake_block]) pygame.display.update () если x1 == foodx и y1 == foody: print ("Вкуснятина !!") Часы.тик (snake_speed) pygame.quit () покидать() gameLoop ()
ВЫХОД:
Терминал:
Увеличение длины змеи:
Следующий код увеличит размер нашего саке, когда он ест еду. Кроме того, если змея сталкивается со своим телом, игра окончена, и вы увидите сообщение «Вы проиграли! Нажмите Q-Quit или C-Play Again ». Длина змейки в основном содержится в списке, а начальный размер, указанный в следующем коде, составляет один блок.
импорт pygame время импорта случайный импорт pygame.init () белый = (255, 255, 255) желтый = (255, 255, 102) черный = (0, 0, 0) красный = (213, 50, 80) зеленый = (0, 255, 0) синий = (50, 153, 213) dis_width = 600 dis_height = 400 dis = pygame.display.set_mode ((dis_width, dis_height)) pygame.display.set_caption ('Змейка от Edureka') часы = pygame.time.Clock () snake_block = 10 snake_speed = 15 font_style = pygame.font.SysFont ("bahnschrift", 25) score_font = pygame.font.SysFont ("comicsansms", 35) def our_snake (snake_block, snake_list): для x в snake_list: pygame.draw.rect (dis, черный, [x [0], x [1], snake_block, snake_block]) сообщение def (сообщение, цвет): mesg = font_style.render (сообщение, True, цвет) dis.blit (mesg, [dis_width / 6, dis_height / 3]) def gameLoop (): game_over = Ложь game_close = Ложь x1 = dis_width / 2 y1 = dis_height / 2 x1_change = 0 y1_change = 0 snake_List = [] Length_of_snake = 1 foodx = round (random.randrange (0, dis_width - snake_block) / 10.0) * 10.0 foody = round (random.randrange (0, dis_height - snake_block) / 10.0) * 10,0 пока не game_over: в то время как game_close == True: dis.fill (синий) сообщение («Вы проиграли! Нажмите C-Play снова или Q-Quit», красный) pygame.display.update () для события в pygame.event.get (): если event.type == pygame.KEYDOWN: если event.key == pygame.K_q: game_over = Верно game_close = Ложь если event.key == pygame.K_c: gameLoop () для события в pygame.event.get (): если event.type == pygame.QUIT: game_over = Верно если event.type == pygame.KEYDOWN: если event.key == pygame.K_LEFT: x1_change = -snake_block y1_change = 0 elif event.key == pygame.K_RIGHT: x1_change = змеиный_блок y1_change = 0 elif event.key == pygame.K_UP: y1_change = -snake_block x1_change = 0 elif событие.ключ == pygame.K_DOWN: y1_change = змеиный_блок x1_change = 0 если x1> = dis_width или x1 <0 или y1> = dis_height или y1 <0: game_close = Верно x1 + = x1_change y1 + = y1_change dis.fill (синий) pygame.draw.rect (дис, зеленый, [foodx, foody, snake_block, snake_block]) snake_Head = [] snake_Head.append (x1) snake_Head.append (y1) snake_List.append (snake_Head) если len (snake_List)> Length_of_snake: del snake_List [0] для x в snake_List [: - 1]: если x == snake_Head: game_close = Верно наша_змея (snake_block, snake_List) pygame.display.update () если x1 == foodx и y1 == foody: foodx = round (random.randrange (0, dis_width - snake_block) / 10.0) * 10.0 foody = round (random.randrange (0, dis_height - snake_block) / 10.0) * 10.0 Длина_змеи + = 1 часы.tick (snake_speed) pygame.quit () покидать() gameLoop ()
ВЫХОД:
Отображение счета:
И последнее, но не менее важное: вам нужно будет отобразить счет игрока.Для этого я создал новую функцию «Your_score». Эта функция отобразит длину змеи за вычетом 1, потому что это начальный размер змеи.
импорт pygame время импорта случайный импорт pygame.init () белый = (255, 255, 255) желтый = (255, 255, 102) черный = (0, 0, 0) красный = (213, 50, 80) зеленый = (0, 255, 0) синий = (50, 153, 213) dis_width = 600 dis_height = 400 dis = pygame.display.set_mode ((dis_width, dis_height)) pygame.display.set_caption ('Змейка от Edureka') часы = pygame.time.Clock () snake_block = 10 snake_speed = 15 font_style = pygame.font.SysFont ("bahnschrift", 25) score_font = pygame.font.SysFont ("comicsansms", 35) def Your_score (оценка): value = score_font.render ("Your Score:" + str (score), True, yellow) dis.blit (значение, [0, 0]) def our_snake (snake_block, snake_list): для x в snake_list: pygame.draw.rect (dis, черный, [x [0], x [1], snake_block, snake_block]) сообщение def (сообщение, цвет): mesg = font_style.render (сообщение, True, цвет) дис.blit (mesg, [dis_width / 6, dis_height / 3]) def gameLoop (): game_over = Ложь game_close = Ложь x1 = dis_width / 2 y1 = dis_height / 2 x1_change = 0 y1_change = 0 snake_List = [] Length_of_snake = 1 foodx = round (random.randrange (0, dis_width - snake_block) / 10.0) * 10.0 foody = round (random.randrange (0, dis_height - snake_block) / 10.0) * 10.0 пока не game_over: в то время как game_close == True: dis.fill (синий) сообщение («Вы проиграли! Нажмите C-Play снова или Q-Quit», красный) Your_score (Длина_змеи - 1) pygame.display.update () для события в pygame.event.get (): если event.type == pygame.KEYDOWN: если event.key == pygame.K_q: game_over = Верно game_close = Ложь если event.key == pygame.K_c: gameLoop () для события в pygame.event.get (): если event.type == pygame.QUIT: game_over = Верно если event.type == pygame.KEYDOWN: если событие.ключ == pygame.K_LEFT: x1_change = -snake_block y1_change = 0 elif event.key == pygame.K_RIGHT: x1_change = змеиный_блок y1_change = 0 elif event.key == pygame.K_UP: y1_change = -snake_block x1_change = 0 elif event.key == pygame.K_DOWN: y1_change = змеиный_блок x1_change = 0 если x1> = dis_width или x1 <0 или y1> = dis_height или y1 <0: game_close = Верно x1 + = x1_change y1 + = y1_change дис.заливка (синий) pygame.draw.rect (дис, зеленый, [foodx, foody, snake_block, snake_block]) snake_Head = [] snake_Head.append (x1) snake_Head.append (y1) snake_List.append (snake_Head) если len (snake_List)> Length_of_snake: del snake_List [0] для x в snake_List [: - 1]: если x == snake_Head: game_close = Верно наша_змея (snake_block, snake_List) Your_score (Длина_змеи - 1) pygame.display.update () если x1 == foodx и y1 == foody: foodx = круглый (случайный.randrange (0, dis_width - snake_block) / 10.0) * 10.0 foody = round (random.randrange (0, dis_height - snake_block) / 10.0) * 10.0 Длина_змеи + = 1 часы.tick (snake_speed) pygame.quit () покидать() gameLoop ()
ВЫХОД:
На этом мы подошли к концу статьи о Snake Game на Python. Надеюсь, вы понимаете все, о чем вам рассказали в этой статье. Убедитесь, что вы как можно больше тренируетесь, и вернитесь к своему опыту.
Есть вопросы? Пожалуйста, упомяните об этом в разделе комментариев этого блога «Snake Game in Python», и мы свяжемся с вами как можно скорее.
Чтобы получить глубокие знания о любых трендовых технологиях и их различных приложениях, вы можете записаться на курс по сертификации Python в режиме реального времени с круглосуточной поддержкой и пожизненным доступом.
Змея игры | Matthew Vlietstra
Игра-змейка для windows / linux, которую я сделал для своего первого задания ICT106 (Основы компьютерных систем).
Исходный код (компилируется с помощью borland и gcc - также должен компилироваться в других компиляторах).
#include
#include
#include
#define SNAKE_ARRAY_SIZE 310
#ifdef _WIN32
#include
#define UP_ARROW 72
#define LEFT_ARROW 75
#define RIGHT_ARROW 77
#define DOWN_ARROW 80
#define ENTER_KEY 13
const char SNAKE_HEAD = (символ) 177;
const char SNAKE_BODY = (символ) 178;
const char WALL = (char) 219;
const char FOOD = (char) 254;
const char BLANK = '';
#еще
# включить
#include
#include
#include
#define UP_ARROW (char) 'A'
#define LEFT_ARROW (char) 'D'
#define RIGHT_ARROW (char) 'C'
#define СТРЕЛКА ВНИЗ (символ) 'B'
#define ENTER_KEY 10
const char SNAKE_HEAD = 'X';
const char SNAKE_BODY = '#';
const char WALL = '#';
const char FOOD = '*';
const char BLANK = '';
пустота gotoxy (int x, int y)
{
printf ("% c [% d;% df", 0x1B, y, x);
}
int kbhit (недействительно)
{
struct termios oldt, newt;
int ch;
int oldf;
tcgetattr (STDIN_FILENO, & oldt);
newt = oldt;
тритон.c_lflag & = ~ (ЗНАЧОК | ЭХО);
tcsetattr (STDIN_FILENO, TCSANOW, & newt);
oldf = fcntl (STDIN_FILENO, F_GETFL, 0);
fcntl (STDIN_FILENO, F_SETFL, oldf | O_NONBLOCK);
ch = getchar ();
tcsetattr (STDIN_FILENO, TCSANOW, & oldt);
fcntl (STDIN_FILENO, F_SETFL, oldf);
если (ch! = EOF)
{
ungetc (ch, stdin);
возврат 1;
}
возврат 0;
}
char getch ()
{
char c;
система ("stty raw");
c = getchar ();
система ("stty sane");
return (c);
}
недействительный clrscr ()
{
система («ясно»);
возвращаться;
}
#endif
#define EXIT_BUTTON 27
#define PAUSE_BUTTON 112
char waitForAnyKey (недействительно)
{
int нажата;
пока (! kbhit ());
нажал = getch ();
return ((char) нажат);
}
int getGameSpeed (недействительно)
{
int скорость;
clrscr ();
делать
{
готокси (10,5);
printf ("Выберите скорость игры от 1 до 9.");
speed = waitForAnyKey () - 48;
} while (скорость <1 || скорость> 9);
возврат (скорость);
}
void pauseMenu (недействительно)
{
int i;
готокси (28,23);
printf ("** Приостановлено **");
waitForAnyKey ();
готокси (28,23);
printf ("");
возвращаться;
}
int checkKeysPressed (направление int)
{
int нажата;
если (kbhit ())
{
нажал = getch ();
если (направление! = нажата)
{
if (нажатие == СТРЕЛКА_НИЗ && направление! = СТРЕЛКА_ВЕРХ)
направление = нажато;
иначе, если (нажата == СТРЕЛКА ВВЕРХ && направление! = СТРЕЛКА ВНИЗ)
направление = нажато;
иначе, если (нажата == СТРЕЛКА_ЛЕВА && направление! = СТРЕЛКА_ПРАВА)
направление = нажато;
иначе, если (нажата == СТРЕЛКА_ПРАВА && направление! = СТРЕЛКА_ЛЕВА)
направление = нажато;
иначе, если (нажата == EXIT_BUTTON || нажата == PAUSE_BUTTON)
pauseMenu ();
}
}
возврат (направление);
}
int collisionSnake (int x, int y, int snakeXY [] [SNAKE_ARRAY_SIZE], int snakeLength, int detect)
{
int i;
for (i = обнаружить; i = 1; i--)
{
змеяXY [0] [я] = змеяXY [0] [я-1];
snakeXY [1] [i] = snakeXY [1] [i-1];
}
переключатель (направление)
{
case DOWN_ARROW:
snakeXY [1] [0] ++;
перерыв;
case RIGHT_ARROW:
snakeXY [0] [0] ++;
перерыв;
case UP_ARROW:
snakeXY [1] [0] -;
перерыв;
case LEFT_ARROW:
snakeXY [0] [0] -;
перерыв;
}
возвращаться;
}
void move (int snakeXY [] [SNAKE_ARRAY_SIZE], int snakeLength, int direction)
{
int x;
int y;
x = snakeXY [0] [snakeLength-1];
y = snakeXY [1] [snakeLength-1];
gotoxy (x, y);
printf ("% c", ПУСТО);
gotoxy (snakeXY [0] [0], snakeXY [1] [0]);
printf ("% c", SNAKE_BODY);
moveSnakeArray (snakeXY, snakeLength, direction);
gotoxy (snakeXY [0] [0], snakeXY [1] [0]);
printf ("% c", SNAKE_HEAD);
готокси (1,1);
возвращаться;
}
int eatFood (int snakeXY [] [SNAKE_ARRAY_SIZE], int foodXY [])
{
if (snakeXY [0] [0] == foodXY [0] && snakeXY [1] [0] == foodXY [1])
{
foodXY [0] = 0;
foodXY [1] = 0;
printf ("\ 7");
возврат (1);
}
возврат (0);
}
int collisionDetection (int snakeXY [] [SNAKE_ARRAY_SIZE], int consoleWidth, int consoleHeight, int snakeLength)
{
int colision = 0;
if ((snakeXY [0] [0] == 1) || (snakeXY [1] [0] == 1) || (snakeXY [0] [0] == consoleWidth) || (snakeXY [1] [ 0] == consoleHeight - 4))
столкновение = 1;
еще
если (collisionSnake (snakeXY [0] [0], snakeXY [1] [0], snakeXY, snakeLength, 1))
столкновение = 1;
возврат (столкновение);
}
void refreshInfoBar (число int, скорость int)
{
готокси (5,23);
printf ("Оценка:% d", оценка);
готокси (5,24);
printf ("Скорость:% d", скорость);
готокси (52,23);
printf ("Кодер: Мэтью Влитстра");
готокси (52,24);
printf ("Версия: 0.5 ");
возвращаться;
}
недействительным createHighScores (недействительным)
{
ФАЙЛ * файл;
int i;
файл = fopen ("highscores.txt", "ш +");
если (файл == NULL)
{
printf ("НЕИЗВЕСТНО СОЗДАТЬ ВЫСОКИЕ РЕЗУЛЬТАТЫ !!! ВЫХОДИТ!");
выход (0);
}
для (я = 0; я <5; я ++)
{
fprintf (файл, "% d", i + 1);
fprintf (файл, "% s", "\ t0 \ t \ t \ tEMPTY \ n");
}
fclose (файл);
возвращаться;
}
int getLowestScore ()
{
ФАЙЛ * fp;
char str [128];
int lowScore = 0;
int i;
int intLength;
if ((fp = fopen ("highscores.txt", "r")) == NULL)
{
createHighScores ();
if ((fp = fopen ("рекорды.txt "," r ")) == NULL)
выход (1);
}
пока (! feof (fp))
{
fgets (str, 126, fp);
}
fclose (fp);
я = 0;
в то время как (str [2 + i]! = '\ t')
{
i ++;
}
intLength = я;
для (я = 0; я = fScore && введено! = 1)
{
оценки [x] = оценка;
strcpy (highScoreNames [x], имя);
x ++;
введено = 1;
}
strcpy (highScoreNames [x], highScoreName);
оценки [x] = fScore;
для (y = 0; y <20; y ++)
{
highScoreName [y] = NULL;
}
x ++;
если (x> = 5)
перерыв;
}
fclose (fp);
file = fopen ("рекорды.txt "," w + ");
для (я = 0; я <5; я ++)
{
fprintf (файл, "% d \ t% d \ t \ t \ t% s \ n", i + 1, scores [i], highScoreNames [i]);
}
fclose (файл);
возвращаться;
}
void displayHighScores (недействительно)
{
ФАЙЛ * fp;
char str [128];
int y = 5;
clrscr ();
if ((fp = fopen ("highscores.txt", "r")) == NULL) {
createHighScores ();
if ((fp = fopen ("highscores.txt", "r")) == NULL)
выход (1);
}
gotoxy (10, y ++);
printf («Рекорды»);
gotoxy (10, y ++);
printf ("Рейтинг \ tScore \ t \ t \ tName");
while (! feof (fp)) {
gotoxy (10, y ++);
если (fgets (str, 126, fp))
printf ("% s", str);
}
fclose (fp);
gotoxy (10, y ++);
printf ("Чтобы продолжить, нажмите любую клавишу... ");
waitForAnyKey ();
возвращаться;
}
аннулировать youWinScreen (недействительно)
{
int x = 6, y = 7;
gotoxy (x, y ++);
printf ("'## :::' ## :: '####### ::' ## :::: '## ::::' ## ::::: '##: '####:' ## ::: ##: '####: ");
gotoxy (x, y ++);
printf (". ##: '## ::' ## .... ##: ## :::: ## :::: ##: '##: ## :. ## :: # ## :: ##: ####: ");
gotoxy (x, y ++);
printf (":. #### ::: ## :::: ##: ## :::: ## :::: ##: ##: ## :: ## :: ### #: ##: ####: ");
gotoxy (x, y ++);
printf ("::. ## :::: ## :::: ##: ## :::: ## :::: ##: ##: ## :: ## :: ## # # ## :: ## :: ");
gotoxy (x, y ++);
printf ("::: ## :::: ## :::: ##: ## :::: ## :::: ##: ##: ## :: ## :: ##.#### :: .. ::: ");
gotoxy (x, y ++);
printf ("::: ## :::: ## :::: ##: ## :::: ## :::: ##: ##: ## :: ## :: ##: . ###: '####: ");
gotoxy (x, y ++);
printf ("::: ## ::::. ####### ::. ####### :::::. ###. ### :: '#### : ## ::. ##: ####: ");
gotoxy (x, y ++);
printf ("::: .. :::::: ....... :::: ....... ::::::: ... :: ... ::: .... :: .. :::: .. :: .... :: ");
gotoxy (x, y ++);
waitForAnyKey ();
clrscr ();
возвращаться;
}
void gameOverScreen (недействительно)
{
int x = 17, y = 3;
gotoxy (x, y ++);
printf (": '###### ::::::' ### :::: '## ::::' ##: '########: \ n") ;
gotoxy (x, y ++);
printf ("'##... ## :::: '## ## ::: ### ::' ###: ## ..... :: \ n ");
gotoxy (x, y ++);
printf ("## ::: .. :::: '## :. ## :: ####' ####: ## ::::::: \ n");
gotoxy (x, y ++);
printf ("## :: '####:' ## :::. ##: ## ### ##: ###### ::: \ n");
gotoxy (x, y ++);
printf ("## ::: ## :: #########: ##. #: ##: ## ... :::: \ n");
gotoxy (x, y ++);
printf ("## ::: ## :: ## .... ##: ##:. :: ##: ## ::::::: \ n");
gotoxy (x, y ++);
printf (". ###### ::: ## :::: ##: ## :::: ##: ########: \ n");
gotoxy (x, y ++);
printf (": ...... :::: .. ::::: .. :: .. ::::: .. :: ........ :: \ n") ;
gotoxy (x, y ++);
printf (": '####### ::' ## :::: '##:' ########: '######## ::' ### #: \ n ");
gotoxy (x, y ++);
printf ("'##.... ##: ## :::: ##: ## ..... :: ## .... ##: ####: \ n ");
gotoxy (x, y ++);
printf ("## :::: ##: ## :::: ##: ## ::::::: ## :::: ##: ####: \ n");
gotoxy (x, y ++);
printf ("## :::: ##: ## :::: ##: ###### ::: ######## ::: ## :: \ n");
gotoxy (x, y ++);
printf ("## :::: ## :. ## :: ## :: ## ... :::: ## .. ## :::: .. ::: \ n");
gotoxy (x, y ++);
printf ("## :::: ## ::. ## ## ::: ## ::::::: ## ::. ## :: '####: \ n");
gotoxy (x, y ++);
printf (". ####### ::::. ### :::: ########: ## :::. ##: ####: \ n") ;
gotoxy (x, y ++);
printf (": ....... :::::: ... ::::: ........ :: .. ::::: .. :: .... :: \ n ");
waitForAnyKey ();
clrscr ();
возвращаться;
}
void startGame (int snakeXY [] [SNAKE_ARRAY_SIZE], int foodXY [], int consoleWidth, int consoleHeight, int snakeLength, int direction, int score, int speed)
{
int gameOver = 0;
clock_t endWait;
int waitMili = CLOCKS_PER_SEC- (скорость) * (CLOCKS_PER_SEC / 10);
int tempScore = 10 * скорость;
int oldDirection;
int canChangeDirection = 1;
endWait = часы () + waitMili;
делать
{
если (canChangeDirection)
{
oldDirection = направление;
direction = checkKeysPressed (направление);
}
если (oldDirection! = направление)
canChangeDirection = 0;
если (часы ()> = endWait)
{
двигаться (snakeXY, snakeLength, direction);
canChangeDirection = 1;
если (eatFood (snakeXY, foodXY))
{
generateFood (foodXY, consoleWidth, consoleHeight, snakeXY, snakeLength);
snakeLength ++;
оценка + = скорость;
если (оценка> = 10 * скорость + темп.
{
скорость ++;
tempScore = оценка;
если (скорость <= 9)
waitMili = waitMili - (CLOCKS_PER_SEC / 10);
еще
{
если (waitMili> = 40)
waitMili = waitMili - (CLOCKS_PER_SEC / 200);
}
}
refreshInfoBar (оценка, скорость);
}
endWait = часы () + waitMili;
}
gameOver = collisionDetection (snakeXY, consoleWidth, consoleHeight, snakeLength);
если (snakeLength> = SNAKE_ARRAY_SIZE-5)
{
gameOver = 2;
оценка + = 1500;
}
} while (! gameOver);
переключатель (gameOver)
{
Дело 1:
printf ("\ 7");
printf ("\ 7");
gameOverScreen ();
перерыв;
случай 2:
youWinScreen ();
перерыв;
}
если (оценка> = getLowestScore () && оценка! = 0)
{
inputScore (оценка);
displayHighScores ();
}
возвращаться;
}
void loadEnviroment (int consoleWidth, int consoleHeight)
{
int i;
int x = 1, y = 1;
int rectangleHeight = consoleHeight - 4;
clrscr ();
gotoxy (x, y);
для (; y ");
готокси (1,1);
делать
{
ключ = waitForAnyKey ();
если (ключ == (символ) СТРЕЛКА ВВЕРХ)
{
gotoxy (x, yStart + i);
printf ("");
если (yStart> = yStart + i)
i = y - yStart - 2;
еще
я--;
gotoxy (x, yStart + i);
printf (">");
}
еще
если (ключ == (символ) СТРЕЛКА ВНИЗ)
{
gotoxy (x, yStart + i);
printf ("");
если (i + 2> = y - yStart)
я = 0;
еще
i ++;
gotoxy (x, yStart + i);
printf (">");
}
} while (ключ! = (символ) ENTER_KEY);
return (i);
}
void welcomeArt (недействительно)
{
clrscr ();
printf ("\ п");
printf ("\ t \ t _________ _________ \ n");
printf ("\ t \ t / \\ / \\ \ n");
printf ("\ t \ t / / ~~~~~ \\ \\ / / ~~~~~ \\ \\ \ n");
printf ("\ t \ t | | | | | | | | \ n");
printf ("\ t \ t | | | | | | | | \ n");
printf ("\ t \ t | | | | | | | | / \ n");
printf ("\ t \ t | | | | | | | | // \ n");
printf ("\ t \ t (o o) \\ \\ _____ / / \\ \\ _____ / / \ n");
printf ("\ t \ t \\ __ / \\ / \\ / \ n");
printf ("\ t \ t | ~~~~~~~~~ ~~~~~~~~ \ n");
printf ("\ т \ т ^ \ п");
printf ("\ t Добро пожаловать в игру" Змейка! \ n ");
printf ("\ t Нажмите любую клавишу, чтобы продолжить... \ n ");
printf ("\ п");
waitForAnyKey ();
возвращаться;
}
недействительный контроль (недействителен)
{
int x = 10, y = 5;
clrscr ();
gotoxy (x, y ++);
printf ("Элементы управления \ n");
gotoxy (x ++, y ++);
printf («Используйте следующие клавиши со стрелками, чтобы направить змею к еде:»);
gotoxy (x, y ++);
printf ("Стрелка вправо");
gotoxy (x, y ++);
printf ("Стрелка влево");
gotoxy (x, y ++);
printf ("Стрелка вверх");
gotoxy (x, y ++);
printf ("Нижняя стрелка");
gotoxy (x, y ++);
gotoxy (x, y ++);
printf ("P & Esc приостанавливает игру.");
gotoxy (x, y ++);
gotoxy (x, y ++);
printf ("Чтобы продолжить, нажмите любую клавишу... ");
waitForAnyKey ();
возвращаться;
}
void exitYN (недействительно)
{
уголь нажата;
готокси (9,8);
printf ("Вы уверены, что хотите выйти (Д / Н) \ n");
делать
{
нажата = waitForAnyKey ();
Press = tolower (нажато);
} while (! (нажато == 'y' || нажато == 'n'));
если (нажал == 'y')
{
clrscr ();
выход (1);
}
возвращаться;
}
int mainMenu (недействительно)
{
int x = 10, y = 5;
int yStart = y;
int selected;
clrscr ();
gotoxy (x, y ++);
printf ("Новая игра \ n");
gotoxy (x, y ++);
printf ("Рекорды \ n");
gotoxy (x, y ++);
printf ("Элементы управления \ n");
gotoxy (x, y ++);
printf ("Выход \ n");
gotoxy (x, y ++);
selected = menuSelector (x, y, yStart);
возврат (выбрано);
}
int main ()
{
welcomeArt ();
делать
{
переключатель (mainMenu ())
{
case 0:
загрузить игру();
перерыв;
Дело 1:
displayHighScores ();
перерыв;
случай 2:
элементы управления ();
перерыв;
случай 3:
exitYN ();
перерыв;
}
} while (1);
возврат (0);
}
Программирование 2D-игр на языке C Учебное пособие: Snake
Цель этого руководства - обучить программированию 2D-игр и языку Си на примерах.Автор программировал игры в середине 1980-х, а в 90-х годах был разработчиком игр в MicroProse. Хотя многое из этого не имеет отношения к программированию современных больших 3D-игр, для небольших казуальных игр это послужит полезным введением.
Реализация Змеи
Такие игры, как змейка, где объекты движутся по 2D-полю, могут представлять игровые объекты либо в 2D-сетке, либо в виде одномерного массива объектов. «Объект» здесь означает любой игровой объект, а не объект, как используется в объектно-ориентированном программировании.
Управление игрой
Клавиши перемещаются с W = вверх, A = влево, S = вниз, D = вправо. Нажмите Esc, чтобы выйти из игры, f, чтобы переключить частоту кадров (это не синхронизируется с дисплеем, поэтому может быть быстро), клавишу табуляции, чтобы переключить отладочную информацию, и p, чтобы приостановить ее. Когда он ставится на паузу, заголовок меняется, и змея мигает,
В змейке основными игровыми объектами являются
- Змея
- Ловушки и фрукты
В целях игрового процесса массив int будет содержать каждый игровой объект (или часть для змеи).Это также может помочь при рендеринге объектов в экранный буфер. Я разработал графику для игры следующим образом:
- Горизонтальное тело змеи - 0
- Вертикальное тело змеи - 1
- Головка с 4-мя поворотами по 90 градусов 2-5
- Хвост с 4-мя поворотами по 90 градусов 6-9
- Кривые изменения направления. 10-13
- Яблоко - 14
- Клубника - 15
- банан - 16
- Ловушка - 17
- Просмотр змейки в графическом файле snake.gif
Таким образом, имеет смысл использовать эти значения в сетке, определенной как блок [WIDTH * HEIGHT]. Поскольку в сетке всего 256 мест, я решил сохранить их в одномерном массиве. Каждая координата в сетке 16 x 16 представляет собой целое число от 0 до 255. Мы использовали целые числа, чтобы вы могли увеличить сетку. Все определяется с помощью #defines с WIDTH и HEIGHT, обе 16. Поскольку размер змеиной графики составляет 48 x 48 пикселей (GRWIDTH и GRHEIGHT #define), окно изначально определяется как 17 x GRWIDTH и 17 x GRHEIGHT, чтобы быть немного больше, чем сетка. .
Это дает преимущества в скорости игры, поскольку использование двух индексов всегда медленнее, чем один, но это означает, что вместо добавления или вычитания 1 из координат Y змеи для вертикального перемещения вы вычитаете WIDTH. Добавьте 1, чтобы переместиться вправо. Однако из-за хитрости мы также определили макрос l (x, y), который преобразует координаты x и y во время компиляции.
Что такое макрос?
# определить l (X, Y) (Y * WIDTH) + X
data-type = "code">
Первая строка - это индекс 0-15, вторая 16-31 и т. Д.Если змея находится в первом столбце и движется влево, то проверка на попадание в стену перед перемещением влево должна проверять, если координата% WIDTH == 0 и координата правой стены% WIDTH == WIDTH-1. % - это оператор модуля C (например, арифметика часов) и возвращает остаток после деления. 31 div 16 оставляет остаток 15.
Управление змейкой
В игре используются три блока (целые массивы).
- змейка [], кольцевой буфер
- shape [] - Содержит графические индексы Snake
- dir [] - определяет направление каждого сегмента змеи, включая голову и хвост.
В начале игры змея состоит из двух сегментов с головой и хвостом. Оба могут указывать в 4 направлениях. Для севера голова имеет индекс 3, хвост - 7, восток - 4, хвост - 8, южный - 5, хвост - 9, а для запада голова - 6, а хвост - 10. У змеи в длину два сегмента, голова и хвост всегда разнесены на 180 градусов, но после того, как змея вырастет, они могут быть на 90 или 270 градусов.
Игра начинается с головы, обращенной на север в точке 120, а хвостом на юг в точке 136, примерно в центре.При небольшой стоимости около 1600 байт памяти мы можем получить заметное улучшение скорости игры, удерживая местоположения змейки в кольцевом буфере snake [], упомянутом выше.
Что такое кольцевой буфер?
Кольцевой буфер - это блок памяти, используемый для хранения очереди фиксированного размера, который должен быть достаточно большим для хранения всех данных. В данном случае это просто змея. Данные помещаются в начало очереди и снимаются с нее. Если передняя часть очереди попадает в конец блока, она оборачивается.Пока блок достаточно велик, передняя часть очереди никогда не догонит заднюю.
Каждое местоположение змеи (то есть единственная координата int) от хвоста до головы (то есть назад) сохраняется в кольцевом буфере. Это дает преимущества в скорости, потому что независимо от того, как долго змея продвигается, во время движения нужно менять только голову, хвост и первый сегмент после головы (если он существует).
Хранение в обратном направлении также полезно, потому что, когда змея получает пищу, змея вырастет, когда она будет двигаться в следующий раз.Это достигается перемещением головки на одно место в кольцевом буфере и изменением старого положения головки на сегмент. Змея состоит из головы, 0-n сегментов), а затем хвоста.
Когда змея ест еду, переменная atefood устанавливается в 1 и проверяется в функции DoSnakeMove ().
Перемещение змеи
Мы используем две индексные переменные, headindex и tailindex, чтобы указывать на расположение заголовка и хвоста в кольцевом буфере. Они начинаются с 1 (заголовок) и 0.Таким образом, ячейка 1 в кольцевом буфере содержит местоположение (0–255) змейки на доске. В позиции 0 находится хвостовая часть. Когда змея перемещается на одно место вперед, и tailindex, и headindex увеличиваются на единицу, оборачиваясь до 0, когда они достигают 256. Итак, теперь местоположение, которое было головой, находится там, где находится хвост.
Даже с очень длинной змеей, которая вьется и свернута, скажем, на 200 сегментов. только индекс головы, сегмент рядом с головой и хвостовой индекс изменяются при каждом перемещении.
Обратите внимание, что из-за того, как работает SDL, мы должны рисовать всю змею в каждом кадре. Каждый элемент помещается в буфер кадра, а затем переворачивается для отображения. Однако у этого есть одно преимущество в том, что мы можем рисовать змею, плавно перемещая несколько пикселей, а не всю позицию в сетке.
Змея | TI-BASIC Wiki | Фэндом
Я сам снимал на видео
Это стандартная игра про змей. Вы едите пищу и избегаете нарваться на хвост.Это включает в себя случайно сгенерированные кирпичные вещи и завернутый экран.
Переменные, которые вам понадобятся:
L ЗМЕЯ
А
B
C
U
V
W
Икс
Z
θ (вы можете использовать любые переменные, которые захотите, но это то, что используется в нашем примере кода ниже)
Здесь использовались три основные программы: prgmGSNAKEA (анимация открытия), prgmGSNAKE (сама игра) и prgmGSNAKER (последовательность возрождения еды).
Начальная анимация - это подпрограмма, вызываемая в начале игры.
ClrHome Func FnOff 0,1,2,3,4,5,6,7,8,9 УчасткиОфф 1,2,3 ClrDraw CoordOff GridOff AxesOff LabelOff ExprOff -62 → Ymin 1 → ΔX 1 → ΔY 36.64 → А 0 → θ 25 → Я 0 → X Пока fPart (A)>. 03 и θ = 0 Х + 1 → Х getKey → θ А + 3 ((Z = 26) - (Z = 24)) -. 02 (Z = 25) → А Pt-On (iPart (A), - 100fPart (A), 2) Если X = 5 26 → Я Если X = 11 25 → Я Если X = 17 24 → Я Если X = 23 25 → Я Если X = 29 26 → Я Если X = 35 25 → Я Конец Для (X, 0,50) Если θ 50 → X getKey → θ Конец
Объяснение [править | править источник]
ClrHome Func FnOff 0,1,2,3,4,5,6,7,8,9 УчасткиОфф 1,2,3 ClrDraw CoordOff GridOff AxesOff LabelOff ExprOff
Этот раздел гарантирует, что на экране не будет отображаться ничего вроде графиков или осей, поэтому ваше прекрасное изображение не будет прервано, например, Y = log (X), разрывающимся по экрану.
0 → Xмин. -62 → Ymin 1 → ΔX 1 → ΔY
В этом разделе экран настраивается таким образом, чтобы каждый пиксель имел целочисленный эквивалент (от 0 до 94, от -62 до 0).
36,64 → А 0 → θ 25 → Я 0 → X
Устанавливает начальные значения для направления движения змейки, места ее начала и ввода пользователя. Целая часть «A» - это координата «X», а десятичная - координата «Y». «Z» - это направление, в котором он движется (26 = вправо, 24 = влево, 25 = вверх). «θ» используется для последнего нажатия клавиши. «X» - это просто счетчик.
Пока fPart (A)>. 03 и θ = 0 Х + 1 → Х getKey → θ А + 3 ((Z = 26) - (Z = 24)) -. 02 (Z = 25) → А Pt-On (iPart (A), - 100fPart (A), 2)
Эта часть важна для моей игры, урезанной только до трех направлений движения и дисплея. Вместо того, чтобы писать анимацию отдельно, я скопировал часть реальной игры и просто рассчитал время изменения направления, имитируя ввод пользователя.
Если X = 5 26 → Я Если X = 11 25 → Я Если X = 17 24 → Я Если X = 23 25 → Я Если X = 29 26 → Я Если X = 35 25 → Я Конец
Это та часть, у которой змейка меняет направление через каждый промежуток времени.Это заставляет змею перемещаться по экрану по S-образной схеме.
Для (X, 0,50) Если θ 50 → X getKey → θ Конец
Это просто приостанавливает его на мгновение. В любой момент анимация пропускается нажатием любой клавиши и переходит прямо к игре.
prgmGSNAKEA Фунт 0 ClrHome Меню («ЗМЕЯ!», «ИГРАТЬ», 1, «ПОМОЩЬ», 2, «ВЫХОД», 3 // 5 пробелов, ЗМЕЯ !, 5 пробелов Фунт 3 ClrHome ClrDraw Выход (1,1, " Стоп Фунт 2 ClrHome Выходные данные (1,1, "НАЖИМАЙТЕ СТРЕЛКИ, ЧТОБЫ ПЕРЕМЕСТИТЬ. ВЫ ЗМЕЯ. ВЫ ДОЛЖНЫ ЕСТЬ ЕДА, ЧТОБЫ ПОЛУЧИТЬ ОЧКИ.НЕ БЕГАЙТЕ В ХВОСТ ИЛИ КИРПИЧ ". Пауза ClrHome Перейти 0 Фунт 1 ClrHome Func FnOff 0,1,2,3,4,5,6,7,8,9 УчасткиОфф 1,2,3 ClrDraw CoordOff GridOff AxesOff LabelOff ExprOff 0 → Xmin -62 → Ymin 1 → ΔX 1 → ΔY {8.02,5.02,2.02} → L ЗМЕЯ 8.02 → А 32.02 → В 0 → С Для (U, 0,30 3randInt (2,29) -1 → V 1-3randInt (2,19 → W Pt-On (В, Вт, 2 Pt-On (В, Вт Конец Пт-Он (8, -2,2 Пт-Он (5, -2,2 Пт-Он (2, -2,2 Pt-On (параметрический ряд (B), - 100фчасть (B), 3 DelVar θ 26 → Я По горизонтали 0 По горизонтали -61 Вертикальный 0 Вертикальный 94 Пока θ ≠ 23 и θ ≠ 45 getKey → θ Если (θ = 24 и Z 26) или (θ = 25 и Z 34), или (θ = 26 и Z 24), или (θ = 34 и Z 25) θ → Z A + 3 ((Z = 26 и параметрическая часть (A) <92) - (Z-24 и параметрическая часть (A)> 2)) + 90 ((Z = 24 и параметрическая часть (A) = 2) - (Z = 26 и iPart (A) = 92)) +.03 ((Z = 25 и fPart (A) <. 59) - (Z = 25 и fPart (A)>. 02)) +. 57 ((Z = 25 и fPart (A) =. 02) - (Z = 34 и fPart (A) =. 59)) → A Если fPart (C) = 0 Pt-Off (iPart ( L, SNAKE (тусклый ( L, SNAKE, -100f)) Деталь ( L, SNAKE (тусклый ( L, SNAKE, 2 Если pxl-Test (100fPart (A) -1, iPart (A) -1) = 1 45 → θ Pt-On (iPart (A), - 100fPart (A), 2 увеличение ({A}, L, ЗМЕЯ) → L, ЗМЕЯ тусклый ( L ЗМЕЯ) -не (fPart (C)) → тусклый ( L ЗМЕЯ C-100 -1 (fPart (C) ≠ 0) → C Если A = B prgmGSNAKER Конец Пауза ClrHome Disp "ОЦЕНКА: Выход (1,8, параметрический ряд (C) Пауза ClrHome Перейти 0
Объяснение [править | править источник]
prgmGSNAKEA Фунт 0 ClrHome Меню («ЗМЕЯ!», «ИГРАТЬ», 1, «ПОМОЩЬ», 2, «ВЫХОД», 3 // 5 пробелов, ЗМЕЯ !, 5 пробелов
Эта часть запускает анимацию запуска и является главным меню.
фунтов 3 ClrHome ClrDraw Выход (1,1, " Стоп
Выход из игры. Выход прямо перед остановкой не дает «Готово» появиться в конце. Примечание. Экран графика не сбрасывается, поэтому его размеры и настройки останутся прежними.
фунтов 2 ClrHome Выходные данные (1,1, «НАЖИМАЙТЕ СТРЕЛКИ, ЧТОБЫ ПЕРЕМЕСТИТЬ. ВЫ ЗМЕЯ. ВЫ ДОЛЖНЫ ЕСТЬ ЕДА, ЧТОБЫ ЗАРАБАТЫВАТЬ ОЧКИ. НЕ БЕГАТЬ В ХВОСТ ИЛИ КИРПИЧ.» Пауза ClrHome Перейти 0
Экран помощи. Этот экран намного лучше, чем тот, который я лично написал для своего, который состоит из «БЛРГ!».
фунтов 1 ClrHome Func FnOff 0,1,2,3,4,5,6,7,8,9 УчасткиОфф 1,2,3 ClrDraw CoordOff GridOff AxesOff LabelOff ExprOff 0 → Xmin -62 → Ymin 1 → ΔX 1 → ΔY
Это снова устанавливает правильные настройки экрана графика.
{8.02,5.02,2.02} → L ЗМЕЯ
В этом списке указаны координаты частей вашей змеи. Целое число - это координата X, а десятичное число - координата Y, деленная на 100.
8,02 → А
«A» - координаты «X» и «Y» головы змеи.Начинается с 8,2; поэтому A = 8,02
32,02 → В
B - это еда. Он начинается в одном месте, а затем случайным образом прыгает, когда его едят.
0 → С
C - это счет и счетчик еды. Целая часть - это счет, а десятичная - это счетчик того, как долго растет ваш хвост, когда вы едите.
Для (U, 0,30 3randInt (2,29) -1 → V 1-3randInt (2,19 → W Pt-On (В, Вт, 2 Pt-On (В, Вт Конец
Это цикл, который помещает на экран случайные блоки.
Пт-Он (8, -2,2 Пт-Он (5, -2,2 Пт-Он (2, -2,2 Pt-On (параметрический ряд (B), - 100фчасть (B), 3
Рисует еду и змею.
DelVar θ 26 → Я По горизонтали 0 По горизонтали -61 Вертикальный 0 Вертикальный 94
Рисует стены и задает начальное направление вправо (26). Z - это направление, в котором движется змея, на основе кодов клавиш.
Пока θ ≠ 23 и θ ≠ 45
Постоянная повторяется, пока вы не нажмете Delete, Clear или пока игра не установит θ на 45.
getKey → θ
Пользовательский ввод. Тета - это ваше нажатие.
Если (θ = 24 и Z 26) или (θ = 25 и Z 34), или (θ = 26 и Z 24), или (θ = 34 и Z 25) θ → Z
Этот оператор «If» проверяет, нажата ли клавиша со стрелкой и вы не пытаетесь изменить свое направление.Если это так, новое направление сохраняется как «Z». Если вы не знаете, какие коды клавиш, 24 = влево, 25 = вверх, 26 = вправо, 34 = вниз.
A + 3 ((Z = 26 и параметрическая часть (A) <92) - (Z-24 и параметрическая часть (A)> 2)) + 90 ((Z = 24 и параметрическая часть (A) = 2) - (Z = 26 и iPart (A) = 92)) +. 03 ((Z = 25 и fPart (A) <. 59) - (Z = 25 и fPart (A)>. 02)) +. 57 ((Z = 25 и fPart (A) =. 02) - (Z = 34 и fPart (A) =. 59)) → A
Этот длинный код представляет собой последовательность движения. Экран разделен на блоки размером 3x3 пикселя, поэтому змейке нужно каждый раз перемещать на 3 пикселя, чтобы случайно не умереть.
Если fPart (C) = 0 Pt-Off (iPart ( L SNAKE (dim ( L SNAKE))), - 100fPart ( L SNAKE (dim ( L SNAKE))), 2
Если десятичная дробь "C" (счетчик) равна 0, тогда он удаляет последний блок в змейке, чтобы сохранить его ту же длину. Если это не последний блок, его не удаляют, поэтому хвост становится длиннее, но не расширяется наружу. Хвост остается на месте, а голова продолжает двигаться.
Если pxl-Test (100fPart (A) -1, iPart (A) -1) = 1 45 → θ
Если угол головы находится в пикселе, который включен, игра завершается.Единственное, что это делает, - это сами блоки змей и блоки на карте. Поскольку голова еще не нарисована, это означает только столкновение с вещами.
Pt-On (iPart (A), - 100fPart (A), 2
Это показывает, где находится голова.
Увеличение
({A}, L ЗМЕЯ) → L ЗМЕЯ
Это добавляет новые координаты в список координат змеи.
тусклый ( L ЗМЕЯ) -не (fPart (C)) → тусклый ( L ЗМЕЯ C-100 -1 (fPart (C) ≠ 0) → C
Если счетчик выключен, он удаляет последний элемент в списке.Если счетчик включен, это не так. Он также уменьшает счетчик, если он не равен нулю.
Если A = B prgmGSNAKER
Возрождает еду после того, как ее съели.
Конец
Конец цикла.
Пауза ClrHome Disp "ОЦЕНКА: Выход (1,8, параметрический ряд (C) Пауза ClrHome Перейти 0
Отображает счет и возвращает вас в главное меню.
Возрождение еды сложное, поэтому я сделал его подпрограммой.
Pt-Off (iPart (B), - 100fPart (B) Не повторяйте (pxl-Test (100fPart (B) -1, iPart (B) -1)) и сумму ({pxl-Test (100fPart (B) -4, iPart (B) -1), pxl-Test (100fPart) (B) + 2, параметрический ряд (B) -1), параметрический элемент (B) -1, параметрический ряд (B) -4), параметрический элемент (B) -4), параметрический элемент (B) +2 )}) <3 3randInt (2,29) -1+.01 (3randInt (2,19)) - 1) → В Конец С + 1.03 → С Pt-On (параметрический ряд (B), - 100фчасть (B), 3
Объяснение [править | править источник]
Pt-Off (iPart (B), - 100fPart (B)
Удаляет старые продукты с экрана. Поскольку еда находится в кресте и окружена рамкой, нам нужно только отключить средний пиксель, чтобы удалить его.
Не повторяйте (pxl-Test (100fPart (B) -1, iPart (B) -1)) и сумму ({pxl-Test (100fPart (B) -4, iPart (B) -1), pxl-Test ( 100fPart (B) + 2, iPart (B) -1), pxl-Test (100fPart (B) -1, iPart (B) -4), pxl-Test (100fPart (B) -1, iPart (B) + 2)}) <3
Эти большие условные проверки «Повторить», чтобы убедиться, что еда не появляется на вашем хвосте или другом кирпиче.Он также проверяет, открыты ли по крайней мере два места рядом с ним, чтобы он не появлялся там, где вы не можете добраться до него.
3randInt (2,29) -1 + .01 (3randInt (2,19)) - 1) → B Конец
Это случайным образом помещает еду на экран и порождает ее в случайном порядке.
С + 1,03 → С Pt-On (параметрический ряд (B), - 100фчасть (B), 3
Добавляет одно очко, устанавливает таймер на три и рисует новую частицу пищи.
Это работает как шарм на моем TI-84 plus Silver Edition, но я не знаю, как это работает на других устройствах.
LordoftheBleak
Это более новая и, вероятно, более быстрая версия той же игры.
: ClrHome
:Плавать
: Func
:Полный
: FnOff
: PlotsOff
: CoordOff
: GridOff
: AxesOff
: LabelOff
: ExprOff
: ClrDraw
: 0 → Xmin
: -62 → Yмин.
: 1 → ΔX
: 1 → ΔY
: Lbl 0
: Меню («ЗМЕЯ ИГРА», «ИГРАТЬ», 1, «ВЫХОД», 2
: Lbl 1
: Для (θ, 0,3
: Вертикальный θ
: По вертикали θ + 91
:Конец
: Для (θ, -2,0
: По горизонтали θ
: По горизонтали θ-60
:Конец
: {5.31 → L 1
: 5,31 →
: 47,31 → I%
: 26 → PV
: .03 → PMT
: Пт-Он (47, -31,3
: Пока нет (pxl-Test (t ( E 2fPart ( N ) -1, iPart ( N -1
: Pt-On (iPart (( N ), - fPart ( N ), 2
: getKey → FV
: Если FV = 45 или ((FV = 24 и PV 26) или (FV = 25 и PV 34) или (FV = 26 и PV 24) или (FV = 34 и PV 25)
: FV → PV
:: N +3 ((PV = 26) - (PV = 24) +.01 ((PV = 34) - (PV = 25 → N
: увеличение ({{ N }, L 1 → L 1
: Если нет (fPart (PMT
: Pt-Off (iPart (Ans (dim (Ans))), -, - E 2fPart (Ans (dim (Ans))), 2
: dim (Ans) -not (fPart (PMT → dim (L 1 )
: PMT-.01 (0 ≠ fPart (PMT → PMT
: Если N ≠ I%
:Потом
: Pt-Off (iPart (I%), -, - E 2fPart (I%)
: Повторять нельзя (pxl-Test (( E 2fPart (Ans) -1, iPart (Ans
: 3randInt (2,30) -1 + .01 (1 + 3randInt (1,10
:Конец
: Ans → I%
: Pt-On (iPart (I%), - E 2fPart (I%), 3
: PMT + 1.03 → PMT
:Конец
:Конец
:Пауза
: ClrDraw
: Выход (1,8, параметрический ряд (PMT
: Пауза "ОЦЕНКА:
: ClrHome
: Перейти 0
: Lbl 2
: Delvar L: Delvar L 1
: ClrHome
: Выход (1,1, "
.