Игры для программистов, часть вторая. Mhrd игра
Игры для программистов, часть вторая / Хабр
Несмотря на опасность, что вновь возникнет спор относительно названия, я решил оставить его прежним, дабы сохранить преемственность. Можно воспринимать его как «игры для тех, кто имеет способность к программированию», или ещё как-нибудь в этом духе. Однако я полагаю, что вы читаете этот пост не ради рассуждений о семантике, потому перейдём к сути.
Infinifactory
Ещё одна игра от автора великолепного SpaceChem, с обзора которого начинался мой предыдущий пост. Теперь (фанфары и визги школьниц) — в 3D! В принципе, наличие третьего измерения — наиболее существенное отличие от предыдущей игры. Суть геймплея не подверглась особым изменениям: нужно брать детальки со входа, делать из них другие детальки и отправлять их на выход. Однако нельзя сказать, что Infinifactory сугубо вторична. Наличие гравитации, способы манипуляции с деталями — всё это создаёт совершенно новые задачи, не вызывая ощущения дежавю.Кстати, интересный факт: сходство с Майнкрафтом не случайно. Создатель Infinifactory — также автор старой малоизвестной игры Infiniminer, одним из идейных клонов которой когда-то стал Minecraft.
TIS-100
Игра того же разработчика, о которой на Хабре уже есть отдельная статья, но я всё равно напишу о ней своими словами, потому что я люблю писать, а игра того заслуживает. Если коротко, это симулятор примитивного многопоточного ассемблера, на котором вам по сюжету приходится писать разнообразные хеллоуорлды. Возможности его бедны, а в каждую ноду помещается очень ограниченное число строк, из-за чего у программиста возникают трудности, которые (я надеюсь) вряд ли встретятся в реальной работе. А чтобы жизнь не показалась мёдом, общаются ноды между собой асинхронно. Из этих трудностей рождается суть игры: ни одна нода не может целиком реализовывать сколь-нибудь сложный алгоритм, потому приходится разделять его между нодами, иногда жестоким и противоестественным способом.
Да, и там тоже присутствует сюжет. Сюжет игры про многопоточный ассемблер. Это звучит даже забавнее, чем старая шутка про Tetris: Story Mode. Суть его в том, что главному герою остаётся в наследство винтажный на вид девайс с очень странной архитектурой. Программируя его, герой восстанавливает данные из битых нод и постепенно узнаёт его историю — откуда он взялся, для чего он создан. И надо сказать, это не то знание, знакомство с которым проходит бесследно…
Human Resource Machine
А вот ещё один ассемблер. Обманчиво простой, с мультяшной графикой и «программированием мышкой». Наверное, не один ребёнок, получив в подарок «детскую развивающую» игру, в ярости сломал об колено клавиатуру, пытаясь оптимизировать решение какой-нибудь дебильно простой задачи, чтобы оно выполнялось на два такта быстрее.
На самом деле, если ребёнок не перфекционист, нацеленный на стопроцентное прохождание, то это действительно будет для него хорошей обучающей игрой. А, и ещё, если кому вдруг интересно, создатели Human Resource Machine — это те, кто сделал World of Goo.Кстати, раз уж мы заговорили про обучающие игры…
MHRD
После таких низкоуровневых вещей, как ассемблер, почему бы не… опуститься на уровень ниже? MHRD — это игра про логические элементы, в которой игрок своими, можно сказать, руками, собирает из голых NAND полноценный процессор. Причём игра подводит его к этому плавно, без резких скачков сложности. На мой взгляд, прохождение этой игры нужно давать в качестве лабораторной работы к курсам типа «Архитектура компьютера».
«Сюжет» игры незамысловат, как в старом добром немецком кино: игрока нанимает на работу корпорация MicroHard, которая хочет завоевать рынок процессоров, или что-то в этом роде. Соответственно, пока она будет завоёвывать рынок, игрок должен собрать сам процессор. Делается это с помощью своеобразного «языке программирования», на котором описываются элементы и их связи. Однажды собранный элемент можно переиспользовать в дальнейших схемах, не описывая его каждый раз заново.
К сожалению, игре не хватило совсем немного, чтобы стать истинным бриллиантом. «IDE» подтормаживает, нет возможности интерактивно поиграть с собранными элементами. Игра не великолепна, но весьма хороша.
Hack'n'Slash
Порой игроки хакают игры, чтобы упростить себе прохождение. Но что насчёт игры, в которой хаки необходимы для прохождения? Hack'n'Slash начинается с того, что главный герой (сходство которого с Линком из Legend of Zelda, разумеется, совершенно случайно) находит странный меч, оканчивающийся USB-штекером. Тыкая этим мечом в разнообразные игровые объекты, он может изменять их свойства — например, самое банальное, установить свойству «здоровье» монстра значение «0».
В дальнейшем герой находит новые артефакты, делающие другие полезные вещи — например, показывающие collision boxes всех объектов на экране. А для победы над финальным боссом необходимо модифицировать скрипты самой игры, написанные, кстати, на языке Lua. Честно говоря, графика меня немного вымораживает, но с геймплейной точки зрения игра весьма любопытна.И раз уж мы заговорили о графике…
Untrusted —or— the Continuing Adventures of Dr. Eval
Раз уж мы заговорили о графике, то у Hack'n'Slash есть брат близнец, который приятнее мне по трём причинам. Во-первых, он бесплатный и опенсорсный. Во-вторых, он в ASCII-графике, что согревает моё сердце как человека, прошедшего оригинальный Dungeon Crawl. И в-третьих, у него под капотом JS, а не Lua — тут, конечно, дело привычки, но первый для меня уже роднее русского, а со вторым я имел дело лишь при написании мелких модов к Don't Starve.
Игровой процесс похож на то, что происходит в самом конце Hack'n'Slash: игрок получает доступ к самому коду игры и может модифицировать отдельные его участки, что необходимо для прохождения. Игра остроумна и прекрасна; единственный недостаток её в том, что она слишком короткая.
Что ж, пожалуй, на этом всё. До встречи в третьей части, ещё через четыре с половиной года.
P.S. Предвижу, что в комментариях упомянут Shenzen IO. Мне известно о её существовании, но, к сожалению, я не успел ещё в неё поиграть, а потому не стал писать о том, чего толком не знаю.
habr.com
MHRD | GAME.DATA
Код для блога: Код для форума:[url=https://gamedata.club/games/simulator/1399-mhrd.html][img]https://gamedata.club//rating-242130b8508abc71c41130252cfd7437.png[/img][/url] Счётчик картинкой - Открыть счётчик |
Дата выхода: | 5 января |
Год: | 2017 |
Жанр: | Simulator |
Поджанр: | Головоломки, Инди |
Платформа: | PC, Mac, Linux |
Мультиплеер: | нет |
Разработчик: | Funghisoft |
Издатель: | Funghisoft |
Сайт игры: | отсутствует |
Описание / сюжет
В этой игре вам предстоит стать разработчиком микроэлектроники, которая получила свое развитие в 1980-х годах.
Вы окажетесь членом амбициозного стартапа и станете первопроходцем в микропроцессорной промышленности, чтобы завоевать доминирующие позиции на рынке и стать успешнейшей компанией.
В MHRD вы будете заниматься созданием центрального процессора собственного производства на основе бинарной логики и специального программного языка.
Приготовьтесь запрограммировать около двух десятков логических операций, умножений, суммирований и так далее, работая на основе функции NAND.
Вы будете обучаться прямо в процессе, чтобы затем перейти к более сложным методикам. В конечном счете вы сможете создать самый настоящий центральный процессор… или же просто взорвете свой мозг.
Скриншоты игры MHRD
Трейлер
Геймплейное видео
OS: | Windows Vista / 7 / 8 / 10 |
Процессор: | Intel Pentium 1.2 GHz |
Опер. память: | 1 Gb RAM |
Видеокарта: | Integrated Graphics |
HDD/SSD: | 150 Mb |
Обнаружили ошибку?
Сообщите нам, выделив фрагмент мышкой и нажав CTRL+ENTER. СПАСИБО!
gamedata.club
22 игры, которые помогут вам научиться программировать
Параметры для контроля конкурентности
Мы рассмотрели все элементы, предоставляемые операционной системой, которые могут использоваться для создания конкурентных программ. Но, как упоминалось в предыдущей статье, они могут создавать множество проблем. Самая очевидная проблема (и в то же время наиболее сложная в определении) – несколько конкурентных задач, обращающихся к одному и тому же ресурсу. Если нет механизма для обработки этих доступов, это может привести к тому, что одна задача запишет одно значение, а другая другое значение. Когда первая задача попытается считать данные, она будет ожидать, что они будут теми, которые записаны в первый раз – однако значение будет уже изменено. Таким образом, по умолчанию используется блокировка доступа к ресурсу и предотвращение доступа к ним других тредов, если оно заблокировано.
Приоритетная инверсия (Priority Inversion)
Чтобы понять различные механизмы блокировки, нам также необходимо понять приоритеты тредов. Как вы можете догадаться, треды могут выполняться с высоким или низким приоритетами – высокие раньше, а низкие позже. Типичным примером является процесс с низким приоритетом, получающий ресурс, который требуется высокоприоритетному процессу, а затем он вытесняется процессом среднего приоритета, поэтому процесс высокоприоритетного процесса блокируется на ресурсе, в то время как средний приоритет фактически выполняется, даже обладая более низким приоритеом. Это называется Приоритетной инверсией (Priority Inversion) и может привести к тому, что тред с более высоким приоритетом будет “голодать до смерти”, так как он никогда не будет выполнен. Поэтому, определенно, надо избегать этого.
Представьте себе наличие двух высокоприоритетных тредов (1 и 2) и тред с низким приоритетом (3). Если 3 блокирует ресурс, к которому 1 хочет получить доступ, 1 придется ждать. Поскольку 2 имеет более высокий приоритет, вся его работа будет выполнена в первую очередь. В случаях, когда процесс не заканчивается, тред 3 не будет выполнен, и, таким образом, поток 1 будет заблокирован до бесконечности.
Наследование приоритета (Priority Inheritance)
Решение для Priority Inversion – это наследование приоритета (Priority Inheritance). В этом случае тред 1 отдаст приоритет треду 3, если он заблокирован. Таким образом, тред 3 и 2 имеет высокий приоритет и оба выполняются (в зависимости от ОС). Как только 3 разблокирует ресурс, высокий приоритет возвращается к треду 1, и он будет продолжать свою первоначальную работу.
Atomic
Atomic содержит ту же идею, что и транзакция в контексте базы данных. Вы наверняка захотите написать значение сразу, как одну операцию. Приложения, скомпилированные для 32 бит, могут иметь довольно странное поведение при использовании int64_t и не иметь его в atomic. Почему? Давайте подробно рассмотрим, что происходит:
int64_t x = 0 Thread1: x = 0xFFFF Thread2: x = 0xEEDDНаличие неатомной операции может привести к тому, что первый поток начнет записывать в x. Но поскольку мы работаем в 32-разрядной операционной системой, мы должны разделить значение, которое мы записываем в x, на две очереди 0xFF.
Когда в то же время Thread2 попытается записать значение в x, может произойти планирование операций в следующем порядке:
Thread1: part1 Thread2: part1 Thread2: part2 Thread1: part2В итоге мы получим:
x == 0xEEFFкоторый не равен ни 0xFFFF, ни 0xEEDD.
Используя atomic, мы создаем единую транзакцию, которая приведет к следующему поведению:
Thread1: part1 Thread1: part2 Thread2: part1 Thread2: part2В результате x содержит значение, установленное Thread2. Сам Swift не работает с atomic. В Swift Evolution предлагается добавить его, но на данный момент вам придется реализовать его самостоятельно.
Lock
Lock – это простой способ предотвратить доступ нескольких тредов к ресурсу. Сначала тред проверяет, может ли он войти в защищенную часть или нет. Если он может войти, он блокирует защищенную часть и продолжает работу. Как только он выйдет, то разблокирует его. Если при входе тред встречает заблокированную часть, то он будет ждать. Обычно это делается при помощи сна и регулярного пробуждения, что позволяет проверить, заблокирован все еще ресурс, или нет.
В iOS это можно сделать с помощью NSLock. Но имейте в виду, что при разблокировке тред должен быть тем же самым, что и блокировал.
Существуют также другие типы блокировок, такие как рекурсивные блокировки (recursive locks). С их помощью тред может блокировать ресурс несколько раз и должен отпускать его так часто, как он заблокирован. В течение всего этого времени исключается работа других тредов.
Другим типом является блокировка чтения-записи (read-write lock). Это полезно для больших приложений, когда многие треды читают ресурсы и иногда пишут. Пока тред не пишет в ресурс, все треды могут получить к нему доступ. Как только тред хочет писать, он блокирует ресурс для всех тредов. Они не могут читать, пока блокировка не будет отпущена.
На уровне процессов существует также распределенная блокировка (distributed lock). Разница заключается в том, что в случае блокирования процесса он просто сообщает об этом процессу, и процесс может решить, как справиться с этой ситуацией.
Циклическая блокировка (Spinlock)
Блокировка состоит из нескольких операций, которые «усыпляют» треды, пока они снова не включатся. Это приводит к изменениям контекста для CPU (переключением регистров и так далее для сохранения состояния тредов). Эти изменения требуют большого времени вычислений. Если у вас действительно небольшие операции, которые вы хотите защитить, вы можете использовать спинлоки. Основная идея в том, потоки опрашивают блокировку в процессе ожидания. Для этого требуется больше ресурсов, чем просто в спящих тредах. В то же время они наблюдают за изменением контекста и, таким образом, быстрее работают при небольших операциях.
Это звучит неплохо в теории, но в iOS всегда все по другому. iOS имеет концепцию Quality of Service (QoS). С QoS может случиться так, что треды с низким приоритетом не будут выполняться вообще. Наличие спинлока на таком треде и более приоритетный тред, пытающийся получить доступ к его ресурсу, приведут к тому, что более приоритетный тред будет «голодать» по нижнего треда, не разблокируя требуемый ресурс и блокируя самого себя. Как результат, спинлоки являются незаконными на iOS.
Mutex
Mutex подобен замку. Разница в том, что это могут быть разные процессы, а не только треды. К сожалению, вам придется реализовать свой собственный Mutex, поскольку Swift его не поддерживает. Это можно сделать с помощью pthread_mutex от C.
Semaphore
Семафор – это структура данных, обеспечивающая взаимную эксклюзивность в синхронизации тредов. Он состоит из счетчика, очереди FIFO и методов wait() и signal().
Каждый раз, когда тред хочет войти в защищенную часть, он вызывает wait () на семафоре. Семафор уменьшит счетчик, и пока он не равен 0, треду будет разрешено работать. В противном случае он сохранит тред в очереди. Всякий раз, когда тред выходит из защищенной части, он будет вызывать signal(), чтобы информировать семафор. Семафор сначала проверяет, есть ли очередь ожидания. Если есть, то из нее вызывается тред, который сможет продолжить работу. Если нет, он снова увеличит счетчик.
В iOS мы можем использовать DispatchSemaphores для реализации такого поведения. Предпочтительно использовать именно их, чем семафоры по умолчанию, так как только они работают на уровне ядра, если это действительно необходимо. В противном случае он просто работает намного быстрее.
Можно рассматривать двоичный семафор (семафор со значением счетчика 0 или 1) как Mutex. Но в то время как Mutex связан с механизмом блокировки, семафор является сигнальным механизмом. Это особо не помогает, так где же разница?
Механизм блокировки – это защита и управление доступом к ресурсу. Таким образом, он предотвращает одновременное обращение нескольких тредов к одному ресурсу. Сигнальная система больше напоминает вызов “Эй, я закончил! Продолжай!”. Например, если вы слушаете музыку на своем мобильном телефоне и вам звонят, общий ресурс (наушники) будет задействован на телефоне. Когда звонок закончится, система проинформирует ваш mp3-плеер при помощи сигнала для продолжения. Это тот случай, когда следует предпочесть семафор мьютексу.
Так в чем же фишка? Представьте, что у вас есть тред с низким приоритетом (1), который находится в защищенной области, и у вас есть тред с высоким приоритетом (2), который просто вызывает wait() на семафоре. 2 спит и ждет, когда семафор разбудит его. Теперь у нас есть тред (3), который имеет более высокий приоритет, чем 1. Этот тред в сочетании с QoS блокирует сигнал от 1 к семафору и, таким образом, голодают оба других потока. Таким образом, семафоры в iOS не имеют приоритетного наследования (Priority Inheritance).
Synchronized
В Objective-C существует также опция использования @synchronized. Это простой способ создания мьютекса. Поскольку у Swift его нет, мы должны копнуть глубже. Вы можете познакомиться с @synchronized, вызвав objc_sync_enter.
Поскольку я неоднократно видел этот вопрос в интернете, давайте ответим на него тоже. Насколько я знаю, это не конфиденциальный метод, поэтому его использование не исключает вас из App Store.
Concurrency Queues Dispatching
Поскольку в Swift нет мьютекса, и синхронизация также была удалена, он стал золотым стандартом для разработчиков Swift, которые используют DispatchQueues. Если вы используете его синхронно, то получите такое же поведение, как и мьютекс, так как все действия помещаются в одну очередь. Это предотвращает одновременное выполнение.
Недостатком является большое количество времени, поскольку нужно много перемещений и изменений контекста. Это не имеет значения, если ваше приложение не нуждается в высокой вычислительной мощности, но в случае, если у вас возникнут потери фреймов, вы можете рассмотреть другое решение (например, Mutex).
Dispatch Barriers
Если вы используете GCD, у вас есть больше возможностей для синхронизации вашего кода. Один из них – Dispatch Barriers. С их помощью мы можем создавать блоки защищенных частей, которые должны выполняться вместе. Мы также можем контролировать, в каком порядке выполняется асинхронный код. Это звучит странно, но представьте, что вам предстоит выполнить долгую задачу, которую можно разделить на части. Эти части необходимо запускать по порядку, но их можно снова разделить на более мелкие куски. Эти меньшие куски части могут выполняться асинхронно. Dispatch Barriers теперь можно использоваться для синхронизации больших частей, в то время как отдельные куски могут работать сами по себе.
Trampoline
Trampoline на самом деле не является механизмом, предоставляемым ОС. Это шаблон, который вы можете использовать, чтобы гарантировать, что метод вызывается в правильном треде. Идея проста: метод проверяет в начале, находится ли он в правильном треде, иначе он вызывает себя в правильном треде и возвращается. Иногда вам будет необходимо использовать вышеуказанные блокирующие механизмы для реализации процедуры ожидания. Это имеет значение только в том случае, если вызванный метод возвращает значение. В противном случае вы можете просто вернуться.
Не используйте этот шаблон слишком часто. Это заманчиво, но в то же время это путает ваших коллег. Они могут не понять, почему вы меняете треды повсюду. В какой-то момент это захламляет код и тратит ваше время.
Заключение
Вау, это был довольно тяжелый пост. Существует так много возможностей для параллельного программирования, и этот пост раскрыл вам только самые общие положения. В то же время, существует множество механизмов и много примеров для рассмотрения. Я, наверное, раздражаю всех на работе всякий раз, когда говорю о тредах, но они важны, и медленно, но верно, но мои коллеги начинают соглашаться. Как раз сегодня я должен был исправить ошибку, в которой операции получали асинхронный доступ к массиву, а Swift, как мы узнали, не поддерживает атомарные операции. Угадайте, что? Это заканчивалось сбоем и падением. Возможно, этого не произошло бы, если бы все мы знали больше о конкурентности, но, честно говоря, я тоже этого не заметил.
Изучить свои инструменты – лучший совет, который я могу вам дать. Надеюсь мой пост дал вам отправную точку для параллелизма, а также предоставил способ контролировать хаос, который проявится, как только вы погрузитесь глубже. Удачи!
apptractor.ru