Главная страница «Первого сентября»Главная страница журнала «Информатика»Содержание №9/2009


Предлагаю коллегам

“Волк, коза и капуста”: реализация в среде Flash

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

— У меня через два урока поставили замену. Можешь быстро найти мне “Волка, козу и капусту”?

Любой учитель информатики, конечно, понимает, что речь идет не о реальных животных и кочане, а о классической алгоритмической игре :).

Коллегу надо было выручать, но, на ее и мою беду, я как-то не мог с ходу собразить, где мне взять требуемую программу. Зато у меня как раз был час свободного времени и Flash — я готовился к курсам повышения квалификации на очных курсах нашего Педагогического университета. Эх, была не была — рискну, неужели за час я не запрограммирую простую задачку?

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

Задача

Итак, мне требовалось “нарисовать” среду и запрограммировать исполнителя, выполняющего допустимые действия (и только их!). Ученики должны были интерактивно выдавать команды исполнителю, перетаскивая персонажей на поле и переправляя лодку. В игре должны были распознаваться терминальные (конечные) состояния — успешная переправа или съедение кого-либо. Одна из визуализаций среды показана на рис. 1. Сказать по правде, “настоящие” картинки персонажей я нашел позже — в описываемой ситуации мне пришлось ограничиться просто прямоугольниками с соответствующими надписями.

Рис. 1. Визуализация среды

При наступлении одного из терминальных состояний на экране возникает соответствующее сообщение (см. рис. 2).

Рис. 2

Рис. 3

Почти все время, пока я в спешке программировал игру, за окном школы в чьей-то машине хрипели песни какого-то популярного исполнителя, среди которых была и “Я убью тебя, лодочник!”. Это музыкальное сопровождение меня изрядно раздражало, но сама идея мне понравилась — лодочника я рисовать не стал. Он ведь все равно из лодки не выходит, так что с алгоритмической точки зрения лодка и лодочник — единое целое. Поэтому на рис. 1 лодочника нет. Если кого-то это смущает, можно его пририсовать. Ну, или ограничиться пояснением, что он есть, но спрятался в лодке :).

Отрисовка и расположение объектов

Взглянем на рис. 3. Как видите, я расположил основные объекты в четырех слоях. Слои имеют информативные названия, поэтому, я надеюсь, понятно, где что. Пятый, пустой, слой я использовал для размещения скрипта. Это стандартный прием программистов, использующих Flash.

Также на рис. 3 можно увидеть библиотеку клипа. В ней имеются:

· Символ “Коза”, который представлен одним экземпляром (Instance name) koza.

· Символ “Волк”, представленный одним экземпляром volk.

· Символ “Капуста”, представленный одним экземпляром kapusta.

· Символ “Берег”, посредством которого отрисованы берега. Берега удобно сделать клипами, поскольку это позволяет проще проверять факт помещения персонажа на берег — не надо оперировать координатами, достаточно проверить факт соприкосновения двух клипов. Этот символ представлен двумя экземплярами left и right.

· Кнопка “Стрелка”, два экземпляра (toright и toleft) которой используются для переправ.

· Кнопки “Ура!” и “GameOver”. Дело в том, что надписи в конечных состояниях сделаны кнопками, возвращающими к началу игры.

· В символе “Лодка” имеются два кадра, соответствующие различным ориентациям лодки, — нос смотрит влево и нос смотрит вправо.

Примечание для эстетов: самому мне, честно говоря, безразлично, как называть, например, капусту — kapusta или cabbage. Но я уверен, что для лучшего и быстрого понимания кода русскоязычной аудиторией название kapusta предпочтительнее.

Реализация и скрипты

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

· У клипов koza, kapusta, volk и lodka я определелил свойство bereg, которое может принимать два значения — left и right, в зависимости от того, на каком берегу находится персонаж.

· У клипа lodka есть также свойство pas. Значение этого свойства — пассажир лодки или null, если лодка пуста.

· В смысле перетаскивания с берега в лодку и обратно, и коза, и волк, и капуста ведут себя абсолютно одинаково. Поэтому я определил три функции: OnPress, OnRelease и OnReleaseOutside — и сделал их обработчиками соответствующих событий всех трех клипов.

· В обработчиках кнопок “Влево”/“Вправо” имеется “смертельный грех”, заключающийся в перемещении лодки и сидящего в ней персонажа на абсолютное значение 100 пикселей. Конечно, можно все аккуратно посчитать — зная координаты клипов-берегов, перемещать лодку “универсально”. Но я демонстрирую все же не настоящий продукт, а пример, сделанный за час и “на коленке”. Пусть и доработанный позже. И не вижу никакого смысла в данном случае “городить огород” (записывать легко конструируемое, но довольно сложное и не прозрачное выражение для координат).

stop(); //Поскольку в нашем клипе три кадра,

//надо остановить проигрывание на первом

//Начальная конфигурация

koza.bereg=kapusta.bereg=volk.bereg="left";

lodka.bereg="left";

//Это присваивание отделено от группы

// предшествующих

//исключительно по соображениям наглядности

lodka.pas=null;

//В лодке никого нет

//(кроме невидимого лодочника)

_root.onEnterFrame=function(){

//В каждом кадре проверяем,

//не достигнуто ли заключительное состояние

if (((koza.bereg==volk.bereg)&&

(koza.bereg!=lodka.bereg))||

((koza.bereg==kapusta.bereg)&&

(koza.bereg!=lodka.bereg)))

gotoAndStop(2); //Кого-то съели…

if ((koza.bereg=="right")&&

(volk.bereg=="right")&&

(kapusta.bereg=="right")&&

(lodka.pas==null))

gotoAndStop(3); //Все перевезли

}

//Обработчик кнопки вправо

toright.onPress=function() {

if (lodka.bereg=="left") {

lodka.gotoAndStop(2);

//Сменим ориентацию носа лодки

lodka.bereg="right";

lodka._x+=100;

if (lodka.pas) {

lodka.pas.bereg="right"

lodka.pas._x+=100;

}

}

}

//Обработчик кнопки влево

toleft.onPress=function() {

if (lodka.bereg=="right") {

lodka.gotoAndStop(1);

lodka.bereg="left";

lodka._x-=100;

if (lodka.pas) {

lodka.pas.bereg="left"

lodka.pas._x-=100;

}

}

}

//Обработчик нажатия кнопки мыши

//на любом персонаже

function OnPress() {

//Запомним координаты персонажа

curx=this._x;

cury=this._y;

//Начинаем перетаскивание

startDrag(this);

}

 

//Обработчик опускания кнопки мыши

//любого персонажа

function OnRelease() {

//Закончим перетаскивание

stopDrag();

//Если мы высаживаем пассажира

//на левый берег…

if ((lodka.pas==this)&&

(lodka.bereg=="left")&&

(this.hitTest(left))) {

lodka.pas=null;

return;

}

//Если мы высаживаем пассажира

//на правый берег…

if ((lodka.pas==this)&&

(lodka.bereg=="right")&&

(this.hitTest(right))) {

lodka.pas=null;

return;

}

//Если мы сажаем пассажира в лодку…

if ((this.hitTest(lodka))&&

(lodka.pas==null)&&

(this.bereg==lodka.bereg)) {

lodka.pas=this;

return;

}

this._x=curx;

this._y=cury;

}

koza.onPress=kapusta.onPress=volk.onPress=OnPress;

koza.onRelease=kapusta.onRelease=volk.onRelease=OnRelease;

koza.onReleaseOutside=kapusta.onReleaseOutside=

=volk.onReleaseOutside=OnRelease;

Се. Ль. Островский

TopList