Как в Магическом Электротехническом практика по Java прошла и немного про Lumia 710

в 12:46, , рубрики: game development, Gamedev, java, microsoft, windows phone, история, командная работа, обзор, Смартфоны и коммуникаторы, студенты, учебный процесс, Учебный процесс в IT

Так уж получилось, что я стал победителем конкурса от Microsoft и получил в качестве приза Nokia Lumia 710. После того, как на совместимость с мобильным IE были проверены все мои сайты, я подумал, что было бы неплохо протестировать уже сам смартфон в реальных условиях и написать обзор. Учебный год был закончен, и предстояла увлекательная летняя практика по Java, во время которой я и хотел протестировать смартфон… Но в результате, я больше программировал игру, чем разбирался с новым смартфоном.

Как в Магическом Электротехническом практика по Java прошла и немного про Lumia 710

Дисклаймер

Данный текст будет рассказывать мою историю с моей позиции. Рекомендуется к прочтению для отвлечения от серьёзных дел и в качестве таблеток реализма для абитуриентов.

Интерлюдия

Практика должна была занять период с 30 июня по 7 июля: курс лекций и выполнение практических заданий. Я уже работаю с Java и собирался во время практики немного систематизировать свои знания в добровольно-принудительном порядке. Я планировал описать каждый из дней практики, как проявил себя смартфон. После третьего дня я планировал обновить смартфон до 7.8, но жизнь внесла свои коррективы…

Пропустить Nokia и начать читать про практику

День первый

Он начался для меня несколько позже, чем для остальных. А всего-то хотел вспомнить разработку на Android вечером. Но как говорится, опоздание менее чем на академический час — не опоздание.
Меня встретила привычная аудитория и множество студентов, уже вкушающих прелести Java.

Фото с места событий

Как в Магическом Электротехническом практика по Java прошла и немного про Lumia 710

(Фото сделано на Lumia 710)

Итоги первой лекции

Конечно, первый день занятий после отдыха (сессии) не нёс фундаментальных знаний. Нам кратко рассказали об истории возникновении Java, областях применения; напомнили основные парадигмы ООП и особенности синтаксиса языка. Короче, день первым можно смело назвать «Введение в Java и очередной Hello World». Заодно нам рассказали о том, что практические занятия мы будем проходить под контролем практикующих аспирантов, коих нам представят в среду.
Жизнь жестока: хорошего не может быть много: лекция закончилась через три часа, вместо ожидаемых пяти. Но конечно, я не зря потратил эти три часа… (минус пятнадцать минут опоздания и пятнадцать минут перерыва).

Первые впечатления от Lumia

Знакомство с Люмией прошло не так гладко, как мне бы хотелось. Аппараты на WP я раньше видел лишь на картинках, но я надеялся, что опыт нескольких андроидфонов и симбиана мне помогут. Авторизировался в Люмии я уже давно, но всё что делал с ней до этого момента — это лишь сыграл в клона FlappyBird.

Как в Магическом Электротехническом практика по Java прошла и немного про Lumia 710

Чем же устройство меня порадовало на лекции? Прежде всего, я вошёл в Яндекс.Почту через стандартное приложение. Устройство адаптировано для российского потребителя, и можно было указать, что почта на Яндексе (видимо, лишив себя удовольствия вводить POP3 сервер) легко справившись с авторизацией (угадайте, нужно ввести в поле «имя пользователя» ваш логин на Яндексе или адрес электронной почты?) я был вынужден минут пятнадцать созерцать надпись «идёт синхронизация почты». К чести разработчиков, письма за последние две недели действительно загрузились на устройство. Оказавшись на «связи» (как я думал), настало время пошелудить Marketplace более подробно. Знаете, что пишут о Маркете? Вроде есть всё, а нет ничего. Я установил ещё пару тайм-киллеров (клон Doodle Jump и головоломку Portal Ball) и мессенджер ВК. С популяризацией html5 так сложилось, что количество standalone приложений на моих устройствах уменьшилось, но я постарался пробежаться по списку необходимых мне приложений. С ограниченным API WP7 на приложения типа Llama надеяться не приходится, но SSH терминал я нашёл с лёгкостью, а вот BT терминал… (ну вы знаете)

Мини-итог

И вот можно расходиться: смартфон разрядился до 20% (за три часа активного использования), про Java ничего нового я не услышал, набрал 10 очков в FlappyBat и обидел девушку молчанием из-за того, что после Android с его push-уведомлениями, проверка почты раз в час немного не подходит моему активному образу жизни. Маша, прости за моё молчание, я правда не знал, как работает почта на WP.

Как в Магическом Электротехническом практика по Java прошла и немного про Lumia 710

Немного сёрфинга не вызвали чувства отвращения: сайты открываются, JS выполняется и почти не зависает. Качество связи (кстати, в MarketPlace есть всевозможные SpeedTest) на уровне всех моих предыдущих аппаратов: толстые стены и количество активных пользователей мобильного интернета дают о себе знать: страницы грузятся, а вот скачивание приложений и видеороликов заставят взгрустнуть.

Лирическое отступление

После занятий я отправился в гости к своей сестре. Проходя по холлу, я заметил стенды для абитуриентов и на глаза мне попался…

Флаер с краткой информацией о направлении Программная Инженерия

Как в Магическом Электротехническом практика по Java прошла и немного про Lumia 710

(Фото сделано на Lumia 710)

Оказывается, я должен стать высококлассным специалистом, который много всего знает. Надеюсь, что предметы, связанные с вышеперечисленным, начнутся с третьего курса. Очень порадовала информация о том, кем работают выпускники направления (бакалавриату «Программная Инженерия» в ЛЭТИ всего пара лет и выпускников, как таковых, просто нет).

Немного про карты

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

Милая пара в метро

Как в Магическом Электротехническом практика по Java прошла и немного про Lumia 710

(Фото сделано на Lumia 710)

Меньше рабочего дня активного использования: очень непривычно после Android который вытяигавал два дня.

День второй

Во второй день нам рассказали о классах и интерфейсах, перечислили примитивные типы, базовые классы и самые полезные библиотеки, научили обработке исключений и разъяснили, что такое GC. Кажется, что я всё это знал, но послушать было действительно интересно. Никто даже не отвлекался от лектора.

Фото с места событий

Как в Магическом Электротехническом практика по Java прошла и немного про Lumia 710

(Фото сделано на Lumia 710)

А в перерыве, было обнаружено окно. Что не окно, а открытая дверь на крышу. Ну какой студент не пофотографируется на крыше? Я кстати, не фотографировался, а вот мои одногруппники подверглись там нападению сумасшедшего баклана.

Фото с крыши университета

Как в Магическом Электротехническом практика по Java прошла и немного про Lumia 710(Фото сделано НЕ на Lumia 710)

После лекции нас всех ждал сюрприз: теоретических занятий больше не будет, а через десяток минут нужно встретиться с аспирантом и определиться с практическими заданиями.
Вам слабо рассказать всю основу Java за пяток часов? Конечно, были даны книги и ссылки для домашнего чтения, но… Хотя если подумать, то «Общие принципы всех языков одинаковые, реальному программисту достаточно 4-5 дней на освоение нового языка».
Встреча с аспирантом было обычным знакомством: определили уровень знаний, назвали имена и email старосты.
Практика оказалась разделена на две части: реализация уже сделанной работы на Java и разработка нового проекта в команде, используя Git для синхронизации кода и Redmine для постановки задач.
Вскоре все правила были выложены в ВК, а староста любезно добавил ссылки для тех, кто не сталкивался до этого с Java.

Скриншот из ВК

Как в Магическом Электротехническом практика по Java прошла и немного про Lumia 710

Интерлюдия

Так уж получилось, что я в команде опытных студентов: один — уже работает java-программистом, а второй сдавал последний учебные год лабораторные по «Алгоритмам и Структурам данным» исключительно на Java, да и я с данным языком уже сталкивался.

День третий

И вот первый акт начался: сроки выставлены, бригады определены, а мастера фотошопа уже фотошопят…

Скриншот из ВК

Как в Магическом Электротехническом практика по Java прошла и немного про Lumia 710

А в моей команде тем временем кипят страсти. Что же нам спрограммировать эдакое? Что делать будем делать игру, решили сразу и почти единогласно. Но вот какую игру?

  • Игра с телепортами, как та, что на Нокии
  • Игра про странствующий электрон (я так и не понял, что это было)
  • Космическая стрелялка с использованием веб камеры

Обсуждение было долгим, но в итоге мы решили делать мини-олдскул-хардкор-рпг. Я составил даже небольшой список плюсов этой идеи.

  • Графику легко найти в сети
  • Логичное разделение работы
  • Каждый сможет взять себе задание по душе, когда основа готова (добавлять врагов с их AI, добавлять уровни, добавлять ловушки)

На том и порешили. Но тут начались проблемы…

Интерлюдия

Так уж случилось, что у каждого из нас был свой стиль программирования. Кто-то не терпел лишнюю трату места, (операторные скобки, когда только одно условие — для слабаков) но вы, если перед операторной скобкой нет пробела. Я ненавижу большое количество классов, а кто-то был готов создать по пакету, чтобы поместить в него «главный родительский класс», заодно используя в конструкторах ТОЛЬКО сеттеры. Стараясь здраво смотреть на вещи, я понимал, что учебный проект с вероятностью в 99% так и останется им: никакого прогресса после окончания обучения, минимум повторного использования кода. Но, когда вы уже год на работе трудитесь над одним проектом, вы будете готовы воскликнуть «только сначала надо написать фреймворк», ведь «ну как писать-то игру без фреймворка». К счастью, мир наступил: пишем, как каждый знает, затем кооперируемся и не произносим вслух слово «фреймворк».

День четвёртый

Пока мои одногруппники занимались первым заданием, я был поглощён идеей показать, что написать основу для игры не так уж и сложно. Тем более, что весь пример кода был приведён на Хабре.
На самом деле весь день я проспал, а работать начал лишь вечером.
За пару часов я подготовил демо:

  • Управляемый игрок (ходьба + стрельба)
  • Перемещающаяся камера
  • Бот (перемещение до точки)
  • Файрбол (бот с перемещением в одну точку)
  • Менеджер ресурсов (загрузка и кэшировние тайлов)
  • Рэндер (с поддержкой анимаций)
  • Случайный генератор карты

Список классов и скриншот демо

Как в Магическом Электротехническом практика по Java прошла и немного про Lumia 710

Демо мою команду немного вдохновило. Но, конечно, код им не понравился: я ведь писал набросок. Пришлось долго убеждать, что FireBall не должен быть наследником класса Unit. Закончил я к ночи, а утром уже была сдача первого задания (алгоритма на Java). Пришлось переделать демо РПГ под поиск пути волновым алгоритмом.

День пятый

Начался он для меня несколько поздно и оригинально. Программировать ночью хорошо, но только, если вы ставили будильник не на Nokia Lumia 710. Встречу с аспирантом я проспал, к счастью, работу можно было сдать удалённо, но пока…
Разбудил меня в стук дверь: моя команда решила, что раз поработать в университете не удалось, можно сделать это в общежитии, и пришли прямо ко мне в комнату.

Как в Магическом Электротехническом практика по Java прошла и немного про Lumia 710

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

  • Определиться, что заказать из еды
  • Убедить IntelliJ IDEA работать с папкой проекта, что находится на FTP
  • Найти компромисс в форматировании кода и количестве классов

С едой мы разобрались быстро: благо, в Питере подобных служб навалом.

Лайфхак

Если вы подружитесь с ДПС около вашего жилья, то есть шанс получать еду бесплатно.

IntelliJ IDEA мы, возможно, и одолели бы, но время было дорого. Поэтому пришлось быстро настроить Git и приступить к работе.
Пункт третий — извечная проблема для многих команд, мы компромисс нашли, чего и Вам желаем.
Другие команды тоже не сидели без дела и заваливали аспиранта вопросами.

Скриншот из ВК

Как в Магическом Электротехническом практика по Java прошла и немного про Lumia 710

Мы работали параллельно: парни переписывали мой код по своим понятиям, а я разъяснял некоторые моменты и подсказывал фундамент для будущего функционала.
В процессе нам даже пришлось переместиться в более комфортную комнату.

Фото с места событий

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

Как в Магическом Электротехническом практика по Java прошла и немного про Lumia 710

Итоги дня

Ближе к ночи, мы приблизились к демке, что я написал:

  • Рендер мира
  • Менеджер ресурсов
  • Управляемый игрок
  • Поддержка анимаций

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

Интерлюдия

Сейчас, глядя на этот день, я понимаю, что нам нужно было написать диздок и определиться в терминологии (спрайты, тайлы...). В последствии я слегка удивился, когда узнал, что «Тайл-класс» — это клетка карты, в которую помещаются другие объекты, а «Тайл-перечисление» — одна квадратная картинка.

День шестой

Так, как Вконтакте имел много отвлекающих факторов, а электронная почта несколько устарела, для общения внутри команды был выбран Slack: мгновенные сообщения, удобный мобильный клиент, поддержка уведомлений в Chromium Based браузерах, интеграция с Git и другими сервисами (Drobox и т.п.).
Шестой день встретил меня умениями (файрбол и лечение) и Logger’ом, который красиво уведомлял при изменении жизней. Умения умениями, только вот мне не очень понравилась анимации. Пришлось фотошопить и гуглить.

Графика умений

Как в Магическом Электротехническом практика по Java прошла и немного про Lumia 710

Заодно, подобрал новый набор графики для окружения (из RPG Macker XP) и игрока (оттуда же).
И тут я подумал: AI писать долго, а игре нужна минимальная играбельность для демонстрации наработанного. Да и нужно как-то выделить игру на фоне конкурентов. Вы давно видели игры со старым добрым разделением экрана, да ещё RPG? Я давно. Для начала я просто добавил второго игрока. После этого я обнаружил ошибку в расчёте координат для рендера (неверно рассчитывалось смещение объектов с учётом того, что Игрок 1 всегда был в центре).
Когда делаешь, что-то интересное время, течёт очень быстро: с удивлением для себя я обнаружил, что времени пять утра. Через пару часов нужно было выходить в университет на очередную встречу с аспирантом…

День шестой

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

Скриншот из ВК

Как в Магическом Электротехническом практика по Java прошла и немного про Lumia 710

Зато, мы хорошо выспались и с новыми силами приступили к работе над проектом.
Для начала был добавлен класс камеры для упрощения рендера. Он, правда, ничего не делал, но был…
Но самым важным стала возможность перемещаться по пикселям, а не клеткам карты. Таким образом, мы слегка «обновили» стиль игры. Из-за этого пришлось переписать множество методов: физики, рендера, определения столкновений… Конечно, это вызвало и ошибки. Раньше файрбол создавался на клетке от игрока, теперь стал появляться на пикселе от игрока и взрывался об него. Эдакий изящный способ самоубиться. А чего стоили столкновения с самим собой (перемещение в рамках одной клетки на пиксель и проверка её на занятость)!

Интерлюдия

Конечно, чтобы мы совсем не загрустили, товарищ староста репостил в группу забавные картинки про Java.

Скриншот из ВК

Как в Магическом Электротехническом практика по Java прошла и немного про Lumia 710

День седьмой

Вновь у нас появились разногласия. И если методы восьмой Java мы просто реализовали в отдельном пакете, чтобы каждый мог сидеть со своей версией JVM, то с видением логики возникли проблемы…

Преткновение два

Такой уж у меня характер, что с того момента, как я начал писать на Java я пароноидально боялся утечек памяти, раз за разом перечитывая статьи на Хабре, если добавить к этому неприязнь «Класс ради классов», то можно понять, как я отнёсся к кастомному классу точки. Ну точка и точка, но место применения… Point представляла собой координаты (x, y) и использовалась только в одном месте: для определения положения объекта (в том числе и тайла) при рендере. Я просто взвыл: рендер происходит ~60 раз за секунду, один рендер затрагивает ~6400 клеток, для рендера одной точки создавалось новая Point.
60 * 6400 = 384000
Пара сотен тысяч объёктов в секунду, которые будут задействованы только раз. Конечно, для GC это небольшая помеха, но почему бы просто не использовать два int, в любом случае работа с кучей будет быстрее. Проблема разрешилась введением пула объектов.

Преткновение три

Следующим камнем преткновения стало изменение системы карты.
Я представлял это так:

  • Двумерный массив с объектами фонами, стен
  • Лист статичных объектов (декалей) типа цветочков или всяких камней
  • Лист статичных объектов 2 (декалей, что НАД игроками) типа арок или потолков
  • Лист динамичных объектов типа игроки, боты

Сначала мы рендерим фон (возможно, даже кэшируя), затем статические объекты, затем статичные объекты, затем статичные объекты, что над динамическими.
Конечно, для такой системы необходимо было бы отсортировать объекты по Y (те объекты, у кого Y меньше оказываются под теми, у кого он больше).

Небольшой пример

Как в Магическом Электротехническом практика по Java прошла и немного про Lumia 710

Мой одногрупник заменил эту системы на двумерный массив стеков. Это давало гибкость в количество слоёв рендера (сколько угодно слоёв декалей), но создавало проблемы при рендере динамических объектов (как определить, что нужно отрисовать под игроком, а что над?). А если вспомнить реалии нашего проекта, то получится что в стеке хранится только один элемент. Опять же лишняя, на мой взгляд, нагрузка: пробег по 6400 стекам только для рендера одного кадра, да и проблема с динамическими объектами проблема не была решена… Сошлись на том, что стеки оставим, но в остальном будем следовать моему представлению.

Преткновение четыре

Это было, наверное, наше самое крупное разногласие во время работы над проектом. Так я представлял игровой цикл:

Вечный цикл{
int a = текущее время();
игровая логика();
сон потока на (N - текущее время() - а);
}

Для сна потока я использовал старый добрый Thread.Sleep(); работающий одногруппник был принципиально с этим не согласен: сначала он хотел вообще обойтись без пауз. Непрерывное выполнение игрового цикла приводило к 100% нагрузке ядра и ~2000FPS на мощных машинах. Мне удалось его убедить, что FPS нужно ограничить, а в выполнение игровой логики передавать время, прошедшее с последнего её выполнения. К сожалению, со Sleep были проблемы: просто неприязнь одногруппника к Sleep и проблема со считыванием нажатия кнопок. Код был переписан на использование Wait. Только вот проблему с полной загрузкой ядра это не решило, да и FPS мои одногруппники решили поддерживать на уровне 100 кадров в секунду. К моей радости, в конце концов, код был переписан на Sleep (слава многопоточности для обработки нажатий клавиш), разделены по времени вызовы физики и рендера и FPS приведён к 60.

День седьмой

Был проведён мёрдж веток, доработка камер и, что главное, рефакторинг кода, относящегося к рендеру и перемещению. Полностью были исправлены ошибки, связанные с перемещением по пикселям вместо клеток карты.
Кроме того, добавили дебаг для отладки рендера.

Скриншот работы отладки столкновений

Как в Магическом Электротехническом практика по Java прошла и немного про Lumia 710

День восьмой

Я решил добавить больше динамики в игру, а именно: ловушки и новый тайл. Чтобы сразу увидеть, решил заодно использовать генератор карт, вместо пустой. Не нужно было спать, когда обсуждали и делали основу системы классов…
Прежде всего, я создал класс тайла (клетка карты) с методом триггера и обновил физику, чтобы они срабатывали. А вот дальше начались проблемы… Пришлось написать класс для графического представления тайла-картинки, добавить в enum тайл-картинку и связать их в фабрики тайлов. В то время, как раньше я обошёлся бы классом стены и пола, в который просто передавалось изображения для рендера.
Неужели во всех компаниях используют Factory для каждого чиха?
А потом я узнал, что генератор карт считает enum с тайлами картинками, набором возможных сущностей (пол, стена...).
Спустя пару часов я имел рабочий генератор карт и пушку, которая создавала файрболы вокруг кнопки.
Стрелять просто так было скучно, поэтому я написал просто AI, который преследовал игрока. Так я узнал, что метод смерти работает. Было очень обидно, когда написанный мною же AI расстрелял меня из пушки, сам того не понимая…

День девятый

В этот день мы совершенствовали текущий код. Например, добавили систему удаления объектов с задержкой. Ведь после гибели бота, должен был появиться труп, который тоже должен исчезнуть через некоторое время. А удаление объектов из листа во время работы с листом не самое приятное занятие. Ох уж этот ConcurrentModificationException…

День десятый

Наконец-то, услышав мои просьбы, было добавлено разделение экрана и работа с несколькими камерами и вывод урона игрокам.

Скриншот работы сплитскрина

Как в Магическом Электротехническом практика по Java прошла и немного про Lumia 710

А вот отображение HP добавить, увы, не успели.

Набросок индикатора здоровья и маны

Как в Магическом Электротехническом практика по Java прошла и немного про Lumia 710

Практика закончилась, и больше мы ничего добавить не успели…

День одиннадцатый

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

Фото с места событий

Как в Магическом Электротехническом практика по Java прошла и немного про Lumia 710

(Фото сделано на Lumia 710)

Единственной заминкой стал вопрос, что писать в зачётку: «зачёт» или оценку…

Фото результата

Как в Магическом Электротехническом практика по Java прошла и немного про Lumia 710

(Фото сделано на Lumia 710)

Мы предполагаем, а Бог располагает

Так можно обозначить летнюю практику и использование Нокии. Летняя практика прошла совсем не как я предполагал: лихорадочное первое задание, вкупе с не совсем законченным вторым оставили нотку неудовлетворенности. Очень огорчило малое количество теоретического материала, ибо самообучиться я могу и вне рамок практики. Несмотря на совсем несхожие стили программирования, я рад опыту командной работы с одногруппниками и совместной работе на GitHub (хотя в терминах я путаюсь до сих пор), Slack (очень порадовала интеграция с Яндекс.Браузером) и Redmine. Раньше я использовал просто заметки в .txt и //TODO. Сейчас, понял удобство такой системы, как Redmine: всё в голове не удержать.
А Lumia? Что можно сказать о смартфоне, которому уже седина в ОС? Когда я покупал SE Vivaz на Symbian, я ждал от смартфона именно этого… Сейчас, после пресловутого Vivaz и нескольких смартфонов на Андроиде, 710-й кажется приветом из двухтысячных. Надеюсь, что Windows Phone 8.1 смотрится более достойно своего предка (историю WP я знаю по этой статье).

Ошибки, которые мы поймали при разработке игры

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

P.S.

Часть истории была изменена, а некоторые события перемещены во времени для целостности повествования.
Я с радостью исправлю любые ошибки, если вы напишите мне в ЛС. Буду рад любой критике, как и кода, так и текста. Постараюсь ответить на каждый вопрос. Увы, моих одногруппников на Хабре нет.
Возможно, история содержит много воды и лишнего, но целью являлось показать реальный учебный процесс, а не сферическую WINstory. Очень извиняюсь за несколько сумбурный стиль повествования и JPG в шапке поста, оригинал был удалён, а ещё раз фотошопить я уже не в силах.

Ссылки

Автор: BIanF

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js