Pull to refresh

Обучение программированию: рисование пальцами и убийство зомби

Reading time 14 min
Views 8.4K
Original author: Jeremy Kun

Zmob, моя первая (и единственная) собственная игра.

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

Образование против исследований


В этом семестре я работаю помощником преподавателя вводного курса по Python, и это вгоняет меня в депрессию. Я вспоминаю свои ранние годы программирования, когда возможности казались почти безграничными, а добавление новых возможностей в мои программы было восхитительным и радостным процессом. Я гордился каждой строкой, хвастал друзьям о том, какие потрясающие вещи мне удавалось сделать, и чувствовал себя всемогущим. Мир в буквальном смысле менялся под моими пальцами. Я мог облечь в плоть практически любую идею, которая мне была интересна, и любую грань жизни, которую хотел исследовать. Я развил в себе неутолимую жажду к программированию, которая остаётся со мной и по сей день.


Так бы я выглядел в юности, если бы в программировании было больше спагетти.

Ирония заключается в том, что когда я сегодня оглядываюсь на написанные мной программы, то ощущаю бесконечное смущение. Мой старый код представлял собой художественный эквивалент детсадовского рисования пальцами. Да, рисунок напоминает лошадь, но только потому, что ребёнок не понимает, что делает. В написанных мной программах была куча багов, их сложно читать, они плохо упорядочены и полны запутанной спагетти-логики.

Но я не могу отрицать, насколько это было интересно, и сколь многому я научился. Я расскажу об этом подробнее, но для начала хочу противопоставить собственный опыт с тем, как ведут себя студенты в этом семестре. Их лабораторные работы — не более чем бессмысленное перетасовывание данных и реализация сухих, скучных функций наподобие схемы Горнера для вычисления многочленов. И (на удивление) их самостоятельные проекты ещё менее интересны. Я думаю, что самая большая разница в том, что моим студентам на самом деле не нужно решать задачи, создавая программы. Поэтому их опыт сводится к повторению инструкций и к тому, что они притворяются компьютерами, чтобы следовать логике собственных программ.

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

У курса тоже есть собственные проблемы. Например, профессор обучает студентов парадигмам C, а не Python (не думаю, что он вообще учился делать что-то правильно в Python), и их сбивает с толку, когда он говорит о фреймах стеков, регистрах и всевозможных не относящихся к делу аспектах архитектуры. Не забывайте, эти студенты раньше никогда не писали программы, а некоторые приступили к курсу едва знакомыми с компьютером. Я не знал, что такое фрейм стека, пока не проучился программированию три года (два из которых были годами моих первых экспериментов).

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

Мой первый опыт был с Java


К счастью, в моих старших классах (в Campolindo High, город Морага, Калифорния) был курс информатики. Я с раннего детства был знаком с компьютерами (с трёх лет, благодаря моим родителям), любил видеоигры и знал основы HTML, поэтому я неизбежно должен был попасть на этот курс. В ретроспективе я считаю его, возможно, самым полезным для меня из всех курсов, следующими за ним идут углублённый английский, немецкий и общественная политика. Он не только дал мне вышеупомянутую жажду программирования, но и зародил во мне математическое зерно, выросшее много лет спустя в огромный бобовый стебель, по которому я взбираюсь до сих пор.

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

Грубо говоря, для завершения курса можно было выбрать один из двух вариантов. Первый: следовать ходу занятий и небольших проектов из учебника по программированию GUI на Java. Большинство студентов примерно два первых месяца выполняло задания, и я тоже, по крайней мере до тех пор, пока не сделал глупую маленькую программу для пиццерии, позволявшую заказывать пиццу.

Второй вариант был гораздо более свободным. Студенты могли делать что угодно — мистер Мэтерс шутливо говорил нам: «в конце каждого квартала я буду оценивать ваши успехи, и если вы заслужите пятёрку, то получите её, но в противном случае получите единицу!»

Разумеется, мистер Мэтерс был самым хорошим человеком из тех, которые мне встречались. Он спокойно сидел за своим компьютером во главе класса, и помогал студентам, подходившим к нему с вопросами. Он спокойно и тихо выслушивал вопрос студента, а потом подсказывал правильный способ видения задачи. Мистер Мэтерс лучше понимал уровень успешности студентов по тому, насколько частыми и уместными были их вопросы, и чаще всего стоявшие в очереди студенты самостоятельно решали свои проблемы ещё до того, как добирались до учителя.

Большинство студентов класса выбрало дорогу «широких возможностей», и это означало, что они хотели разрабатывать игры. Я не говорю о хороших играх, я говорю об играх, создаваемых старшеклассниками. Я написал самую уродливую в мире игру «ударь крота», тупой ИИ для «Морского боя», покерный автомат, в котором на рубашке всех карт было лицо мистера Мэтерса (оно же заменяло лица всех королей, дам и валетов). Для создания последней игры я даже сотрудничал с другими студентами, которые использовали в своих карточных играх колоду с лицом мистера Мэтерса. Сотрудничество стало ещё более тесным во второй год прохождения курса (да, я ещё раз прошёл тот же курс), но прежде чем мы перейдём к нему, я должен рассказать ещё кое-какие необходимые подробности.

Во-первых, сеть в кабинете была устроена таким образом, что мистер Мэтерс мог удалённо контролировать со своего компьютера любой компьютер в классе. Программа для наблюдения имела вселяющее почтение имя «Vision» и халтурщики трепетали перед её мощью. Vision позволяла мистеру Мэтерсу смотреть наш код, пока мы стояли перед его столом, и помогала ему отслеживать прогресс студентов. Во-вторых, нам позволялось использовать общий диск в школьной сети, поэтому мы могли передавать файлы между компьютерами в кабинете. Это давало прямые выгоды в обучении, например, мы могли делиться примерами кода, спрайтами и звуковыми файлами из наших программ. Но что более важно, это давало классу чувство общей культуры. Иногда мы проводили соревнования, в которых каждый студент класса фотошопил лицо Мэтерса в какую-нибудь забавную сцену (на самом деле, мы использовали MS-Paint). Это было время зарождения Интернет-мемов, и естественно, что мы, молодёжь, были на острие прогресса.

В-третьих, у нас часто бывали дни «исследований», в которые нас освобождали от всяких учебных обязательств. Мы могли играть в игры, лазить в Интернете, просто сидеть и общаться. Чаще всего всё заканчивалось игрой в Unreal Tournament по LAN, но к концу моего второго кода я решил использовать эти дни и для работы над своими программами; мне интереснее было работать над своими играми, чем играть в чужие.

Всё это придавало курсу ощущение расслабленности. Нас не готовили к промежуточным проверкам знаний или углублённым экзаменам (хотя со временем я сдал и их, получив довольно высокий результат, учитывая, что совсем к ним не готовился). Иногда нас даже не заставляли работать. Курс был больше направлен на создание сообщества и даже несмотря на то, чтобы подтрунивали над мистером Мэтерса, мы уважали его как учителя.

Как и я, большинство студентов, окончивших первый год, осталось на ещё один. И вот тогда у нас начали появляться потрясающие проекты.

Zmob


Второй год курса информатики был полностью посвящён играм. Более того, мы начали задумываться об играх реального времени, типа сайдскроллерных платформеров, в которые любили играть сами (да, Super Mario Brothers и Donkey Kong Country). Я попытался сделать нечто подобное самостоятельно, но быстро потерялся в том, как заставить работать коллизии. Создание уровней, анимирование персонажей и скроллинг экрана были сложными задачами, но посильными для меня.


Один из моих первых сайдскроллеров, вдохновлённый серией Starfox.

Но когда мне надоело учить персонажа запрыгивать на блоки, я нашёл проект получше: Zmob (сокращённо от Zombie Mob). Он был вдохновлён нашим сотрудничеством. Я помогал другу найти способ отрисовки двух кругов особым образом: один круг был большим и неподвижным, а второй был меньше, всегда касался первого, а линия между двумя центрами проходила через положение курсора мыши. Другими словами, меньший круг представлял собой «направление» пары кругов и всегда был направлен в сторону мыши. Это была довольно простая тригонометрия, но прежде чем я понял, как это сделать, я решил, что интересно будет поработать над зомби-стрелялкой с видом сверху. Поэтому я приступил к этому проекту. Вот как выглядела заставка ранней версии игры (опечатки и ошибки полностью сохранены):


Начальный экран Zmob 1.0

Игрок управляет чёрным кругом, а серым кругом обозначено оружие. Зомби (синие круги) регулярно создаются в случайных позициях и движутся с разной скоростью прямо в сторону персонажа. Игрок может ходить по полю, а зажав Shift, какое-то время бегать быстрее, чем они. И разумеется, игрок стреляет в них, пока они не умрут, а игра заканчивается, когда умирает персонаж. Количество создаваемых зомби со временем увеличивается, а боеприпасы ограничены (однако можно получить больше боеприпасов после определённого количества убийств), так что смерть игрока неизбежна. Цель игры — набрать как можно больше очков.

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

Важно было то, что у меня появлялась куча идей, и мне нужно было понять, как реализовать эти идеи. Я хотел, чтобы зомби не проходили сквозь друг друга. Я хотел сделать оружие, отравлявшее зомби, чтобы когда отравленный зомби касался здорового, то тоже отравлял его. Мне хотелось, чтобы в игре происходила куча вещей, и решения естественным образом приходили в виде особенностей Java, которые я использовал.


Отравляющая пушка. Белые зомби отравлены, синие — здоровы.

Например, в начале зомби обозначались просто кругами. Не было никакой информации, отличавшей одного зомби от другого, поэтому я мог хранить их просто как список координат x,y. Когда я захотел добавить им полоску жизни, и разные скорости, и научиться отравлять их, мне пришлось создать класс зомби, чтобы можно было задавать каждому зомби его внутреннее состояние (отравлен или нет, быстрый или медленный и т.д.). Затем я создал класс игрока, класс предметов и класс пуль.

И пули оказались самой интересной для меня частью. Я хотел, чтобы каждая пуля на экране обновлялась только при вызове мной функции «update()». Оказалось, что это было аналогично преобразованию пули в интерфейс, от которого наследовал каждый специализированный класс пуль. Я уже увидел необходимость и элегантность объектно-ориентированного программирования. Всё это совершенно ускользало от меня, когда я делал эти глупые интерфейсы «Shape», повторяя за вводными туториалами. Я решал задачу, которую мне нужно было решить, и понимание наследования навсегда осталось в моём сознании.

Логика пуль сама по себе была удовольствием. Первые три созданных мной пушки были скучными: пистолет, пулемёт и дробовик. Как обычно, они разбрасывали мелкие чёрные круги. Я хотел выделиться и создать крутую пушку. Первую свою идею я назвал волновым лучом.


Волновой луч: синусоидные пули.

Идея волнового луча заключалась в том, что пули движутся в направлении выстрела по синусоиде. Однако для меня это стало огромной сложностью: как можно повернуть синусоиду на произвольный угол? У меня были координаты x и y пуль, но все изощрённые формулы, в которых я случайным образом пробовал использовать синусы, косинусы и тангенсы, с треском проваливались. Лучшее, чего мне удалось добиться — получить некрасиво растянутую в стороны синусоиду.

Через неделю безуспешных попыток я подошёл к своему учителю по статистике (с которым до сих пор поддерживаю связь) и спросил его, не знает ли он колдовские математические формулы, которые мне нужны.

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


В результате я написал следующий код:

x += frame*Math.cos(angle) + Math.sin(frame)*Math.sin(angle)
y += frame*Math.sin(angle) + Math.sin(frame)*Math.cos(angle)

Когда я запустил код, он работал настолько идеально, что я закричал от восторга. Спустя неделю мучений и бездарных попыток решить проблему решение оказалось элегантным, красивым и чудесным. После этого я использовал матанализ, чтобы прыжки в моём сайдскроллере по StarFox выглядели более естественными. Я экспериментировал с другими матричными операциями, такими как сдвиг и растяжение. К концу того года я лучше стал разбираться в «замене базиса» (хотя даже не знал ещё этих слов), чем большинство студентов, изучавших линейную алгебру в колледже! Это просто была другая система координат для пространства; там были повёрнутые координаты, увеличенные и растянутые координаты, суженные и обратные координаты. Я испробовал всё это в поисках интересного геймплея.

И я обогнал программу обучения не только в математике. К концу года я «закончил» игру. Я создал цепное оружие, создававшее цепные реакции при попадании в зомби, улучшил внешний вид, добавив новую графику для игрока и зомби. Я даже разработал умную систему размещения тайлов, измерявшую размер экрана и окна, и правильно отображавшую фон. Мне надоело измерять размеры вручную, поэтому я написал программу, выполнявшую эту работу. Это кажется тривиальным, но на самом деле это самое ядро решения задач в информатике.


«Бета-тестингом» игры занимался весь класс, то есть мы просто провели несколько дней, играя в игру и находя баги. И мои одноклассники нашли много багов. Очевидные (ошибки деления на ноль сводили пули с ума) и менее заметные (если правильно рассчитать время, зомби не смогут достаточно близко подобраться к игроку и будут просто наталкиваться друг на друга).

Выявилась ещё одна довольно важная проблема — скорость. После добавления изображений я решил использовать библиотеку Java, чтобы поворачивать изображения в каждом кадре в нужном направлении. Сегодня некоторые говорят, что Java медленная, но эта часть была очень медленной, особенно когда дело доходило до сотни и больше зомби. Моё решение, так уж получилось, оказалось парадигмой программирования под названием кэширование. Я заранее вычислял все повороты, которые понадобятся в игре, а затем сохранял их. На самом деле, то, что реализовал я, называется ленивой загрузкой (lazy-loading). Это немного более сложная техника, используемая для хранения только вычисленных поворотов, когда они оказываются необходимы.

Я узнал название этой техники, только когда добрался до динамического веб-программирования, изучавшегося на третьем курсе колледжа и мы обсуждали объектно-реляционное отображение данных Hibernate для баз данных! Как и в случае с линейной алгеброй, задачи, которые я ставил перед собой самостоятельно, позволили мне открыть или изобрести заново важные концепции гораздо раньше, чем если бы мне их преподавали. Я развил в себе глубокое понимание концепций и тех типов задач, которые они могут решать. Это совершенно отличалось от того изучения, которое происходит в колледже: студенты запоминают название концепции и её значение, но только лучшим студентам удаётся понять, почему она важна и как её использовать.

Честный анализ в ретроспективе


Должен признать, что был больше предан своей работе, чем среднестатистический ученик. Небольшая часть класса занималась только глупостями. У некоторых студентов была цель в голове, но они не преследовали её с той же настойчивостью, что и я. У нас не было доступа к множеству хороших примеров за пределами собственных блужданий по Интернету и посредственного качества книг, имевшихся у мистера Мэтерса. Выбранный язык Java для некоторых, наверно, имел слишком крутую кривую обучения, но в результате он дал больше пользы, чем вреда.

Но с другой стороны, те из нас, кто хорошо работал, исследовали всё самостоятельно и усваивали материал в собственном темпе. Мы сражались с задачами, которые сами хотели решить, что приводило нас к новым озарениям. Один из моих одноклассников сделал чат-клиент и сетевую версию Tron, другие написали ролевые игры, музыкальные приложения, алгоритмы шифрования и многое другое. К концу курса ширина и глубина нашего коллективного знания была гораздо больше, чем кто-либо мог ожидать от любого курса старшей школы по любому предмету. Я говорю это не наобум: я потратил много времени на анализ литературы, на обсуждение современных проблем, на запоминание немецкого словаря, на редактирование эссе и проведение биологических экспериментов, но программирование было особым случаем. Этот опыт был и увлекательным, и художественным, и техническим, и логическим, и интуитивным. Более того, этот навык оказался полезным для меня на рынке труда. Сегодня я могу выпуститься из магистратуры и найти хорошую работу инженера-разработчика ПО в любом крупном городе и наверно в любой из отраслей, в которой занимаются программным обеспечением. Именно этот курс наставил меня на тот путь, которым я иду сейчас.

Хуже всего то, что моё сердце разбивается, когда мои студенты говорят: «я не думал, что программирование будет похоже на это». К этому я не готов. Самое лучшее, что я им могу ответить: «Не судите о программировании по этому курсу. На самом деле оно может быть увлекательным».

Что им нужно


Для меня абсолютно понятно — чтобы заинтересовать студентов в программировании, им нужна пара вещей:

1. Мгновенное вознаграждение

Мои студенты проводят слишком времени, разбираясь в собственном коде. Нужно что-то изменить так, чтобы они видели эффект сразу же. Им нужны инструменты обучения, разработанные Бретом Виктором (чтобы понять, о чём я говорю, перемотайте видео на 10:30, но оно стоит того, чтобы посмотреть целиком). И им нужно работать над визуальными программами. Программами для рисования, играми и музыкой. Программами, чей эффект можно прочувствовать неинтеллектуальным способом, а не скучной проверкой того, правильно ли они считают производные многочленов.

2. Проекты должны быть значимыми, или хотя бы интересными.

Как и тогда, когда я учился сам, студентам нужна возможность исследования. Позвольте им работать над собственными проектами и как учитель обладайте достаточными знаниями, чтобы помогать им, когда они зайдут в тупик (или ещё лучше — устраивайте мозговой штурм вместе с ними). Если создание уникального для каждого студента проекта невозможно, то хотя бы пусть они работают над чем-то значимым. Увы, но в последних двух проектах курса, который я преподаю, студенты занимались вводом/выводом файлов и сложением матриц. Почему бы не позволить им поработать над видеоигрой или над поисковым движком (это может казаться сложным, но вот вводный курс по теме на udacity), или над рисованием/анимацией, над чат-клиентом, над решением судоку? Да хотя бы покажите, как получать данные с Facebook через Graph API. Все эти вещи могут быть в значительной степени абстрактными, чтобы с ними смог справиться студент любого уровня, и в то же время каждая из них требует способности работать с определёнными концепциями (основы работы с сетью для чат-клиента, матрицы для судоку, ввод-вывод файлов в частях поискового движка, и т.д.). Несмотря на избыток интересных вещей, над которыми могут работать студенты, преподаватели, похоже, просто не хотят давать им интересных проектов, поэтому снова и снова заставляют студентов вычислять суммы матриц.

3. Возможность читать чужой код.

Это неотъемлемая часть обучения. Студенты не только должны уметь писать свой код, но и читать чужой. Они должны уметь смотреть на примеры и выбирать из них важные части для использования в собственной работе. Они должны научиться сотрудничать со своими одноклассниками, работать над общим проектом и проводить мозговые штурмы при обсуждении багов. Они должны научиться критиковать код, как они могут критиковать фильм или кафе. Студенты должны иметь собственные суждения о ПО и стремиться к правильной реализации, в открытую высмеивая раздутый или хаотичный код (ну ладно, возможно, не так жестого, но они всё-таки должны быть сознательно готовы к этому).

Эти три принципа лежат в основе компьютерной науки и разработки ПО, а вся остальная чушь (кадры стеков, ленивая загрузка и оболочки Linux) может подождать до тех пор, пока студенты не заинтересуются и не начнут стремиться к большему. Больше того, это может подождать до тех пор, пока у них не появится возможность выбора области, которая требует знаний оболочек Linux, или веб-фреймворков, или сетевой безопасности, или обработки графики. Я научился всем основам и кое-чему ещё, не касаясь терминала Linux и даже не зная о его существовании. И я ни капли не сомневаюсь, что мои нынешние студенты тоже способны на это.

И когда студенты полностью погрузятся в код (как и я, потратив год-два на написание спагетти-кода), они могут научиться видеть красоту элегантных способов упорядочивания программ, и выразительную мощь, которой можно обладать для написания полезных программ. В каком-то смысле программирование похоже на архитектуру: хорошая программа обладает красотой формы и функционала. В этот момент они должны начать задумываться о системном и сетевом программировании, потому что тогда они смогут сравнивать новые парадигмы с собственными. Они могут критиковать, обсуждать и вносить инновации, или по крайней мере ценить их, и применять идеи к тем «зомби-проектам», над которыми они работают.

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

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

Поэтому я добавлю в свой вишлист преподавателя старших классов курс под названием «Программирование как рисование пальцами». (Или «Программирование на чистом листе»? Или «Как убить зомби»?) Я открыт к предложениям.
Tags:
Hubs:
If this publication inspired you and you want to support the author, do not hesitate to click on the button
+16
Comments 11
Comments Comments 11

Articles