Одна игра на разных языках разные игры на одном языке
Бейсик, Паскаль, Си, Ассемблер, Форт, Фортран, диБейзIII, Солт,
Мульти-Эдит, Смолток. Пока достаточно. Можно ли назвать то
общее, что могло бы объединять эти языки. И если раньше этим
общим было только то, что все это - языки программирования, то
теперь к ним прибавилась еще одна черта: игрушка Soko-Ban,
которая далее будет реализована на всех этих языках.
Предисловие
Как-то раз, в молодости, восхищенный логически выверенными построениями лабиринтов игрушки Soko-Ban, я решил запрограммировать ее. Сделал это на Паскале. И, убедившись, что программа работает, отложил листинг куда-то в сторону. В самом деле, не продавать же его было американцам.
И вот некоторое время спустя произошла следующая история. Встретился с одним товарищем. Тот попросил рассказать о языке Паскаль. Я начал рассказывать, обяснил типы, операторы, процедуры, строение программы. А когда дело дошло до примера программы на языке вдруг вспомнил о когда-то написанном листинге с игрушкой Soko-ban. Быстренько введя друга в алгоритм функционирования игры, я тут же указывал на соответствующий фрагмент программы... А поскольку и игра и программа представляли собой завершенное построение, то мой рассказ о языке Паскаль не просто отложился в деталях в голове товарища, но сделал это [рассказ] в виде единого целого.
И, по-достоинству оценив методическую помощь, оказанную мне программой, в следующий раз другому товарищу я начал сразу же рассказывать о языке Паскаль на примере листинга.
Но это, оказалось было не все. И когда в одной ситуации, мне пришлось тому же товарищу, который уже знал Паскаль, объяснять язык Си, я снова прибег к помощи того же листинга игрушки Soko-Ban. Поскольку с алгоритмом игры он уже был знаком, как алгоритм был реализован на Паскале знал, мы бысто, указывая на общее и разное в обоих языках, наваяли программу на Си.
Более того, когда мне, как преподавателю необходимо было студентов, знающим Паскаль, обучать языку Ассемблера, мой старый листинг снова пригодился мне: я предложил им реализовать программу с языка Паскаль, на язык Ассемблера. А поскольку в данном случае в их задачу вошло еще и разобраться самим в алгоритме программы (фактически восстановить его из программы), то качество усвоения материала, я вас уверяю, увеличилось.
Вот таким образом обстояло дело исторически.
Что же имеем мы на сегодняшний день? И что представляет собой данная статья? Что вы, читатель, можете извлечь из нее полезного для себя?
Но по-порядку. Первый раздел статьи, не считая ее введения, представляет описание алгоритма игры Soko-ban, проанализированный с точки зрения сложности ее программной реализации.
Дальнейшие же разделы являют собой собственно программы - реализации игры на различных языках программирования. Вводные параграфы каждого "программного" раздела представляют язык, анализируют его характерные, особенности (конечно, кратко), и описывают конкретные нюансы реализации алгоритма игры на данном языке. Листинг же, следующий далее, подробно и откоментированно реализует игрушку.
Листинги разных игр похожи друг на друга в той же степени, в которой похожи одни на одного сами языки. Причем немногим более, чем поверхностный анализ программ уже показывает, что они представляют собой не переписанный алгоритм, реализованный на Паскале, к примеру, а в каждом языке, точнее в программе на соответствующем языке передан и сохранен дух самого языка, сохранены особенности технологии производства программ на данном языке и стилистика языка.
При описании программ и в их [программ] комментариях используется терминология, характерная для того или иного языка. Так, программа на Форте, состоит из экранов, определений слов. В реализации на Смолтоке приведен полностью протокол класса, инициализированы необходимые экземпляры и методы применительно к подмножеству реализации Смолтока Smalltalk/V. Причем предыдущий абзац вряд ли будет понят читателем, имеющем смутное представление о Форте или Смолтоке. Но тем, кто знает язык, и что важнее, тем кто тот или иной язык изучает, такой подход позволит проверять свое знание языка и понимание его.
Тот факт, что каждый язык предназначен для конкретных целей и решает эффективнее именно те задачи, для которых спроектирован, не только не оспоряется автором, но даже подтверждается каждой программой. (куда уж характернее пример программы на макро-языке редактора Мульти-Эдит, который, безусловно, хорош для написания макрокоманд обработки текста в среде редактора и заставил поломать голову при программировании вещи, совершенно не свойственной для редакторов текстов: написание программы с элементами интерактивного взаимодействия, являющейся к тому же компьютерной игрушкой). И тем не менее, такой подход оправдан. Ибо программисту, пользующемуся Мульти-Эдитом и решившему написать быстренько макро-команду, не имевшему ранее дело с макро-языками (допустим, Траком) это "быстренько" не только выльется в определенный (достаточно-таки длительный) период времени, но и отобьет всякую охоту заниматься этим в следующий раз. Данная же программа игрушки, потребовав на разбор не много, все-таки, времени, даст необходимый минимум знаний особенностей упомянутого макро-языка.
Таким образом, данное произведение следует воспринимать не только как курьез в области программирования, но и как справочник по языкам программирования в их сравнительном плане. (Более точно можно сказать: справочник по фразеологии языков программирования.)
Идея и реализация данной статьи чем-то перекликается с существовавшей в свое время (молодое поколение программистов вряд-ли помнит об этом, но было! было...) традицией писать на разных языках программирования интроспективные программы, что позволяло судить о выразительной мощи языков. И следовательно языкам менее "выразительно мощным" не чувствовать себя ущербными.
Несколько слов о выборе языков программирования. В данном издании статьи будут приведены программы на тех языках, которые распространены сейчас в среде IBM PC-подобных машинах, то есть там, где не только не сложно (относительно) достать тот или иной компилятор того или другого языка, но и подбирались языки, все-таки распространенные в данной среде.
Алгоритм игры Soko-Ban
Сначала, почему именно данная игрушка удостоена такого пристального внимания? Во-первых, так сложилось исторически, как можно узнать из предисловия. Во-вторых, программа, используемая с данной целью (а именно
- с целью быть написанной на разных языках и приведенной в листингах полностью) должна удовлетворять ряду условий: быть не слишком большой, но и не слишком маленькой, быть не слишком сложной, но и не слишком простой. В-третьих, программа должна удовлетворять ряду общих требований: быть интересной, зрелищной и т.д.
Совокупность данных требований и позволила остановиться на игрушке Soko-Ban.
Принцип игры
Во всех версиях игра (и это было одним из требований технического задания) внешне выглядит следующим образом:
╠╠╠╠╠╠╠╠╠╠
╠╠ ╠╠
╠╠<> <>╠╠
╠╠╠╠╠╠ ╠╠╠╠╠╠
╠╠ <> <> ╠╠
╠╠╠╠╠╠ ╠╠ ╠╠╠╠╠╠ ╠╠ ╠╠╠╠╠╠╠╠╠╠╠╠
╠╠ ╠╠ ╠╠╠╠╠╠ ╠╠╠╠╠╠╠╠╠╠╠╠╠╠ . . ╠╠
╠╠ <> <> . . ╠╠
╠╠╠╠╠╠╠╠╠╠ ╠╠╠╠╠╠╠╠ ╠╠><╠╠╠╠╠╠╠╠ . . ╠╠
╠╠ ╠╠╠╠╠╠ ╠╠╠╠╠╠╠╠╠╠╠╠
╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠
Игра относится к классу логических головоломок и представляет собой лабиринт выложенный (во всех версиях данных реализаций) спрайтом, или изображением стенки ╠╠. Тут и там в лабиринте раскиданы или расположены изображения ящиков <>. А еще в лабиринте находятся изображения мест ящиков . . Главный герой игры >< (в меру - упитан, в меру - воспитан, в меру - симпатичен), часто в листингах называется двигателем, потому что двигает ящики, при помощи нажатия юзером клавиш Up, Left, Right и Down передвигается по лабиринту, натыкаясь на стенки и на ящики. Причем, если ящик один, а после него (в направлении по оси герой-ящик) находится свободное пространство, то при нажатии в данном направлении соответствующей клавиши, ящик сдвигается в том направлении. То есть, главный герой двигает ящики по лабиринту и пытается устанавливать их на места для ящиков. Если ящик становится на свое место, то он уже выглядит следующим образом шш (только повернутым на 45 градусов).
Вот цель-то игры и состоит в том, чтобы двигатель установил все ящики на соответствующие места. А ввиду ограничений, имманентных игре, (именно: если ящик подтолкнули к стене в углу, его оттуда уже не выковыряешь - раз, и два ящика толкать - силенок не хватает - два) игра, особенно на дальнейших лабиринтах, становится нетривиальной логической задачей.
Нюансами реализации, требующими особой проработки являются следующиме несколько мест в игре.
Во-первых, это различная реакция графических изображений частей игры (далее - спрайтов) на свое местонахождение. А именно, если герой находится на пустом месте, он выглядит следующим образом: ><. Если он наступает на место для ящика, он опять-таки выглядим вышеприведенным образом, в отличии от ящика, который на пустом месте выглядит <>, а на месте для ящика - шш (только повернутым на 45 градусов).
Второе (учитывающее первое) это момент инициализации различных лабиринтов в игре. Ибо в разных лабиринтах бывает разнообразие следующих ситуаций: ящик на пустом месте, ящик на месте для ящика, герой на пустом месте, герой на месте для ящика(! - попробуй внешне различить!).
Третье, проистекающее из первого и второго - это момент подсчета очков: как и каким образом его осуществлять, если учитывать, что мы пишем не только универсальный алгоритм обработки множества различных лабиринтов, но и эффективный по времени выполнения алгоритм.
Прежде всего, о программе в целом и об игрушке как программе.
В любой компьютерной игрушке (и наша не является исключением) можем мы различить следующие части:
- логический драйвер устройств ввода;
- драйвер устройств вывода;
- структуры данных (как внутри игрушки, так и вне ее - форматы
данных, хранящихся в файлах и т.д.);
- основной цикл игры.
Логический драйвер ввода
То, что так красиво называется, является, грубо говоря, подпрограммой, а точнее выражаясь, функцией, занимающейся вводом команд с клавиатуры. Хотя в более сложных игрушках драйвер ввода обрабатывает разные устройства ввода: мыши, джойстики, птицы...
Так вот, программы уровня непосредственно обработки устройств ввода называть принято драйверами. А программы, с которыми взаимодействует игрушка, принято называть логическим драйвером ввода (понимание этих слов понадобится нам при чтении листингов).
В частности, логический драйвер унифицирует ввод, что позволяет запараллеливать при необходимости работу клавиатуры и мыши, например.
Драйвер устройств вывода
Драйвер вывода мы собираемся писать таким образом, чтобы он был универсален. А под универсальностью мы будем понимать возможность для себя с минимальными затратами вводить на любом этапе работы изменения в программу.
Структуры данных и алгоритмы их обработки
Безусловно, уже глядя на лабиринт игрушки, мы понимает, что основной структурой данных в программе будет массив, где будет закодирована информация о текущем состоянии в игре. Более тонким является вопрос, каким образом мы будем хранить информацию в этом массиве. Но не будем томить читателя последовательностью рассуждений о возникающих на каждом шагу трудностях и путях их преодоления. Перейдем к конечному результату.
Массив игры представляет собой эквивалент изображения на экране, но в кодах, что позволяет функции движения осуществлять проверку корректности перемещения по лабиринту.
Каждая позиция массива игры хранит код спрайта, который находится на соответствующем ему месте на экране. Но! Старший бит каждой позиции является признаком того, есть ли данная позиция местом для хранения ящика. таким образом, если данная координата содержит код спрайта границы лабиринта, то код ее будет... один. А если данная координата является местом для установки ящика, то код ее будет другой, но старший бит данной позиции в массиве игры будет установлен в единицу.
Далее, вводится два набора спрайтов. Во втором код каждого спрайта имеет установлен старший бит. При такой кодировке функция вывода спрайта на экран пишется для каждого набора спрайтов.
Что позволяет успешно преодолеть первую из перечисленных нами трудностей. Функция вывода спрайта будет выводить для кода героя и кода героя с установленным старшим битом одно и то же изображение, а для кода ящика и кода ящика, установленного на место - разные изображения.
Похожая кодировка позволяет преодолеть и вторую нашу трудность, связанную с инициализацией следующего лабиринта. Один код мы используем для кодирования героя, стоящего на пустом месте, другой - для героя, который стоит на месте, предназначенном для установки ящика.
Причем, при инициализации лабиринта мы используем два счетчика в игрушке: общее количество мест для установки ящиков и количество уже установленных на место ящиков. Если при этом учеть еще и принцип, по которому мы собирается обрабатывать визуализацию перемещения - если по координате, на которой только что стоял ящик, пишется не-ящик, уменьшить счетчик количества поставленных на место ящиков; если же по координате, на которой должен стоять ящик, пишется код ящика, то увеличить счетчик количество пославленных на место ящиков - то становится ясным, как божий день, как определить завершение игры: сравнить значение обоих счетчиков. При равенстве значений, все ящики установлены на предназначенные для них места. Что, собственно говоря, и позволяет преодолеть нам третью трудность, встреченную на пути реализации игрушки.
И, перед тем, как перейти к собственно программам, еще одно слово об обработке движений главного героя.
Процедура перемещения главного героя осуществляет обработку движения в заданном направлении. Направление задается парой смещений: дельта-X и дельта-Y, которые позволяют одному алгоритму обрабатывать сразу четыре направления (как выразился бы поэт: одним выстрелом убить сразу четыре зайца). Так, например, если дельта-X = 0, а дельта-Y = + 1, то это означает, что отрабатывается движение в направлении вниз, то есть, согласно следующей системе координат:
^ ( 0, -1 )
}
|
<_______+______>
( -1, 0 ) } ( + 1, 0 )
}
V ( 0, + 1 )
Вот в данном направлении, оператор
массив_игры[ координата-X + дельта-X, координата-Y + дельта-Y ]
обеспечит обращение к позиции по координате в нужном направлении, а оператор
массив_игры[ координата-X + дельта-X 2, координата-Y + дельта-Y 2 ]
к позиции, второй от текущей по данной координате.
Вот, собственно по-существу и все. Перейдем к делу.