18.10.2018


Где-то три месяца назад я жаловался на то, что приказал долго жить универсальный скрипт по синхронизации динамического IP-адреса сервера с доменами — ddclient. Он поддерживал API многих сервисов динамического (dynamic) DNS, в том числе и Cloudflare. Однако, поддержка последнего однажды сломалась, а разбираться в огромном скрипте на незнакомом мне Perl'е как-то не особо хотелось. В итоге на какое-то время я забил на идею домашнего хостинга и выключил сервер. Сегодня мы вновь вернёмся к этому вопросу, ведь я наконец-то его решил!

Обождав какое-то время, отдохнув и набравшись сил, я решил посмотреть по сторонам. Оказалось, в gist'ах легко можно найти небольшие bash-скрипты, дёргающие Cloudflare API прямо через curl. В комментариях даже есть ссылка на форк с готовыми конфигами к systemd! Мне они не очень понравились, так что я написал свой вариант. В моём варианте параметры передаются не в самом скрипте, а через переменные окружения, описанные в файле сервиса. Поменять нужно следующие параметры:

User=<имя пользователя, от имени которого будет запускаться скрипт>
ExecStart=<путь к директории со скриптом>/cloudflare-update-record.sh
WorkingDirectory=<путь к директории со скриптом или любой другой путь для временных файлов>
Environment="CLOUDFLARE_AUTH_EMAIL=<логин в Cloudflare>"
Environment="CLOUDFLARE_API_KEY=<ключ к Cloudflare API>"
Environment="CLOUDFLARE_ZONE_NAME=<название зоны (домен второго уровня)>"
Environment="CLOUDFLARE_RECORD_NAME=<домен с А-записью>"

В принципе на этом можно было бы и закончить мой рассказ, но так уж получилось, что я ленивый дурак и долгое время игнорировал возможность создания CNAME-записей в настройках доменов. Вернее просто не соизволил ознакомиться с другими видами записей, кроме А, и даже не догадывался о существовании возможности указать реальный IP-адрес только для одного домена, а дальше ссылаться на него со всех остальных! А поскольку скрипт выше позволяет обновлять только один домен, я принялся модифицировать его, но в итоге так ни к чему и не пришёл, а только в очередной раз вымотал себе нервы: упаси боже ещё хоть раз писать эти дурацкие bash-скрипты с их неинтуитивным синтаксисом, дурацкими пайпами и утилитами с кучей «магических» аргументов!

В итоге я наткнулся на пакет cloudflare-ddns, написанный на Пайтоне и позволяющий с большей гибкостью управлять DNS-записями. Он оказался не без багов, которые впоследствии мне пришлось фиксить (и перетащить к себе половину кода модуля :/ ). Однако в результате был рождён пакет cfdyndns-updater.

На самом деле он должен был называться cfddns-updater, на что намекает название как самого модуля внутри, так и исполняемого файла — cfddns_updater. Чем же вызвано такое несоответствие? Моей глупостью и неосторожностью. Поторопившись и залив релизную версию немного раньше времени, я удалил только что созданный проект cfddns-updater. И как оказалось впоследствии, это было серьёзной ошибкой! PyPI — это не GitHub — удалить проект, как репозиторий, и пересоздать его под тем же именем невозможно. PyPI просто не позволяет этого! Таким образом, одно неосторожное движение начинающего «пакетёра» делает целое название потерянным для общества навеки… Простите меня, пожалуйста :(

Что же он делает? По сути это просто более удобная обёртка над модулем cloudflare_ddns, исправляющая некоторые ошибки и позволяющая задавать конфигурацию в виде YAML-файла (спасибо библиотеке PyYAML). При этом скрипт запускает вечный цикл, который периодически просыпается ото сна и проверяет, изменился ли IP-адрес и не пора ли обновить DNS-запись.

Для работы скрипта нужен Python версии 3.6 или новее. Установить пакет можно, как обычно, с помощью pip'а:

pip install cfdyndns-updater

Дальше следует создать конфигурационный файл. Разместить его можно в домашней директории под названием .cloudflare-ddns-config. Для POSIX-совместимых систем также поддерживается путь к глобальному файлу конфигурации, который используется вместо пользовательского при его отсутствии: /etc/cloudflare-ddns-config. Впрочем, путь к файлу с конфигуацией всегда можно передать явно в качестве единственного опционального аргумента, принимаемого скриптом:

cfddns_updater config.yml

Сам файл конфигурации выглядит как-то так:

email: <логин в Cloudflare>
api_key: <ключ к Cloudflare API>
periodicity: <таймаут между проверками в секундах>
domains:
  - example.org    # 'proxied: true' подразумевается
  - www.example.org
  - domain: ssh.example.org
    proxied: false

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

28.09.2018


Слишком громкий заголовок, учитывая, что я мало чего понимаю в настоящей информационной безопасности. Только базовые вещи вроде SQL-инъекций, XSS- и CSRF-уязвимостей, необходимости шифрования трафика по TLS и SSH и основные принципы их работы, немного про симметричное и ассиметричное шифрование. Ну про признаки хорошего пароля я даже и не говорю, хотя универский курс ИБ посвящает этому вопросу целых две лабораторные работы. В общем, знаю я немного (только самые основы), но раз уж курс пройден и программы на лабораторные работы написаны, то грех их не выложить в своём бложике.


DES

Начну с конца, а именно с пятой лабораторной работы, потому что про первые говорить вообще нечего, так что их просто вкратце упомянем позже.

Сначала я подумал, что нам надо написать свою реализацию алгоритма шифрования DES. И я даже начал работать в этом направлении. Но потом столкнулся с рядом проблем:

  • предоставленная нам программа-пример не демонстрировала все шаги;
  • описания в умных статьях из интернета слишком сложные, непонятные и даже в какой-то степени разные (где-то делают упор на одном, в другой статье больше времени посвящают другому и т. д.);
  • я не очень умный, чтобы во всём этом разобраться :(

После этого я узнал, что на самом деле поставленная перед нами задача куда проще: всего лишь решить тест в программе-примере.

Программа-пример Исходные данные Первый шаг

В результате у меня получилось программа на Котлине, которая автоматизирует примерно половину этого теста.

Результат выполнения DES.jar

Поскольку развивать дальше я её не планирую, то обойдёмся без каких-либо репозиториев по старинке: выкладываю код простым zip-архивом. Проект без проблем импортируется в IntelliJ IDEA и интегрируется с Maven'ом. Но на всякий случай прикладываю и уже собраный JARник со всеми зависимостями.


Гаммирование с использованием конгруэнтного генератора

Четвёртая лаба сразу пугает страшными словами. И это, кстати, забавно. Если зайти на страницу в «Википедии» про гаммирование, а потом перейти на английскую версию статьи, то увидим уже вполне простое и понятное программисту название: XOR cipher. И задача сразу становится проще! Дополнительно прочитав какую-либо из статей, убеждаемся, что нам всего лишь надо проXORить символы шифруемого текста с символами гаммы, которая и будет ключом.

Со второй частью труднее. Нужно всё-таки прочитать статью про этот конгруэнтный генератор и реализовывать его, используя данную там формулу.

Ну что ж. Я опять взял свой любимый Kotlin и «запилил» небольшой сниппет. Попробовать его можно здесь.


Генерируем пароли

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

У первой задание состоит из двух простых частей:

  1. определить минимальную длину пароля, устойчивую к заданным условиям взлома;
  2. сгенерировать пароль, удовлетворяющий условиям.

Условия:

  • Вероятность подбора пароля: 10-4;
  • Скорость подбора паролей: 100 паролей в день (да, задача не очень реалистична);
  • Максимальный срок действия пароля: 12 дней.

Поиграться с моей реализацией данной задачи можно здесь (да, опять Kotlin).

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

  • Q — остаток от деления длины имени пользователя на 6;
  • первые два символа — случайные большие буквы английского алфавита;
  • символы от третьего до 10-Q-1 — случайные малые буквы английского алфавита;
  • оставшиеся символы — случайные цифры.

Эту лабораторку я выполнял, когда Котлин уже немного отпустил, так что эта программа, как и все последующие, написана на Python 3. А из этого следует, что ссылок на удобные онлайн REPLы больше не будет.

В этом сниппете я собрал все лабы из этого раздела (благо они все состоят из одного файла). Следовательно, там код как этого задания, так и предыдущего.

Результат выполнения генератора паролей
Эта картинка из отчёта, поэтому фон командного интерпретатора изменён на светлый, чтобы сэкономить тонер при печати.

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

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


Шифры перестановки

Наконец мы переходим к самому сладкому. К вишенке на торте этого поста. Скрипт, на который я потратил больше всего времени, но сделал его по всем правилам и канонам. Там даже тесты есть, Карл! К сожалению, для моих проектов это пока редкость…

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

Если передать флаг --vertical, то будет использоваться алгоритм вертикальной перестановки. Он принимает в качестве ключа последовательность из n цифр от 1 до n. При этом число n должно быть меньше 10! Оно выступает в роли количества столбцов, которые заполняются слева направо. Затем все столбцы переставляются в соответствии с ключом. Результат получается путём повторного прохода таблицы слева направо и сверху вниз.

Результат работы простой маршрутной перестановки Результат работы вертикальной перестановки

Репозиторий

Скрипт написан на Python 3, так что для работы нужно наличие интерпретатора и менеджер пакетов pip, так как впервые я решил разобраться и запаковать код в полноценный пакет, чтобы позволить setuptools автоматически создать небольшой исполняемый файл в директории, обычно присутствующей в переменной PATH, что позволит с лёгкостью его запускать из командной строки! Для установки достаточно скачать wheel-пакет по ссылке выше и выполнить следующую команду:

pip install --user <путь к скачанному файлу>

Программа вызывается следующим образом:

routecipher [--vertical] [-v] {encrypt,decrypt} ключ текст
АргументОписание
--vertical использовать алгоритм вертикальной перестановки
-v, -vv включает вывод информационных или даже отладочных сообщений
ключ зависит от выбранного метода шифрования; см. описание и примеры на скриншотах выше
08.04.2018


Введение

Сегодня будет много Джавы. Очень. Даже если это будет не совсем Java. Так что все хейтеры главного корпоративного языка и его платформы могут сразу закрывать вкладку браузера и идти собирать обновления для Gentoo.

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


Постановка задачи

Работа выполнялась по учёбе для двух разных предметов в разных, хоть и соседних, семестрах. Поэтому и задач будет две.

На первом предмете нам ставили целью научиться работать с разными базами и источниками данных. Одним из ключевых требований была загрузка данных либо из Microsoft Word, либо из Excel. В итоге должно получиться десктопное приложение.

На втором предмете упор делался на веб-технологии и создание серверных приложений на языке Java под контейнер сервлетов Tomcat. Поэтому нужно веб-приложение, работающее с базой данных.

Задача проста: составить справочних географических объектов и их адресов. Не знаю, как это нормально объяснить, поэтому предлагаю просто посмотреть пример заполненных данных в виде Excel'евской таблички (а именно с ней мы и будем работать).


Десктопное приложение

Главное окно приложения

Репозиторий

Пойдём в хронологическом порядке и начнём с обычной программы для персонального компьютера.

Как обычно, нам предлагали писать её на опенсорсном наследнике Delphi — Lazarus'е. Как вы понимаете, это ни разу не мой вариант. Ко времени выполнения работы я уже какое-то время писал небольшие части DeskChan на Джаве и успел немного освоиться в платформе JVM. Более того, местами уже даже начал вводиться Kotlin, изучению которого я посвятил пару тёплых летних недель. Поэтому не надо долго гадать, чтобы понять мой выбор.

К моменту начала разработки приложения уже был написан прототип DeskChan Launcher'а на Котлине. Причём мне очень понравилось: язык весьма приятен после многословной Джавы. Одним из сдерживающих факторов при разработке «Лончера» был размер выходного исполняемого файла: не хотелось, чтобы программу в 20-30 Мб ставил установщик в половину её размера. К сожалению, рантайм Котлина сам по себе привносит уже 800 Кб к размеру JARника, но дальше расти было крайне нежелательно. Поэтому отрисовка интерфейса там сделана с помощью JavaFX напрямую.

Однако для лабораторного проекта таких требований нет совершенно! Поэтому мы вольны использовать любые технологии и библиотеки! И первым же делом я добавил новую прослойку в построение формочек графического интерфейса — библиотеку TornadoFX. Приятный фреймворк для построения графических приложений на Котлине, который сразу призывает продумывать архитектуру и отделять слои приложения друг от друга. Главной же фишкой является описание экранных форм с помощью строго статически типизированных билдеров. Вряд ли такой способ полюбят дизайнеры, но если программисту приходится верстать UI, то такой подход — просто спасение.

Для работы с XLSX-файлами от Excel'я используем библиотеку Apache POI. Сразу скажу, что я дважды работал с этим форматом таблиц (в прошлый раз это было на С#'пе), и оба раза это была в той или иной степени, но боль. Старайтесь избегать таких извращённых форматов!

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

Весь код по работе с источниками данных находится в пакете ru.narfu.kozalo.addresses.data.sources. Там располагаются и все интерфейсы, и обе реализации.

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

Я пытался

На перепутье

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

И тут меня вновь понесло в сырые экспериментальные решения, коим стал проект kotlinx.html, позволяющий писать HTML-странички с помощью всё тех же типобезопасных билдеров. Сыроватость чувствуется в неуклюжем API, но задумка хорошая.

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


Servlets serve you, sir

Репозиторий

Вот наконец мы и дошли до более энтерпрайзных и серьёзных технологий. Отныне никаких Котлинов и DSLей! Только читая Java! Причём седьмой версии! Сайтики мутить — это вам не формочки рисовать.

Тут необходимо краткое отступление о моём рабочем окружении. Вся сервлетная часть писалась исключительно на ноутбуке под управлением Debian 8. Из стандартных репозиториев был установлен Tomcat 8, который отказался работать с байт-кодом, скомпилированным под Java 8, поэтому пришлось откатиться до Java 7. Неприятно, но не очень страшно.

При работе с сервлетами сразу сталкиваемся с рядом проблем.

Во-первых, из коробки у меня ничего не заработало и Томкат не запускался. Пришлось читать логи и вручную править юниксовые права доступа.

Во-вторых, деплоить веб-приложение — это боль. Рекомендую сразу не полениться и прочитать главу про сервлеты у Шилдта. Сколько я не читал статей в интернете, а только у него нашлось нормальное введение, как на низком уровне работают сервлеты, без всяких надстроек вроде JSP. Тем не менее, вручную копировать классы, следуя его инструкциям, — удовольствие не из приятных. Поэтому я быстро научился загружать приложение WARкой через веб-интерфейс Томката, чем и довольствовался какое-то время, осваивая работу с сервлетами и контейнером.

На самом деле в IDE есть средства для автоматизации загрузки приложений в контейнер сервлетов, но у меня так и не получилось настроить это по-человечески. IntelliJ IDEA самостоятельно запускает и отключает Томкат, из-за чего требует прав администратора. Я не понимаю, зачем это нужно, если можно просто редеплоить новые WARки.

И тут на помощь приходит Maven со своими бесконечными плагинами! Только поставив tomcat7-maven-plugin (он подходит и для 8-го Томката) я смог вздохнуть спокойно и начать жить счастливо. Не поленитесь его установить! Он позволяет (ре)деплоить приложение в один клик, используя всё тот же RESTful API, что и сайт самого Томката.

Ладно, перейдём ближе к коду. Разобравшись, как это всё работает, и вооружившись дополнительной библиотекой тегов, я смог быстренько на JSP накидать странички с отображением адресов, повторив функционал серверной части десктопного приложения. Но поскольку в задании было требование и на возможность редактирования записей, пришлось доделать дополнительные формочки и сделать простенький REST API на чистых, вручную написанных сервлетах (JSP компилируются в классы при первом обращении).

Кстати! В этот рад для общения с СУБД я решил опробовать знаменитый Hibernate, оформив все сущности по спецификации JPA (в Java EE'шных стандартах очень много аббревиатур). В принципе получилось неплохо, но где-то я всё-таки налажал, потому что временами происходит блокировка БД, которая чинится перезапуском контейнера сервлетов. У меня уже не было сил искать причину такой серьёзной поломки, так что если кто-нибудь ткнёт меня носом на косяк в комментариях, то я буду очень благодарен.


Заключение

В целом, если подводить какой-то итог, экселевские таблицы — это зло, энтерпрайзные технологии — чуть меньшее, но тоже зло. Kotlin няшен, но не для всех технологий применим (ах да, я же забыл упомянуть, почему не стал писать на нём сайт! Мне было лень думать, как лучше всего подружить изменяемые джавовские объекты, используемые Hibernate'ом, со всем таким неизменяемым Котлином) и ещё сыроват в плане инфраструктуры. Надеюсь, дальше будет лучше: с платформой JVM я бы ещё поработал. В конце концов под неё есть ещё много более функциональных языков, с которыми мне хотелось бы ознакомиться…


Дополнено 3 июня 2018 года

На самом деле я жутко протупил с этим дополнением, потому что его надо было добавить ещё 9 апреля. Но сначала было лень, потом некогда и так всё закрутилось… В общем мне сказали, что всё фигня и ничего из описанного в части про серверную Джаву не нужно использовать, если ты не наркоман.

Мудрости от @DogeShibu
08.03.2018


Вернёмся почти на два года назад и попадём на летнюю учебную практику, которую мы тогда проходили на кафедре в стенах родного института. Задания не отличались особой интересностью или сложностью, но я решил всё-таки опубликовать их решения в блоге для протокола, потому что на данный момент это единственный случай, когда мне довелось попрограммировать на Ruby!

Задание нужно было выполнять на двух языках программирования. Первым я взял очевидный Python, а вот вторым решил попробовать похожий на него Ruby, предварительно просмотрев видосик с курсом молодого бойца.

Сами задания предельно просты:

  1. Для введённого с клавиатуры числа n, найти вторую с начала цифру.
  2. Составить тест с вариантами ответов по решению нелинейных уравнений для предмета «Численные методы».
  3. Составить тест со свободными ответами по криптографии из дискретной математики.
  4. Разработать программу для решения нелинейного уравнения методом Ньютона.
  5. Разработать программу, осуществляющую численное интегрирование функции заданного типа.
  6. Обернуть все задания в общий цикл и сделать единую программу.

4 и 5 задания могут показаться сложными, но на самом деле там всё очень просто.

Для четвёртого задания, разумеется, дано уравнение: 13,4sin(3x) – sqrt(6x-12) = 0

Параметры для пятого приведены в таблице ниже:

Подинтегральная функция f(x)abh
cos(x) / (x+2) 1 2 0,2

Реализация

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

Результат работы программы
30.11.2017


Традиционный скриншот программы

Проект с исходным кодом

Возвращаемся в далёкий 2015 год. Прямо в самое начало: в февраль месяц. Первый курс, второй семестр, одна из первых практических пар по ООП. Нам задали написать эмулятор игрового автомата, что и привело к созданию данной программулины на C#. Разумеется, я публикую её не совсем в первозданном виде, получившемся в результате полуторачасового кодинга, а, как обычно, всё-таки внёс несколько изменений: отполировал код ReSharper'ом, сделал небольшой рефакторинг, поправил косметические дефекты интерфейса, запилил иконку и т. п. Кода немного, так что даже не стал запариваться с репозиторием и просто заливаю всё в виде архива, как в старые добрые времена.

Для запуска нужен .NET Framework 4.5 (или аналогичная версия Mono Runtime). Получаем случайное количество денег, однорукого бандита и рычаг (кнопку), за который можно дёргать, пока деньги не кончатся, и творить магию азарта!

09.10.2017


Главное окно программы Результаты

Репозиторий

Небольшая программа на C#, написанная на третьем курсе и предназначенная для эмуляции процесса бросания различных наборов игральных костей с целью сбора статистической информации. Написана с использованием MVVM в качестве основного паттерна и WPF с XAML для описания интерфейса. Для работы требует .NET Framework 4.5.2.

10.09.2017


Кручу-верчу, конус на OpenGL получить хочу!

Скриншот программы

Репозиторий

Вертим конус на C#'е с использованием OpenGL. Программа со второго семестра первого курса (хотя большей частью написана ещё в первом), представляющая собой демонстрацию вращения конуса в трёхмерном пространстве. Конус можно изменять по форме, цвету и размеру.

Программа позволяет выбрать из трёх способов визуализации конуса:

  1. с помощью стандартной функции GLUT;
  2. с помощью полигонов-треугольников;
  3. с помощью множества отдельных окружностей.

Для работы требуется .NET Framework 4!


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

27.08.2017


Репозиторий


Относительно свежая программа на С# из начала 3-го курса или конца 2017 года. По сути представляет собой прототип очередного клиента для чтения (собственно, только для чтения) сообщений из ВК. Целью создания было знакомство с архитектурным паттерном Model-View-ViewModel (MVVM) и построением интерфейса на Windows Presentation Framework (WPF) с использованием разметки на XAML и локализацией на двух языках (русском и английском). Поэтому большей степенью может быть полезна только с целью изучения кода.

Для чтения сообщений нужно получить токен (для этого есть кнопочка Получить, которая перекинет на страницу авторизации и выдаст токен в адресной строке как access_token). В готовой сборке для авторизации используется ключ моего специально созданного приложения. В исходном же коде вместо него стоит 0 (константа VkAppId в VkMsgsReader/VkMsgsReader/MainWindowViewModel.cs), что отключает работоспособность данной кнопки.

16.08.2017


Скриншот

Репозиторий

Простая консольная программа на Python, написанная в начале третьего курса и вычисляющая корни квадратного уравнения по введённому выражению. Запускается с помощью файла main.py. А запустив test.py, можно прогнать автоматические тесты. Совместима с Python 3 и Python 2.7.

23.07.2017


Скриншот программы

Репозиторий

Button, make me a sandwich!

За окном ливень, в душе грусть, а в крови алкоголь, но время для публикации новой программы пришло и ничто не сможет этому воспрепятствовать! А сегодня мы зайдём ещё дальше по хронологии и вернёмся в конец первого семестра (конец 2014 — начало 2015). Это была моя первая программа не только для универа, но и вообще на языке C#, которая писалась вообще без знания особенностей языка с помощью Google, MSDN и StackOverflow. Учитывая вышеизложенное, не стоит там ждать хоть какой-либо архитектуры (не говоря уже о грамотной) или аккуратности кода. Там даже комментарии написаны по-русски, чтобы можно было показывать код преподавателю!

Собственно, всё, что делает эта программа, это заставляет указанное количество кнопок бегать по форме и сталкиваться друг с другом. Жестоко, бессмысленно и беспощадно... Совсем как жизнь, не так ли? Кроме того, каждый кадр записывается в специальный журнал, который можно сохранить в файл и впоследствии загрузить и покадрово воспроизвести. That's it, program lovers!

Для запуска требуется .NET Framework 4. Кроме того, существуют некоторые проблемы, описанные в репозитории, которые, вероятно, уже так и не будут пофикшены.

<<   / 3   >>