Лунолет-1

...Куркулятор ворует продукты...

Алиса в стране чудес

Оригинальная статья:
 Михаил Пухов, журнал "Техника молодежи", июнь 1985 г.
Переработал в апплет:
 Егор Абрамович
Последняя правка:
 22.03.2012 07:03
Нажмите, чтобы запустить игру

Содержание

Предыстория

В середине 80-х годов у меня и у моих друзей появились дома программируемые микрокалькуляторы МК-54. Это было самое первое электронное вычислительное устройство, которое попало ко мне в руки. О, что это была за машинка! Она знала синусы, косинусы, и умела запоминать нажатые кнопки! Это был отнюдь не бухгалтерский, а вовсе даже научный вариант вычислительного устройства.

перевод статьи (1)

In the middle of 80-ies my friends and I beeing teens in Soviet Union got in our
disposal <a href="http://en.wikipedia.org/wiki/MK-61">the programmable
calculator</a>. Oh, it was a wonder of machinery that time! It knowed about
sines, cosines and was able to remember buttons pressed!

<p>

Used by: package-summary.html (57)

Помнится, что заполонившие все вокруг впоследствии дешевые китайские калькуляторы вызывали у меня некоторое чувство недоумения - почему, ну почему, они умеют делать только четыре арифметических действия и могут запоминать только одно число?.. Но это было уже позже, а в то счастливое время было здорово нажимать всякие кнопки и обнаружить, например, что если набрать на экранчике какое попало число и все время нажимать кнопку синус, то в конце концов получается 0, а если косинус - почему-то странное число 0.739085...

Научно-популярные журналы в это время стали публиковать разные статьи о микрокалькуляторах. "Хитом" стала статья, опубликованная Михаилом Пуховым в журнале "Техника молодежи" [*] за июнь 1985 г. В ней рассказывалось о человеке, который, якобы, принес в редакцию фантастический рассказ, а редактор вздумал проверять его с помощью микрокалькулятора. А потом изложил эту историю в статье со слов гостя и привел программу для калькулятора, которая моделировала маневры космических аппаратов в непосредственной близости безатмосферных небесных тел. Особый вес модели придавало то, что консультантом раздела был Герой Советского Союза, летчик-космонавт СССР Ю.Н.Глазков! Автор справедливо замечал, что игра может использоваться также в качестве учебного пособия или "как основа ряда электронных игр для программируемых микрокалькуляторов". О, как он был прав! Для того чтобы поиграть в эту игру, необходимо было терпеливо ввести около сотни кодов в калькулятор, что занимало минут 20, притом нельзя было ошибиться, случайно нажав не ту клавишу, при том программа стиралась каждый раз при выключении микрокалькулятора! Но и это не могло остановить настоящих энтузиастов!

перевод статьи (2)

Popular science magazines started to publish some articles about calculators
that time. In June 1985, an article by Mikhail Pukhov at <a
href="http://en.wikipedia.org/wiki/Tekhnika_Molodezhi">Tekhnika Molodezhi</a>
issue became a real hit. It told about person who brought a science fiction
story to the magazine office and editor decided to check it with his pocket
calculator. Later he has published this story and added a program for the
calculator to it. This program was able to model the maneuvers of spacecrafts in
the vicinity of atmosphereless celestial bodies.

<p>

In order to play this game, it was necessary to enter patiently about a hundred
codes into calculator, which took about 20 minutes. Mistakes, when accidentally
pressing the wrong key, were not allowed. The program was erased every time you
turn off the calculator! But this could not stop real enthusiasts!

Used by: package-summary.html (57)

Игра была увлекательная, что и говорить... Да только вот беда, подходящий калькулятор сейчас найти сложно... Впрочем, вычислительной техники вокруг - завались... Ну вот я и решил как-то восстановить программу, переработав ее в Java-апплет в среде Greenfoot, которая предназначена для обучения подростков программированию и восхищает меня своей наглядностью и простотой.

перевод статьи (3)

The game was exciting, to be sure... The only trouble was that right now it is
hard to find a calculator of such type... However, there are a lot of computers
around... Well, I decided to restore the program somehow, reworking it into <a
href="http://www.greenfoot.org/scenarios/3362">Java-applet</a> in <a
href="http://www.greenfoot.org">Greenfoot</a> environment designed to teach
teens programming. Greenfoot delights me with its clarity and simplicity.

<p>

Used by: package-summary.html (57)

Я решил оформить свою версию в стиле литературного программирования с помощью программы pyWeb. Поэтому исходники программы вы можете найти ниже, они будут перемешаны с текстом статьи. Поскольку оригинальная статья представляет из себя комментарии Пухова к рассказу его гостя, то эта статья является моими комментариями к статье Пухова, то есть комментариями к комментариям.

Благодарности

Михаэлю Кенигу,
и другим разработчикам - за среду разработки Greenfoot, удивительно подходящую для наглядного программирования игр и головоломок в формате Java-апплетов.
Михаилу Пухову,
за написание статьи и программы для калькулятора, которая вдохновляет любителей программирования из бывшего СССР уже больше четверти века.
Стивену Лотту,
за создание системы pyWeb, позволяющей литературно изложить код программы.

История

Итак, начнем! Слово Михаилу Пухову:

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

"Пилот наконец решился и нажатием кнопки отправил в реактор последние остатки топлива. На космонавтов обрушилась десятикратная перегрузка. Тысячетонная громадина корабля дрогнула и медленно двинулась вверх. Люди были спасены".

Подобными эпизодами изобилуют поступающие в нашу редакцию рассказы начинающих фантастов. Рецензировать такие произведения затруднительно. Интуитивно ясно, конечно, что после столкновения с "громадным метеоритом" от звездолета ничего не останется, а "последних остатков топлива" не хватит, чтобы даже при "десятикратной перегрузке" обеспечить взлет "тысячетонной громадины корабля" со сколько-нибудь приличной планеты. Но какими аргументами подкрепить интуитивные соображения? Не будешь же каждый раз проделывать громоздкие вычисления по соответствующим формулам - рассказов в отдел фантастики приходит ежедневно около десяти. Где взять время для этих проверок?

Чтобы решить эту проблему, редакция "Техники молодежи" использовала доступную ей на тот момент вычислительную технику - программируемый микрокалькулятор:

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

Есть среди моих проверочных программ и такая, которая рассчитывает взлет с безатмосферных планет и посадку на их поверхность. Подробная инструкция к этой программе (условно она называется "Лунолет-1") и описание увлекательной компьютерной игры, в которую можно играть с ее помощью, приведены далее. Но вернемся к проверке поступающих в редакцию материалов.

Надо сказать, что, помимо многочисленных писем, к нам довольно часто приходят посетители. Как правило, это весьма необычные люди. Один несет новый проект или действующую модель вечного двигателя либо безопориого движителя; другой рассказывает о встречах со "снежным человеком" и чудом дотянувшими до наших дней мезозойскими динозаврами. А года два назад в редакции появился человек, который категорически утверждал, что он якобы... провалился к нам из будущего, из конца XXI века!

Этот человек бывал у нас на протяжении двух недель. И каждый раз рассказывал что-нибудь о будущем. Мы внимательно все выслушивали (так полагается по долгу службы), записывали его рассказы на магнитофон. Ничего, впрочем, особенного в них не было - любой из нас при желании мог придумать и не такое. Да и как проверить? Потом он куда-то пропал, и вскоре все забыли о нем.

А недавно возникла мысль: теперь, когда у меня есть мои проверочные программы, я могу с помощью компьютера проанализировать и рассказы этого человека. Ведь все записано на пленку, а пленки хранятся в архиве!

Какое-то время ушло на розыски и подготовительную работу. И вот передо мной расшифровка старой магнитофонной записи. Рядом - готовый к вычислениям микрокалькулятор.

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

Жирным шрифтом мы будем помечать комментарии редакции, а текст рассказчика из будущего будем выделять наклонным шрифтом.

Вот одна из его историй:

Я управлял космическим кораблем один-единственный раз в жизни. [†]

Конечно, в юности, как и многие мои сверстники, я мечтал стать космонавтом. Но мечты эти развеялись на первой же медкомиссии: при перегрузках больше трех g мне становилось плохо. А тех, кто не выдерживал пятикратной, к дальнейшим испытаниям не допускали. Волей-неволей пришлось забирать документы. Я подал на вычислительную технику, через шесть лет благополучно защитил диплом и - ирония судьбы! - был направлен по распределению на Луну, в Центр имени С. П. Королева. Там я работаю до настоящего времени.

После того как по соседству нашли неорганическую нефть. Центр сильно разросся. Теперь это настоящий город с населением порядка трех тысяч человек. Прикрывающие его купола соединены туннелями. Как в метро, только стены прозрачные. Это, грубо говоря, большие трубы, протянутые прямо по лунной поверхности. Одни нз туннелей ведет к астровокзалу. Космо-порт Центра - обширный комплекс, он обслуживает всю Солнечную систему. По роду работы я часто бываю в порту, потому что корабли напичканы электроникой, рано или поздно что-нибудь выходит нз строя, а чинить вычислительную технику - это моя специальность и прямая обязанность.

Случай, о котором я упомянул, произошел летом 2087 года. Работа у нас строится циклически: четыре месяца трудимся, два отдыхаем. Как правило, на Земле. Родные тоже прилетают иногда погостить - пассажирская линия Земля - Луна открылась давно. В то лето ко мне прилетел сын, Сергей. Я не видел его несколько месяцев, за это время он сильно подрос. Ему скоро двенадцать, и он бредит космонавтикой. Мы с женой надеялись, что самостоятельное путешествие на Луну очень его обрадует. Да и сам он, как она сообщила, в ночь накануне вылета совсем не спал.

Но когда я встретил его на астровокзале, он выглядел разочарованным.

- Ерунда! - сказал он. - Сидишь в кресле, стюардесса носит конфеты и воду в тюбиках. Как в самолете. Никаких перегрузок. Хоть и невесомость, но плавать по воздуху запрещают. Заставляют сидеть в кресле да еще и пристегиваться. Вот если бы самому в рубке сидеть за штурвалом и нажимать рычаги...

Он горько вздохнул и грустил минут пять, пока мы добирались домой. Потом отправился погулять. Вернулся через полчаса, разочарованный еще больше: на поверхность не выпускают, скафандр не дают, все кругом самое обычное, деревья и люди как на Земле. Никакой Луны нет. Разве что тяжесть поменьше, но это ему неинтересно - после двух-то суток невесомости на борту лайнера. Было уже поздно, и я уложил его спать. А потом и сам лег - завтра с утра на работу. Я пообещал взять его с собой: там интересно - вычислительные машины, манипуляторы и прочее.

Но наутро мне позвонили - появилось срочное дело. Я все записал, потом сварил кофе. Сергей был уже на ногах. Когда мы позавтракали, я сообщил, что планы изменились, так что пусть пока посидит дома. Он сначала возмутился, но потом смирился с необходимостью. Я пояснил, что буду в отсутствии каких-нибудь полтора часа.

- Папа! - сказал он. - А куда ты пойдешь?
- На космодром.
- А, - он разочарованно махнул рукой. - Я там уже был. Там скучно. Никуда не пускают.
- Мне не в порт, - объяснил я. - Мне на местные линии. Это небольшая площадка в стороне от главного поля. Лайнеров, на каком ты летал, там нет. Только лунолеты местного сообщения.
- Настоящие? - заинтересовался он.
- Разумеется, не игрушечные. Но они маленькие - всего две тонны сухого веса. Вернее, сухой массы. Здесь ведь все весит в шесть раз меньше, чем на Земле.
- Знаю, - отмахнулся Сергей. - А что ты там будешь делать?
- Работать, - пожал я плечами. - На одной из этих машин отказал киберпилот. Надо посмотреть. Если какой-нибудь пустяк, сделаю на месте. А если что-то серьезное...
- Ты пойдешь в рубку? - Глаза у него расширились. Я невольно расхохотался.
- Какая там рубка! Кабина, два кресла. Не повернешься.
- Но приборы там? -. продолжал он допрос. - Рычаги управления там?
- Конечно, - простодушно признался я. - Где же им быть еще?
- Возьми меня с собой, - потребовал он.
- Но это не игра, - попробовал я объяснить. - Это работа...
- Папа! - сказал он. - Ты мне обещал.
- Только не говори маме, - попросил я, сдаваясь.

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

На проходной я показал удостоверение вахтенному.

- А это что за гражданин?
- Он со мной, - сказал я. - Это мой сын.
- А где его документ? - спросил вахтенный.
- Ему не положено. Он еще маленький.

Минуту вахтенный размышлял. Ситуация, очевидно, была для него новой.

- Ладно, пускай идет. Под личную ответственность.

Скафандры нам выдали без проблем. Сережке, конечно, он был великоват, но только чуть-чуть - не такой уж он у меня маленький.

- Баллоны стандартные, - предупредил выпускающий. - На два часа. Справитесь?
- Конечно. Мне и часа хватит с гарантией. Двадцать минут туда, двадцать обратно, десять на месте. Ну и десять на всякие осложнения.
- Ясно, - добродушно сказал выпускающий. - Только пусть лучше осложнений не будет. Хорошо?

Я опустил забрало, и мы прошли в воздушный шлюз. А спустя короткое время уже шагали под голубым светом Земли. Была середина лунной ночи - на Земле в эти дни новолуние.

Сказать, что Сергей был восхищен, - значит, ничего не сказать. Он был ошеломлен. Похоже, он никак не ожидал, что ему выдадут скафандр. Настоящий, с индивидуальной системой жизнеобеспечения. На Земле я бы в таком весил килограммов сто пятьдесят, да и Сергей потянул бы на добрую сотню. Мы осторожно шагали по ровному реголиту - искусственного покрытия здесь никто не прокладывал, убрали крупные камни, и все. Не для важных персон. Но дорожка была ухоженной. До нас здесь ходили многие - все, кто работал на дальних лунных базах и опорных пунктах. Строители, ученые, инженеры...

- А там что такое? - Сергей вновь обрел способ ность задавать вопросы. Его голос в моем шлемофоне звучал непривычно.

Он показывал направо. Там выступали из-за горизонта массивные строения промышленного блока.

- Это тебе неинтересно, Сережа, - сказал я. - Там качают нефть и гонят из нее керосин. Для двигателей. - Я махнул в сторону космопорта. - И еще вырабатывают жидкий кислород.
- Для скафандров? - Он произнес слово "скафандр" с особенным выражением. Я усмехнулся:
- И для скафандров, конечно. Но в основном его везут на те же заправочные станции. Горючее ведь не будет гореть без окислителя.

Некоторое время мы шагали в молчании. Идти стало чуть труднее - дорожка поднималась к вершине обычного для Луны плоского холма. Еще сотня шагов - и мы достигли места своего назначения. Нашим глазам открылась стоянка лунолетов.

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

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

- Это настоящий корабль? - спросил он.

Я понял. На Земле похожие машины стоят в каждом парке отдыха. Аттракционы. Влезай в люк и испытывай всякие ощущения.

- Самый что ни на есть, - сказал я.
- А как им управлять? Я усмехнулся.
- Проще простого. Вот этот ящик, - я указал пальцем, - называется киберпилот. Если тебе нужно попасть, допустим, на базу "Циолковский", ты набираешь на пульте задание, потом нажимаешь вот эту кнопку, и киберпилот благополучно доставляет тебя куда надо. Но сейчас именно он-то и неисправен.

Это Сергея на время утихомирило. Он, видимо, ожидал чего-нибудь в духе земных аттракционов, когда сам даешь вводные и тебя швыряет в разные стороны. Я аккуратно снял с киберпилота пломбу и сдвинул лицевую панель. На вид все в порядке. Дал на схему напряжение. Циферблаты на пульте ожили.

Где же искать повреждение?..

- А это что за рычаги? - услышал я голос Сергея.

Я обернулся. Оказывается, он устроился во втором кресле. Играл в космонавта. Указывал он на рычаги ручного управления двигателем.

- Это для посадки, - объяснил я. - Киберпилот всегда доставит тебя куда надо, но он не знает местность. Вдруг там трещина, скажем, или какой-нибудь камень. Тогда нужно дать небольшой импульс, чуть притормозить спуск, чтобы машина проскочила опасное место.

Я опять повернулся к киберпилоту. Однако не тут-то было.

- А как дать импульс? - спросил Сергей.
- Ты мне мешаешь, - сказал я. - Откуда мне знать? Впрочем, гляди: написано "Расход топлива" и цифры, ага, в килограммах. Рычаг стоит здесь, значит, ты собираешься истратить 65 килограммов топлива. А возле правого рычага - "Время" и тоже цифры. Это, видимо, время, за которое ты собираешься свое топливо израсходовать. Меньше время - больше тяга, а если время больше - тяга соответственно меньше. Сейчас рычаг стоит на цифре три. Значит, если ты подашь эту команду на двигатель, он израсходует 65 килограммов за три секунды.
- А это много?
- Не знаю, - сказал я. - По-моему, все равно что ничего.
- А как подать команду на двигатель?
- Откуда я знаю? - огрызнулся я. - Ты мне мешаешь. Пульт наверняка заблокирован, а баки пусты. Полюбуйся, - я ткнул пальцем в индикатор. - Видишь? "Топливо", четыреста. Всего-навсего! А шкала на две с половиной тонны.
- А как... - продолжал он допрос.
- Отстань от меня! - скомандовал я. - Ты мне мешаешь. Скажешь еще хоть слово, тут же идем домой. И вообще, отошлю тебя к маме.

Он обиженно умолк, а я занялся киберпилотом. Набрал контрольный тест - он прошел нормально. Набрал второй - тоже полный порядок. Что они там, спятили? Совершенно исправная машина. Я набрал третий тест. И тут началось...

Проверка

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

Сначала определим константы, которые мы будем использовать при расчете. Дело происходит на Луне, ускорение свободного падения 1,62 м/с 2, поэтому:

константы (4)

/**
 * Gravity on the Moon.
 */
final double g = 1.62;

Used by: Lunolet.java (23)

Масса корабля без горючего две тонны, сюда нужно добавить массу рассказчика вместе со скафандром (150 кг) и его сына (100 кг), соответственно:

константы (5)

/**
 * Mass of the rocket plus two cosmonauts in space suits.
 */
final double mass = 2000+150+100;

Used by: Lunolet.java (23)

Двигатель, очевидно, работает на керосине и жидком кислороде, скорость истечения 3660 м/с.

константы (6)

/**
 * The engine is obviously running on kerosene and liquid oxygen,
 * the exhaust velocity of 3660 m/sec.
 */
final double c = 3660;

Used by: Lunolet.java (23)

Очередь за предельным ускорением. По словам рассказчика, ему становится плохо уже при трех g, поэтому:

константы (7)

/**
 * Max acceleration that cosmonaut can bear.
 */
final double limit = 9.81*3;

Used by: Lunolet.java (23)

Теперь определим переменные, которые мы будем использовать.

Скорость и высота равны нулю:

переменные (8)

/**
 * Current altitude of lunolet.
 */
private double h = 0;

/**
 * Current velocity of lunolet.
 */
private double v = 0;

Used by: Lunolet.java (23)

Запас топлива 400 кг:

переменные (9)

/**
 * Remaining amount of fuel.
 */
private double fuel = 400;

Used by: Lunolet.java (23)

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

переменные (10)

/**
 * Current supply of oxygen.
 */
private double oxygen = 3600;

Used by: Lunolet.java (23)

Исходные данные проверены. На экране горит высота - ноль. Рядом скорость - тоже ноль. Все правильно. Можно во всеоружии ждать грядущих события. А они, несомненно, вот-вот последуют.)

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

- Не смей! - крикнул я.

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

Вот и первые данные, которые можно использовать для JUnit-теста:

проверка (11)

lunolet.startEngine(65, 3);
while (!lunolet.isPaused() && !lunolet.isGameOver()) lunolet.act();
assertEquals(Lunolet.G_LOC_MESSAGE, lunolet.getMessage());
assertEquals(335, Math.round(lunolet.getFuel()));

Used by: LunoletTest.java (56)

(Значит, пульт все-таки заблокирован не был и команда прошла на двигатель. Не особенно, конечно, убедительно, но нас интересует фактическая сторона дела. Двухтонная машина, жалкие 60 кг топлива, и вдруг - "чудовищной силы удар"! К счастью, проверить данный эпизод нетрудно. Подадим ту же команду и на свой пульт: Топливо: 65, Время: 3, Пуск. Экран блекнет, потом загорается вновь... Как это ни удивительно, перегрузка действительно превысила допустимую, рассказчик потерял сознание и какое-то время после отсечки двигателя не сможет управлять лунолетом.)

...Когда я очнулся, кругом было только небо. Сколько я был без сознания? Не знаю. Но мы падали, падали со страшной скоростью! Очевидно, за время моего беспамятства ракета прошла вершину траектории и теперь стремительно неслась вниз. Сергей тянулся к рычагам управления. Но игры кончились. Какую-то секунду я не мог опомниться, но еще через секунду был у пульта. Что я мог успеть в такой ситуации? Заметил лишь показания индикаторов - скорость восемьдесят, высота триста с чем-то.

- Папа! - крикнул Сергей.

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

(На нашем же индикаторе высота полета 169 м - везде округляем до целых. Скорость 84 м/с. Ну что ж, будем считать, проверка закончена. Скорость еще более-менее, но высота в рассказе завышена вдвое. Можно выключать компьютер. Впрочем, пока он добирался до пульта, прошло еще две секунды. Две секунды свободного падения с выключенным двигателем. Но куда можно упасть эа две секунды? Разве что на Луну - не в небо же! Ладно, для очистки совести засылаем соответствующую команду: Топливо: 0, Время: 2, Пуск. На экране зажигаются высота 334, скорость 81. Невероятно, но цифры совпали! Почему же он утверждает, что лунолет падал? Непонятно. Но набираем новую команду: Топливо: 65, Время: 3, Пуск. Опять сигнал "ПЕРЕГРУЗКА"...

проверка (12)

assertEquals(169, Math.round(lunolet.getAltitude()));
assertEquals(84, Math.round(lunolet.getVelocity()));

lunolet.startEngine(0, 2);
while (!lunolet.isPaused() && !lunolet.isGameOver()) lunolet.act();
assertNull(lunolet.getMessage());
assertEquals(335, Math.round(lunolet.getFuel()));
assertEquals(334, Math.round(lunolet.getAltitude()));
assertEquals(81, Math.round(lunolet.getVelocity()));

lunolet.startEngine(65, 3);
while (!lunolet.isPaused() && !lunolet.isGameOver()) lunolet.act();
assertEquals(Lunolet.G_LOC_MESSAGE, lunolet.getMessage());
assertEquals(270, Math.round(lunolet.getFuel()));

Used by: LunoletTest.java (56)

...Когда я очнулся снова, мы опять падали. Я рванулся к красной клавише, но взгляд мой упал на индикатор высоты. Почти километр! И цифры росли! Значит, мы вовсе не падали - мы неслись вверх со скоростью реактивного истребителя! И в прошлый раз мы, конечно же, тоже поднимались! Меня ввела в заблуждение невесомость. Мы действительно падали, но падали вверх! И я, болван, включив двигатель, только усугубил наше и без того тяжелое положение. Зато теперь появилось время, чтобы собраться с мыслями...

(Не так-то просто, оказывается, поймать его на слове! У нас очередной останов. Высота 916 м - действительно, почти километр, - скорость 166 м/с. Примерно 600 км/ч. Маловато, конечно, для истребителя, но... Можно ли (считать это серьезной ошибкой? Будем объективны, оставим рассказчику право на художественное преувеличение. Как бы то ни было, включать двигатель он вроде пока нe собирается, так что нашей программе можно тоже дать передышку.)

проверка (13)

assertEquals(916, Math.round(lunolet.getAltitude()));
assertEquals(166, Math.round(lunolet.getVelocity()));

Used by: LunoletTest.java (56)

...- Папа! - сказал Сергей, выбираясь из-под меня. - А почему ты говорил, что 65 килограммов - это все равно что ничего?

- Отстань от меня! - приказал я. - Марш во второе кресло и пристегнись!

Я проследил, как он это выполнил, и пристегнулся сам. Цифры на индикаторе высоты увеличивались, но все медленнее и медленнее. На киберпилот надежды нет, придется выкручиваться самому. Но пока, пожалуй, лучше не делать ничего. Гнать вверх бессмысленно. Вниз (а рядом с красной клавишей я углядел другую, "Реверс тяги") - еще хуже. Вот начнем падать, тогда... Я заранее установил рычаги в положение 25 кг и 2 с и ждал. Да, я сильно ошибся насчет этих килограммов. Похоже, в них большая сила...

- Папа! -- подал голос Сергей. - А мы сможем улететь в космос?
- Отстань! - рявкнул было и, но вдруг у меня защемило сердце. Ребенок не понимал, что мы на волосок от гибели, для него это было игрой! - Сереженька, - сказал я как можно ласковее, - в космос мы с тобой еще слетаем. Но сейчас, пожалуйста, помолчи".

Мы взлетели уже почти на десять километров. Цифры на указателе скорости дошли до нуля и начали медленно растн, теперь уже с отрицательным знаком. Когда скорость достигла примерно тридцати метров в секунду, я нажал красную клавишу. Высота к этому времени уменьшилась почти до девяти километров. Свободный полет продолжался ровно две минуты...

(Наконец-то появилась новая цифра, которую можно проверить. Две минуты с нулевой тягой. Команда: Топливо: 0, Время: 120, Пуск. После останова высота 9175, скорость - минус 28. Опять все сходится! Новая команда: Топливо: 25, Время: 2, Пуск. Машина рассчитывает маневр.)

проверка (14)

lunolet.startEngine(0, 120);
while (!lunolet.isPaused() && !lunolet.isGameOver()) lunolet.act();
assertNull(lunolet.getMessage());
assertEquals(270, Math.round(lunolet.getFuel()));
assertEquals(9175, Math.round(lunolet.getAltitude()));
assertEquals(-28, Math.round(lunolet.getVelocity()));

lunolet.startEngine(25, 2);
while (!lunolet.isPaused() && !lunolet.isGameOver()) lunolet.act();
assertNull(lunolet.getMessage());
assertEquals(245, Math.round(lunolet.getFuel()));

Used by: LunoletTest.java (56)

...На сей раз перегрузка была терпимой. На индикаторах мелькали цифры. Высота почти не изменилась, но мы опять поднимались! Что ж, мы так и будем болтаться, на этой высоте, пока не кончится все топливо? И весь кислород?! В отчаянии я установил рычаги в положение 10 и 10 и дал реверс тяги. Лунолет кувыркнулся двигателем вверх. Далеко внизу я увидел постройки Центра, обширные поля космодрома и крошечное пятнышко площадки, с которой мы так неосмотрительно стартовали...

(У нас после останова высота 9151, скорость - чуть меньше пяти метров в секунду и, действительно, снова направлена вверх. Вводим команду Топливо: 10, Время: 10, Реверс. После останова высота равна 9044 м, скорость - 26 м/с со знаком минус. Снова падаем, и довольно быстро.)

проверка (15)

assertEquals(9151, Math.round(lunolet.getAltitude()));
assertEquals(5, Math.round(lunolet.getVelocity()));

lunolet.setReverse(true);
lunolet.startEngine(10, 10);
while (!lunolet.isPaused() && !lunolet.isGameOver()) lunolet.act();
assertNull(lunolet.getMessage());
assertEquals(235, Math.round(lunolet.getFuel()));
assertEquals(9044, Math.round(lunolet.getAltitude()));
assertEquals(-26, Math.round(lunolet.getVelocity()));

Used by: LunoletTest.java (56)

...Скорость падения увеличивалась быстрее, чем я рассчитывал. Я решил притормозить: убрал реверс, установил 25 кг и 5 с и надавил на красную клавишу. Но она не поддалась. Видимо, эти пульты устроены так, что новая команда блокируется, пока не исполнена прежняя. И лишь когда десять секунд истекли, корабль вновь крутанулся двигателем вниз. Но когда тот выключился, мы оставались все на тех же девяти километрах и опять, хоть и очень медленно, поднимались!

(Команда: Топливо: 25, Время: 5, Пуск. Результат: высота 8984, скорость два с половиной метра в секунду, снова со знаком плюс. Он что, действительно собирается провести здесь всю оставшуюся жизнь?)

проверка (16)

lunolet.setReverse(false);
lunolet.startEngine(25, 5);
while (!lunolet.isPaused() && !lunolet.isGameOver()) lunolet.act();
assertNull(lunolet.getMessage());
assertEquals(210, Math.round(lunolet.getFuel()));
assertEquals(8984, Math.round(lunolet.getAltitude()));
assertEquals(25, Math.round(lunolet.getVelocity()*10));

Used by: LunoletTest.java (56)

...Нет, решил и, так дело не пойдет. Если мне даже удастся установить приемлемую скорость спуска - допустим, пять метров в секунду, - и поддерживать ее до самого прилунения, то сколько времени на это уйдет? Полчаса? Час? Топлива не хватит наверняка. Да и кислород...

- Папа! - вновь подал голос сын. - Топлива всего двести...
- Двести десять! - рявкнул я. - Но помолчи же!

Мы уже снова падали - все быстрее и быстрее. Топлива осталось чуть больше половины. Но если, черт побери, на половине топлива мы ухитрились забраться сюда, то оставшейся половины должно хватить для возвращения! Если, конечно, его разумно тратить... Только как это - разумно?

Я решил выждать сколько возможно, а потом дать резкий тормозной импульс. Заранее установил рычаги в положение 100 и 3. На этот раз свободный полет продолжался полторы минуты. Мы уже опять неслись со скоростью истребителя, но только вниз. До поверхности оставалось чуть больше двух с половиной километров, когда я надавил красную клавишу. На нас вновь обрушились перегрузки...

(Полторы минуты свободного падения. Топливо: 0, Время: 90, Пуск. Высота 2652, скорость падения 143. Около 500 км/ч. Но сравнение с истребителем, как мы договорились, не ошибка, а всего лишь гипербола. Вводим Топливо: 100, Время: 3, Пуск. На экране, естественно, сигнал "ПЕРЕГРУЗКА". Перегрузки снова превысили допустимую величину. Не слишком ли часто? Высота 2123, скорость минус 32.)

проверка (17)

lunolet.startEngine(0, 90);
while (!lunolet.isPaused() && !lunolet.isGameOver()) lunolet.act();
assertNull(lunolet.getMessage());
assertEquals(210, Math.round(lunolet.getFuel()));
assertEquals(2652, Math.round(lunolet.getAltitude()));
assertEquals(-143, Math.round(lunolet.getVelocity()));

lunolet.startEngine(100, 3);
while (!lunolet.isPaused() && !lunolet.isGameOver()) lunolet.act();
assertEquals(Lunolet.G_LOC_MESSAGE, lunolet.getMessage());
assertEquals(110, Math.round(lunolet.getFuel()));
assertEquals(2123, Math.round(lunolet.getAltitude()));
assertEquals(-32, Math.round(lunolet.getVelocity()));

Used by: LunoletTest.java (56)

...Когда вернулось сознание, высота упала на полкилометра, но скорость снизилась до тридцати метров в секунду. Маневр удался! Можно попробовать идти дальше с этой же скоростью, а где-нибудь ближе к поверхности повторить маневр. Только как удержать скорость? Я решил экспериментально подобрать нужную тягу. Для начала дал 10 кг за 20 с. К нам снова вернулся вес - правда, поменьше, чем на Луне, - но скорость все-таки росла. К окончанию маневра она достигла 50 метров в секунду. Я увеличил тягу: те же десять килограммов, но теперь за 15 с...

(Повторяем оба маневра. Топливо: 10, Время: 20, Пуск. Высота 1314, скорость спуска 49. Топливо: 10, Время: 15, Пуск. Высота 515, скорость 58. Многовато!)

проверка (18)

lunolet.startEngine(10, 20);
while (!lunolet.isPaused() && !lunolet.isGameOver()) lunolet.act();
assertNull(lunolet.getMessage());
assertEquals(100, Math.round(lunolet.getFuel()));
assertEquals(1314, Math.round(lunolet.getAltitude()));
assertEquals(-49, Math.round(lunolet.getVelocity()));

lunolet.startEngine(10, 15);
while (!lunolet.isPaused() && !lunolet.isGameOver()) lunolet.act();
assertNull(lunolet.getMessage());
assertEquals(90, Math.round(lunolet.getFuel()));
assertEquals(515, Math.round(lunolet.getAltitude()));
assertEquals(-58, Math.round(lunolet.getVelocity()));

Used by: LunoletTest.java (56)

...Скорость все равно увеличивалась, а до лунной поверхности оставалось каких-нибудь полкилометра. Запас топлива - 90 килограммов. Делать было нечего. Я дал 35 кг за полторы секунды - и, конечно же, вновь отключился...

(Команда: Топливо: 35, Время: 1.5, Пуск. На экране "ПЕРЕГРУЗКА". Результат: высота 390, скорость спуска 17. Примерно 60 км/ч. Это уже полегче.)

проверка (19)

lunolet.startEngine(35, 1.5);
while (!lunolet.isPaused() && !lunolet.isGameOver()) lunolet.act();
assertEquals(Lunolet.G_LOC_MESSAGE, lunolet.getMessage());
assertEquals(55, Math.round(lunolet.getFuel()));
assertEquals(390, Math.round(lunolet.getAltitude()));
assertEquals(-17, Math.round(lunolet.getVelocity()));

Used by: LunoletTest.java (56)

...Когда я пришел в себя, то понял, что маневр удался. До поверхности было еще почти 400 метров, а скорость упала больше чем втрое! Семнадцать метров в секунду - скорость электрокара! Я знал уже, как ее сохранить. Я заслал 22 кг за 22 с. Сам я родился 22 марта. Сергей родился, когда мне было 22 года. И вообще, 22 - число для меня счастливое...

(Команда: Топливо: 22, Время: 22, Пуск. На экране мелькают цифры.)

проверка (20)

lunolet.startEngine(22, 22);
while (!lunolet.isPaused() && !lunolet.isGameOver()) lunolet.act();
assertNull(lunolet.getMessage());
assertEquals(33, Math.round(lunolet.getFuel()));

Used by: LunoletTest.java (56)

...Я действительно угадал! Скорость почти не менялась, только в десятых долях. Высота равномерно уменьшалась: 250... 200... 150... 100... 50... И вдруг до меня дошло, что мы вот-вот врежемся в поверхность Луны, амортизаторы не удержат! Топлива оставалось 33 кг. Оставив рычаг расхода на прежней отметке, я рванул второй вниз до упора - 0,7 с - и давил, давил, давил на красную клавишу. Но она поддалась, лишь когда до гибельного удара осталось меньше секунды. Опять перегрузки...

(На экране: высота 13,5, скорость 17,5. Команда: Топливо: 22, Время: 0.7, Пуск. "ПЕРЕГРУЗКА")

проверка (21)

assertEquals(135, Math.round(lunolet.getAltitude()*10));
assertEquals(-175, Math.round(lunolet.getVelocity()*10));

lunolet.startEngine(22, 0.7);
while (!lunolet.isPaused() && !lunolet.isGameOver()) lunolet.act();
assertEquals(Lunolet.G_LOC_MESSAGE, lunolet.getMessage());
assertEquals(11, Math.round(lunolet.getFuel()));

Used by: LunoletTest.java (56)

...Как ни удивительно, но я, видимо, отключился лишь на ничтожную долю секунды. Когда сознание вернулось ко мие, скорость была прежней, а от поверхности нас отделяли всего 7 метров. Двигатель молчал. Впоследствии я не раз задумывался, как такое могло случиться. Неужели сбой двигателя? Не знаю. Но в тот момент мне было не до размышлений. Палец лежал на клавише. Я повторил команду, послав в двигатель последние капли топлива. Я сделал это в тот же миг, как открыл глаза. Новый удар ускорений...

(Высота 7, скорость 17. Все сходится! Но не могли же одновременно подкачать и двигатель лунолета, и наша программа! Попробуем разобраться в ситуации - у нас-то время есть. Проверяем запас топлива. На индикаторе 11 кг. Значит, двигатель свои 22 кг отработал. Почему же тогда прежняя скорость? Попробуем определить время, на которое выключался двигатель, по запасу кислорода. Сейчас у нас 3253, а до маневра было 3274. Выходит, двигатель выключался на 21 с. Ничего себе, "ничтожная доля секунды"! Впрочем, и это нельзя считать неточностью рассказчика: выглядело-то все именно так, а откуда ему знать, сколько он был без сознания? У него ведь нет такой аппаратуры, как наша! А произошло следующее: расход был задан слишком большой, двигатель не только полностью погасил скорость, но и вновь разогнал лунолет вверх. И только после этого выключился на 21 секунду, за это время Луна вновь подтянула корабль к себе. А ои думает, "сбой двигателя"! Но ладно, вводим последнюю команду: Топливо: 22, Время: 0.7, Пуск. Да, но ведь топлива осталось всего 11 кг, а задано 22! На индикаторе загорается "НЕТ ТОПЛИВА". Машина сигнализирует о том, что команда на двигатель подана с превышением наличного запаса топлива. Когда оно иссякнет - а это случится ровно через 0,35 с, - он выключится окончательно. На экране мелькают цифры, и вдруг загорается ноль. Поверхность! Смотрим скорость: 3,5 м/с. Отличная посадка!)

проверка (22)

assertEquals(7, Math.round(lunolet.getAltitude()));
assertEquals(-17, Math.round(lunolet.getVelocity()));
assertEquals(11, Math.round(Math.round(lunolet.getFuel())));

lunolet.startEngine(22, 0.7);
while (!lunolet.isPaused() && !lunolet.isGameOver()) lunolet.act();
assertFalse(lunolet.isGameOver());
assertEquals(0, Math.round(lunolet.getFuel()));
assertEquals(0, Math.round(lunolet.getAltitude()));
assertEquals(-35, Math.round(lunolet.getVelocity()*10));

Used by: LunoletTest.java (56)

...Двигатель молчал. Я лежал в кресле в ласковых объятиях привычного лунного тяготения. Лунолет, покачиваясь на амортизаторах, стоял невдалеке от того места, откуда мы стартовали. На индикаторах застыли скорость - меньше четырех метров в секунду - и время - 350. Значит, мы летали неполные шесть минут...

Я повернул голову - как там мой Сергей? Он лежал неподвижно, глаза его были закрыты.

- Сережа, - позвал я. Он не шелохнулся.
- Сережа! - заорал я. Он оставался недвижен.

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

Он продолжал играть! Он играл в космонавта, убитого перегрузками!..

Потом, конечно, я многим рассказывал об этом приключении. Все, само собой, изумлялись, как это мне, впервые оказавшемуся за пультом, удалось выполнить столь успешную посадку. Только один приятель, по профессии селенолог, выслушал все внимательно и произнес: "Неплохо! Но мне, я думаю, в такой ситуации хватило бы и шестидесяти!" Он имел в виду, что затратил бы на посадку не триста с чем-то килограммов топлива, как я, а всего шестьдесят. Я не понимаю, как это можно сделать - ведь на старте было сожжено шестьдесят пять, значит, и на финиш должно уйти минимум столько же! Однако в подробности он вдаваться не стал. Хватило бы, и точка! Эти селенологи лихие ребята - гоняют на своих лунолетах по всей Луне. Им виднее.

Сережка, разумеется, тоже хвастался всем подряд. Его версия происшедшего звучала примерно так: "Папа посадил корабль на Луну, зато в космос поднял его я!" Друзья, конечно, сильно ему завидовали.

И только маме он ничего не сказал. Потому что пообещал.

(Рассказ подошел к концу. Но, собственно, и наша работа закончена. Отодвигаем клавиатуру в сторону. Какие неточности удалось обнаружить? Никаких. Так, парочку преувеличений. А проверить такие факты, как последний, наша программа не в состоянии...

Если же кому-нибудь захочется выяснить, прав ли был тот лихой селенолог, сделайте это сами. Закачивайте топливо в баки и дерзайте. Но только, пожалуйста, не забудьте перед стартом уменьшить массу лунолета на 100 кг. Вот так: final double mass = 2000+150; Пока не приобретете опыта, не берите с собой ребенка!)

Модель - начало программы

Приключения героев закончились, а для нас все только начинается. Первым делом нам потребуется создать класс, который будет моделировать лунолет. Поскольку мы используем Greenfoot, наш класс будет расширять класс Actor:

Lunolet.java (23)

→ какие объекты из других пакетов мы будем использовать? (55)

/**
 * Lunolet is a small rocket on a moon surface.
 * It is controlled by 2 sliders: one sets the amount of fuel to be burnt,
 * Another one sets time interval for which this fuel will be burnt.
 *
 * @author Egor Abramovich
 * @version December 2011
 */
public class Lunolet extends Actor
{

        → константы (4)    → (5)    → (6)    → (7)    → (26)    → (29)    → (30)
        → переменные (8)    → (9)    → (10)    → (25)    → (32)    → (37)    → (39)
        → работа двигателя (24)
        → возможные состояния лунолета (31)
        → загружаем картинки (48)
        → сообщения (28)    → (35)    → (42)    → (46)
        → посылка сообщений другим компонентам (49)    → (51)
        → расчет следующего хода (40)
        → установка параметров работы двигателя (34)    → (36)
        → геттеры и сеттеры (33)    → (38)    → (50)    → (52)    → (53)    → (54)
}

Пересчитываем высоту и скорость

Компьютер будет постоянно пересчитывать физические параметры лунолета в следующий момент времени - высоту, скорость, оставшееся количество топлива и кислорода. Если нам известны текущие значения этих параметров, то мы можем вычислить новые значения, которые будут иметь место через время dt:

doc-files/formulae.png

работа двигателя (24)

/**
 * Updating height, velocity, fuel and oxygen supply of lunolet.
 * <p>
 * <img src="doc-files/formulae.png">
 */
public void updatePhysics(double dt) {
    double vprev = v;
    v = v + (a - g) * dt;           // new velocity
    h = h + (v + vprev) / 2 * dt;   // new height
    fuel = fuel - q*dt;             // reduce fuel
    oxygen = oxygen - tstep;        // reduce oxygen
        → пошлем сообщение другим объектам о том, что положение лунолета изменилось (27)
}

Used by: Lunolet.java (23)

Здесь
a:это дополнительное ускорение, которое придают лунолету работающие двигатели,
g:ускорение свободного падения на поверхности Луны,
q:расход топлива в единицу времени, [кг/сек]

переменные (25)

/** Additional acceleration. */
double a;

/** Amount of combusted fuel per second, [kg/s] */
double q;

Used by: Lunolet.java (23)

Разбиваем временной интервал

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

doc-files/formulae.png

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

Зададим достаточно маленький промежуток времени tstep, и каждый раз при пересчете высоты и скорости будем сдвигаться на это значение:

константы (26)

/**
 * How much time in seconds passes between 2 clicks of "Act" button?
 */
public final static double tstep = 0.1;

Used by: Lunolet.java (23)

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

Подчеркнем, что мы хотим написать объект Lunolet таким образом, чтобы он ничего не "знал" о других объектах программы, не упоминал их в своем коде и не зависел от них. Все коммуникации будет осуществлять внутренний объект state, который мы обсудим позже в разделе Связь с другими объектами.

пошлем сообщение другим объектам о том, что положение лунолета изменилось (27)

if (h<epsilon) h = 0.0;
if (fuel<epsilon) fuel = 0.0;
if (oxygen<epsilon) oxygen = 0.0;

if (h==0.0) {
    if (v<maxLandingSpeed) {
        state.set(StateMachine.GAME_OVER);
    } else {
        setMessage(SOFT_LANDING_MESSAGE);
        state.set(StateMachine.INITIAL);
    }
    paused = true;

} else
    state.changed();

Used by: работа двигателя (24); Lunolet.java (23)

Если произошла успешная посадка, пошлем сообщение на консоль:

сообщения (28)

/**
 * Message sent to console when lunolet has been happily landed
 * on the surface of the Moon.
 */
public static String SOFT_LANDING_MESSAGE = "SOFT LANDING!";

Used by: Lunolet.java (23)

Здесь мы вообще-то хотели сравнивать высоту, скорость и запас кислорода с нулем, чтобы не допустить отрицательных значений:

if (h<0) h = 0.0;
if (fuel<0) fuel = 0.0;
if (oxygen<0) oxygen = 0.0;

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

константы (29)

/**
 * Precision of float comparisons.
 */
final double epsilon = 1e-5;

Used by: Lunolet.java (23)

Здесь мы также проверяем основное условие игры: не произошла ли уже посадка, и, если да, то не превысила ли скорость максимально допустимую:

константы (30)

/**
 * Landing speed should be lower than this value, otherwise
 * there will be an explosion.
 */
final double maxLandingSpeed = -6.0;

Used by: Lunolet.java (23)

Состояния лунолета

Лунолет может находится в разных состояниях, совершаемые действия будут разными для этих состояний:

INITIAL:Начальное состояние системы, когда лунолет еще находится на поверхности Луны.
FLIGHT:Контролируемый полет, выполнение очередной команды космонавта.
FREE_FLIGHT:Неконтролируемый полет, у нас закончилось топливо или кислород, и космонавт не может больше отдавать команды.
GAME_OVER:Конец игры, мы снова приземлились на поверхность Луны.

возможные состояния лунолета (31)

/**
 * Possible states of lunolet.
 */
enum StateMachine {
    INITIAL,
    FLIGHT,
    FREE_FLIGHT,
    GAME_OVER;
}

Used by: Lunolet.java (23)

Пауза

Определим переменную paused, которая будет переводить лунолет в состояние ожидания ввода очередной команды от пользователя. Физическое время для системы в этом состоянии как бы приостановлено.

На экране это состояние отражается большой кнопкой старта в правом нижнем углу.


paused = true

система ожидает от пользователя ввода команды


paused = false

система занята вычислениями и игнорирует ввод пользователя

переменные (32)

/**
 * Are we waiting for the next move from the cosmonaut?
 */
boolean paused = true;

Used by: Lunolet.java (23)

Определим методы доступа к этой переменной:

геттеры и сеттеры (33)

/**
 * Are we waiting for the next move from the cosmonaut?
 */
public boolean isPaused() {
    return paused;
}

/**
 * Pauses <code>act</code> method.
 */
public void setPaused(boolean paused) {
    this.paused = paused;
}

Used by: Lunolet.java (23)

Задаем команду для двигателя

startEngine() запускает команду "израсходовать указанное количество топлива за указанный промежуток времени".

установка параметров работы двигателя (34)

/**
 * Main method to control rocket engine.
 * @param dm  Amount of fuel to be burnt
 * @param dt  Amount of time for this.
 */
public void startEngine(double dm, double dt)
{
    lastMessage = null;
    paused = false;

    if (dm>fuel) {
        setMessage(NOT_ENOUGH_FUEL_MESSAGE);
        dt = dt*fuel/dm;
        dm = fuel;
    }

    setImage(reverse==1? rocketThrust: rocketReverseThrust);
    fireEngine(dm, dt);
}

Used by: Lunolet.java (23)

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

сообщения (35)

/**
 * Message sent to console when engine is requested
 * to spend more fuel than available.
 */
public static String NOT_ENOUGH_FUEL_MESSAGE = "NOT ENOUGH FUEL FOR THIS!";

Used by: Lunolet.java (23)

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

установка параметров работы двигателя (36)

private void fireEngine(double dm, double dt) {
    nsteps = (int) Math.round(dt/tstep); // adjust dt to tstep
    dt = nsteps*tstep;
    q = dm / dt;                         // calc combustion rate [kg/s]
    a = q * c / (mass + fuel) * reverse; // calc addit. acceleration
}

Used by: Lunolet.java (23)

переменные (37)

/** How many time steps on <code>tstep</code> should we do for lunolet? */
int nsteps;

Used by: Lunolet.java (23)

Мы можем направить лунолет двигателем вверх, чтобы он разгонялся по направлению к Луне с помощью команды setReverse(true).

Двигатели разгоняют ракету вверх при setReverse(false).

геттеры и сеттеры (38)

/**
 * Allows to direct engines up side down.
 * @param directFlag  If true, engine working in a normal way,
 *                    if false, engine is turned up side down.
 */
public void setReverse(boolean directFlag) {
    reverse = directFlag? -1: 1;
    setImage(reverse==1? rocket: rocketReverse);
}

/**
 * Are engines reversed now?
 */
public int getReverse() {
    return reverse;
}

Used by: Lunolet.java (23)

переменные (39)

/**
 * Reverse coefficient indicates when lunolet is turned upside down (-1).
 * Otherwise it is +1.
 */
int reverse = 1;

Used by: Lunolet.java (23)

Метод act()

Метод act() для объекта Greenfoot определяет действие, которое объект должен совершить в следующий момент времени.

расчет следующего хода (40)

/**
 * Calculate what will happen with lunolet in the next moment.
 */
public void act()
{
    if (paused) return;

    switch (state.value) {
      case INITIAL:
        if (a>0)
            state.set(StateMachine.FLIGHT);
        break;

      case FLIGHT:
        updatePhysics(tstep);
            → управляемый полет: проверяем топливо и кислород (41)
            → уменьшаем счетчик шагов (43)
        break;

      case FREE_FLIGHT:
        updatePhysics(tstep);
            → свободный полет: проверяем кислород (47)
        break;
    }
}

private boolean noOxygen = false;

Used by: Lunolet.java (23)

управляемый полет: проверяем топливо и кислород (41)

if (fuel==0.0) {
    setMessage(NO_FUEL_MESSAGE);
    state.set(StateMachine.FREE_FLIGHT);
    a = 0.0;
}
if (oxygen==0.0) {
    if (state.value==StateMachine.FLIGHT) {
        setMessage(NO_OXYGEN_MESSAGE);
        noOxygen = true;
        state.set(StateMachine.FLIGHT);
    }
}

Used by: расчет следующего хода (40); Lunolet.java (23)

Здесь мы можем послать на консоль сообщения о том, что недостаточно топлива, либо закончился кислород.

сообщения (42)

/**
 * Message sent to console when there is no more fuel.
 */
public static String NO_FUEL_MESSAGE = "OUT OF FUEL!";

/**
 * Message sent to console when there is no more oxygen.
 */
public static String NO_OXYGEN_MESSAGE = "NO MORE OXYGEN!";

Used by: Lunolet.java (23)

уменьшаем счетчик шагов (43)

nsteps--;
if (nsteps<0) nsteps = 0;
if (nsteps==0) {
    if (noOxygen) {
        setImage(rocket);
        a = 0.0;
        q = 0.0;
    } else
    if (Math.abs(a)>limit) {
            → ускорение слишком велико (44)
    } else
    if (fuel==0.0) {
        state.set(StateMachine.FREE_FLIGHT);
        setImage(rocket);
        a = 0.0;
        q = 0.0;
    } else
        paused = true;
}

Used by: расчет следующего хода (40); Lunolet.java (23)

Модель микрокалькулятора принимает время потери сознания равным разнице текущего ускорения ракеты и предельного ускорения, которое может выдержать космонавт:

Math.abs(a) - limit

ускорение слишком велико (44)

/* Acceleration is too high. */
setMessage(G_LOC_MESSAGE);
setImage(rocket);
double t = Math.abs(a) - limit;
fireEngine(0, t); // this will add more `nsteps`
→ внести поправку дискретизации (45)

Used by: уменьшаем счетчик шагов (43); расчет следующего хода (40); Lunolet.java (23)

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

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

внести поправку дискретизации (45)

double correction = t-nsteps*tstep;
updatePhysics(correction);

Used by: ускорение слишком велико (44); уменьшаем счетчик шагов (43); расчет следующего хода (40); Lunolet.java (23)

Здесь мы посылаем на консоль сообщение о том, что космонавт потерял сознание из-за перегрузок.

сообщения (46)

/**
 * Message sent to console when G-force is too high.
 */
public static String G_LOC_MESSAGE = "G-FORCE INDUCED LOSS OF CONSCIOUSNESS";

Used by: Lunolet.java (23)

свободный полет: проверяем кислород (47)

if (oxygen==0.0) {
    if (!noOxygen) {
        setMessage(NO_OXYGEN_MESSAGE);
        noOxygen = true;
    }
}

Used by: расчет следующего хода (40); Lunolet.java (23)


rocket.png

rocketWithThrust.png

rocketReverse.png

rocketReverseThrust.png

загружаем картинки (48)

GreenfootImage rocket, rocketThrust, rocketReverse, rocketReverseThrust;

public Lunolet() {
    rocket = new GreenfootImage("rocket.png");
    rocketThrust = new GreenfootImage("rocketWithThrust.png");
    rocketReverse = new GreenfootImage("rocketReverse.png");
    rocketReverseThrust = new GreenfootImage("rocketReverseThrust.png");
}

Used by: Lunolet.java (23)

Связь с другими объектами

Мы предполагаем написать объект Lunolet так, чтобы он ничего не знал о других объектах Greenfoot, которые его окружают - слайдерах, панелях и т.п. Для этого воспользуемся классами Observer (Наблюдатель) и Observable (Наблюдаемый), имеющимися в Java.

Мы не будем непосредственно наследовать Lunolet от класса Observable (мы должны наследовать его от Actor, чтоб он был визуальным компонентом Greenfoot), а заведем для этих целей небольшой внутренний класс State:

посылка сообщений другим компонентам (49)

/**
 * Inner class responsible for notifications to objects
 * interested in changes of this object.
 */
class State extends Observable {

    StateMachine value = StateMachine.INITIAL;

    void changed() {
        setChanged();
        notifyObservers();
    }

    void display(String message) {
        setChanged();
        notifyObservers(message);
    }

    void set(StateMachine value) {
        this.value = value;
        changed();
    }

}

State state = new State();

Used by: Lunolet.java (23)

Посылка текстового сообщения будет пересылать его Наблюдателям.

геттеры и сеттеры (50)

/** We'll save last message sent by lunolet to console for test purposes. */
private String lastMessage;

private void setMessage(String message) {
    state.display(message);
    lastMessage = message;
}

public String getMessage() {
    return lastMessage;
}

Used by: Lunolet.java (23)

Observer - это интерфейс, который должны имплементировать другие компоненты, которые хотят знать об изменениях состояния лунолета. Для этого они должны определить у себя метод update(Observable o, Object arg) и зарегистрироваться у лунолета через метод addObserver(this).

посылка сообщений другим компонентам (51)

void addObserver(Observer o) {
    state.addObserver(o);
}

Used by: Lunolet.java (23)

Определим методы доступа для высоты и скорости:

геттеры и сеттеры (52)

/**
 * Current velocity of lunolet.
 */
public double getVelocity() {
    return v;
}

/**
 * Current velocity of lunolet.
 */
public void setVelocity(double v) {
    this.v = v;
}

/**
 * Current altitude of lunolet.
 */
public double getAltitude() {
    return h;
}

/**
 * Current altitude of lunolet.
 */
public void setAltitude(double h) {
    this.h = h;
    state.changed();
}

Used by: Lunolet.java (23)

Определим методы доступа для запасов топлива и кислорода:

геттеры и сеттеры (53)

/**
 * Remaining amount of fuel.
 */
public double getFuel() {
    return fuel;
}

/**
 * Remaining amount of fuel.
 */
public void setFuel(double fuel) {
    this.fuel = fuel;
}

/**
 * Current supply of oxygen.
 */
public double getOxygen() {
    return oxygen;
}

Used by: Lunolet.java (23)

Когда игра окончена, мы переводим переменную state в состояние GAME_OVER.

геттеры и сеттеры (54)

/**
 * No more actions if lunolet has already exploded.
 */
public boolean isGameOver() {
    return state.value==StateMachine.GAME_OVER;
}

Used by: Lunolet.java (23)

Импортируем объекты из других пакетов:

какие объекты из других пакетов мы будем использовать? (55)

import java.util.Observable;
import java.util.Observer;

import greenfoot.*;  // (World, Actor, GreenfootImage, Greenfoot and MouseInfo)

Used by: Lunolet.java (23)

JUnit-тест

Имея под рукой модель, мы можем поместить операторы проверки в стандартную структуру JUnit-теста.

LunoletTest.java (56)

import greenfoot.util.GreenfootUtil;
import junit.framework.TestCase;

public class LunoletTest extends TestCase {

    public void testStory() {
        GreenfootUtil.initialise(new GreenfootUtilDelegateImpl());
        Lunolet lunolet = new Lunolet();
        lunolet.setPaused(false);
            → проверка (11)    → (12)    → (13)    → (14)    → (15)    → (16)    → (17)    → (18)    → (19)    → (20)    → (21)    → (22)
    }
}

JavaDoc

Сгенерируем описание пакета:

package-summary.html (57)

<body>
→ перевод статьи (1)    → (2)    → (3)
</body>

Сноски

[*]Название журнала себя оправдало. Техника была представлена молодежи.
[†]Тут-то рассказчик и попался! Судя по последующему циклу статей, он совершил на аналогичном звездолете с М.Коршуновым межпланетный перелет с Луны на Землю.