ZX-Ревю 1993 №7-8 1992 г.

Sinclair logo - продолжение, начало см. на с. 69-74, 90-96.


SINCLAIR LOGO

(Продолжение) Начало см. на с. 69-74, 90-96. ГЛАВА 5 ПОВТОРЯЮЩИЕСЯ ОПЕРАЦИИ Условия.

Под "условием" в ЛОГО понимается операция, которая дает в результате логическое значение TRUE ("истина") или FALSE ("ложь"), в ответ на вопрос типа: "Это число равно другому?"

В ЛОГИКЕ, а также в языках программирования, имеющих дело с обработкой списков (ЛИСП, ПРОЛОГ, ЛОГО), такие операции называют ПРЕДИКАТами. Может быть поэтому в ЛОГО все примитивы, относящиеся к этой категории, оканчиваются на "-P" Попробуйте набрать:

PRINT EQUALP 5 5

- получите в результате:

TRUE

Здесь условие EQUALP проверяет, равны ли два параметра. Если да, то выдается результат TRUE, иначе выдается результат FALSE. Например:

PRINT EQUALP 5 7 FALSE

Математические условия.

Как и в других математических операциях здесь используют символы, которые размещают между двумя аргументами (так называемая индексная форма записи). Это следующие символы: > - больше < - меньше = - равно. Примеры:

PRINT 1 > 2 FALSE

PRINT 5 < 8 TRUE

MAKE "FACT 5-5

PRINT :FACT TRUE

Знак "=" используется для сравнения двух объектов любого типа. Это могут быть числа, слова, списки. Эта операция дает значение TRUE тогда и только тогда, когда оба объекта одинаковы. Эта операция тождественна операции EQUALP, которую мы рассмотрели ранее. Разница, как видите, только в форме записи, поскольку EQUALP записывается не "между", а "перед" двумя параметрами (префиксная форма записи).

MAKE "SHOPPING[BREAD BUTTER MILK STAMPS]

PRINT :SHOPPING =[BREAD BUTTER MILK STAMPS] TRUE

MAKE "GIRL "MARY

PRINT :GIRL=MARY TRUE

MAKE "NAME "MARY

PRINT :GIRL = :NAME TRUE

PRINT (7-5)-2 TRUE PRINT EQUALP(7-5) 2 TRUE

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

MAKE "TODAY "FRIDAY

IF :TODAY = "FRIDAY [PRINT "TGIF] TGIF

Если оператор IF имеет три операнда, то в третьем операнде записывается список инструкций, которые должны выполняться, если условие "ложно".

MAKE "TODAY "MONDAY

IF :TODAY= "FRIDAY [PRINT "TGIF] [PRINT [NOT FRIDAY YET]] NOT FRIDAY YET

Как и конструкции REPEAT, команда IF может иметь достаточно длинную запись и ее приходится переносить со строки на строку.

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

MEMBERP

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

ТО CHECKSHOP: STUFF

MAKE "SHOPPING[BREAD BUTTER MILK STAMPS]

IF MEMBERP: STAFF:SHOPPING

[PRINT [GOT IT]] [PRINT [OH DEAR FORGOTTEN IT]

END

CHECKSHOP "MILK GOT IT

CHECKSHOP "BACON

OH DEAR FORGOTTEN IT

NUMBERP

Эта программа выдает значение TRUE, если ее аргумент является числом. В противном случае выдается FALSE.

Вам нередко приходится проверять, является ли то, что ввел пользователь, числом, прежде, чем приступить к обработке этого ввода. Согласитесь, нелепо задать вопрос о том, сколько Вам лет ("How old are you?") и пытаться как-то обсчитывать полученный результат, если в ответе было:

NOT VERY MUCH ("не очень много")

ТО CHECKNUM

MAKE "ANSWER FIRST READLIST IN MUMBERP: ANSWER [PRINT [[THANK YOU]] [PRINT [[I WANTED A NUMBER]]

END

Обратите внимание на то, что мы используем FIRST READLIST. Если бы мы использовали только READLIST, то результат был бы списком. Даже если в нем было бы всего одно число, например 5, он все равно распознавался бы только как список, а не как число.

WORDP

Эта операция дает в результате TRUE, если ее операндом является слово. Вспомните, что числа тоже являются словами, поэтому они тоже дают в результате TRUE.

PRINT WORDP 3 TRUE

PRINT WORDP "WISE TRUE

PRINT WORDP [MEN] FALSE

В последнем случае операнд является списком, в который входит только одно слово, но все же это список, а не слово и потому в результате выдано FALSE.

LISTP

Дает в результате TRUE, если операнд является списком.

PRINT LISTP:SHOPPING TRUE

В практической работе Вам придется много раз писать процедуры для обработки списков. Очень хорошей практикой будет устраивать в начале каждой процедуры проверку: "А является ли входной параметр списком?"

EMPTYP

Проверяет, не пустой ли операнд. Ищет пустое слово " или пустой список [] и если то, либо другое найдено, выдает TRUE. Этот оператор имеет очень широкое применение. Длинные списки анализируются путем разбиения их на элементы и последовательной обработки элементов одного за другим, пока список не станет пустым. Точно также слова анализируют, разбивая их на символы. Мы в дальнейшем будем очень часто использовать выражение вида:

IF EMPTYP : ALIST[STOP]

для того, чтобы останавливать процесс. NAMEP

Дает в результате TRUE, если операндом является имя чего-либо, т.е. этому имени соответствует некоторое содержание.

MAKE "MONTH "APRIL

PRINT NAMEP "MONTH TRUE

PRINT NAMEP "FRED FALSE

Рекурсия.

Мы уже рассматривали такие процедуры, как

ТО TRI

FORWARD 50 RIGHT 120 TRI

END

Запустите эту процедуру. Сама она не остановится. Придется для этого использовать

BREAK (CAPS SHIFT + SPACE). Самое важное в этой процедуре - это то, что она входит в бесконечный цикл повторов без использования повтора REPEAT, а вызовом самой себя TRI. Итак, TRI исполняет первых две инструкции - FORWARD 50 и RIGHT 120, после чего снова вызывается TRI, которая исполняет первые две инструкции и т.д. и т.п.

Рекуррентные процедуры могут иметь входные параметры, как и любые другие процедуры. В простейшем виде эти входные параметры всегда одни и те же при каждом вызове процедуры. Мы можем легко изменить процедуру TRI и задать любой многоугольник.

ТО POLY :SIDE :ANGLE FORWARD :SIDE RIGHT :ANGLE POLY :SIDE :ANGLE

END

Эта процедура отличается от той, которую мы рассматривали в главе 2 (с инструкцией REPEAT). Она может изображать некоторые такие вещи, которые той не под силу. Попробуйте:

POLY 50 144

Когда процедура вызывает саму себя, это называется рекурсией. В ЛОГО это основной способ организации повторяющихся процессов.

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

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

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

Аналогичный прием может быть использован и в ЛОГО:

ТО ADDON :NUMBER PRINT :NUMBER ADDON :NUMBER+1

END

ADDON 1 1 2 3

и т. д.

При первом вызове ADDON печатается 1 и во второй вызов передается параметр 2 и

т. д.

Рассмотрим еще один пример. Предположим, что нам надо найти сумму чисел от 1 до N, где число N начинается с единицы и непрерывно возрастает. Может быть, мы примем решение, что процесс надо остановить, когда сумма превысит одну тысячу. И тогда мы вводим команду STOP, служащую для того, чтобы останавливать текущую процедуру. В принципе, STOP действует так же, как и END, но может размещаться в любом месте процедуры.

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

ТО ADDUP :NUMBER

IF :TOTAL >1000 [STOP] MAKE "TOTAL :TOTAL + :NUMBER PRINT :TOTAL ADDUP :NUMBER+1

END

MAKE "TOTAL 0

ADDUP 1 1 3 6 10 15

и т. д.

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

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

ТО LBL :ALIST

PRINT FIRST :ALIST LBL BUTFIRST :ALIST

END

Все очень просто. Процедура распечатывает содержимое начала списка, а затем точно так же поступает с его остатком. Когда же она закончит свою работу? Когда список станет пустым.

ТО LBL :ALIST

IF EMPTYP :ALIST (STOP) PRINT FIRST :ALIST LBL BUTFURST :ALIST

END

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

Конечно, в некоторых случаях рекуррентные выражения могут быть весьма сложными, но простейшие применения рекурсии поистине просты. Кроме бесконечных рекурсий (которые на самом деле тоже бесконечными не являются, ведь при каждом вызове процедуры ЛОГО фиксирует факт этого вызова в оперативной памяти и рано или поздно эта память исчерпывается) существуют еще две возможности. В приведенных примерах процедура начинается с проверки. В языках, предназначенных для структурного программирования, таких как Паскаль или скажем, в развитой версии БЕЙСИКа Бета-БЕЙСИК, эта конструкция называется циклом WHILE. В ЛОГО это выглядит так:

ТО WHILE <параметр> IF <условие> [STOP] ... <тело процедуры> ... WHILE <параметр>

END

В этой процедуре <тело> не исполняется, если <условие> принимает значение TRUE. В частности, эта процедура вообще не исполняется, если исходное значение параметра таково, что <условие> справедливо.

Другой прием состоит в проверке условия в конце серии повторяющихся инструкций. Это соответствует команде UNTIL Паскаля или БЕТА БЕЙСИКа.

TO UNTIL <параметр>

... <тело процедуры>

IF <условие> [UNTIL<napaMeTp>]

END

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

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

TO DOTOTAL

MAKE "TOTAL О TOTALUP

PRINT SENTENCE [THE TOTAL IS] :TOTAL

END

Процедура TOTALUP демонстрирует использование процедуры NUMBERP для того, чтобы проверять те числа, которые пользователь вводит с клавиатуры. Она позволяет пользователю ввести числовую последовательность и закончить ее словом END или любым другим нечисловым вводом. Процедура организована по схеме UNTIL, рассмотренной выше. Единственное отличие состоит в том, что вводится дополнительная инструкция

MAKE "TOTAL :TOTAL + :NUM

если условие справедливо (TRUE). Эту инструкцию можно было бы поместить и в начало процедуры, но только в том случае, если бы первое значение NUM задавалось бы вышележащей процедурой DOTOTAL.

ТО TOTALUP

PRINT [TYPE IN A NUMBER] MAKE "NUM FIRST READLIST

IF NUMBERP :NUM[MAKE "TOTAL :TOTAL +NUM TOTALUP]

END

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

Генератор случайной последовательности.

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

Давайте рассмотрим сначала как ЛОГО может выдавать случайные числа. Вообще-то эти числа рассчитываются и потому не являются вполне случайными, но маловероятно, что Вы заметите их неслучайность.

Так, RANDOM 5, например, выдаст одно число из последовательности 0,1,2,3,4. Точно также RANDOM 6 даст число от 0 до 5.

Попробуйте:

REPEAT 50 [PRINT RANDOM 6]

- и посмотрите, что из этого выйдет.

Если Вам надо иметь случайное число не из диапазона [0;4], а из диапазона [1;5], то Вы тоже можете воспользоваться оператором RANDOM 5, но к полученному результату надо прибавить единицу. Это делается так:

1 + RANDOM 5.

Обратите внимание на то, что запись RANDOM 5+1, будет неправильной, поскольку сначала будет исполнено сложение 5 + 1, а затем процедура RANDOM с параметром 6.

Давайте используем эту процедуру для того, чтобы извлечь из списка случайный элемент. Прежде всего введем переменную ITEMNO - случайное целое число от 1 до значения COUNT, а затем используем эту переменную для выбора элемента списка.

ТО CHOOSE :ALIST

MAKE "IТЕМNО 1 + RANDOM COUNT :ALIST MAKE "CHOICE ITEM :ITEMNO :ALIST

END

Попробуйте эту процедуру с каким-либо списком. Например, для списка PRESENT:

CHOOSE :PRESENT

Для того, чтобы генерировать предложения, нам нужна сначала какая-то канва, какая-то скелетная схема, например:

THE SMALL DOG QUICKLY BITES THE CARELESS MAN

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

• Артикль "the" может иметь в качестве альтернативы артикль "а".

• Прилагательное SMALL (маленький, -ая) может быть заменено на иное, например: [BIG BROWN SPOTTED CARELESS]

• Существительное "DOG" (собака) является таким же объектом, как и [MAN TREE TABLE KENNEL]

• Наречие QUICKLY (быстро) описывает, как происходит событие. Вместо него можно употребить [VICIOUSLY SLOWLY CARFULLY HAPPILY]

• Глагол BITES (кусает) - указывает на происходящее действие точно так же, как и глаголы: [LIKES CHASES CARRIES HIDES].

• Еще один артикль "THE".

• Еще одно прилагательное "CARELESS" (беспечный)

• Еще одно существительное "MAN" (человек, мужчина). Итак, если мы введем сокращения:

AST - артикль; ADJ - прилагательное; ADV - наречие; VER - глагол;

KOU - существительное, то получим следующую скелетную схему:

[АКТ ADJ NOU ADV VER ART ADJ SOU]

Приравняем этот список переменной PATTERN, а с именами АКТ, ADJ, NOU и пр. свяжем свои списки глаголов, наречий, существительных и т.п. Так, например, список с именем "ADJ будет содержать:

[SMALL BIG BROWN SPOTTED CARELESS]

Теперь мы можем конструировать случайные предложения.

ТО RANDSENTENCE

MAKE "PATTERN [ART ADJ NOU VER ART ADJ NOU]

MAKE "ART [SMALL BIG BROWN SPOTTED CARELESS]

MAKE "KOU [МАК TREE TABLE KENNEL DOG]

MAKE "ADV [QUICKLY VICIOUSLY SLOWLY CARFULLY HAPPILY]

MAKE "VER [BITES LIKES CHASES CARRIES HIDES]

REPEAT 50 [DORANDOM]

END

TO DORANDOM

MAKE "OUTLINE [] FOLLOW :PATTERN PRINT "OUTLINE PRINT "

END

Процедура FOLLOW сначала проверяет, не является ли ее параметр (а это список, в котором закодирована скелетная схема нашей фразы) пустым. Если это так, происходит выход из процедуры. Иначе, берется первый элемент скелета, а он является именем списка слов и из этого списка делается случайный выбор. Выбранное слово добавляется к собираемому предложению OUTLINE.

ТО FOLLOW :APATTERN

IF EMPTYP :APATTERN [STOP]

MAKE "THISWORD FIRST :APATTERN

CHOOSE THING :THISWORD

MAKE "OUTLINE SENTENCE :OUTLINE :^ICE

FOLLOW BUTFIRST :APATTERN

END

ГЛАВА 6. ЧЕРЕПАШЬЯ ГРАФИКА-2.

Идеи, рассмотренные в двух предыдущих главах, могут быть проиллюстрированы с помощью "черепашьей графики". Возьмем процедуру RESPI из гл. 3 и модифицируем ее так, чтобы "черепашка" в одних случаях поворачивала бы направо, а в других - налево. Сделаем это, обеспечив дополнительный входной параметр, который будет списком чисел, значений COUNT, для которых "черепашка" должна поворачивать влево. Когда COUNT является числом, входящим в этот список, выполняется поворот налево, иначе - направо. Вставлять всю эту логику в оператор REPEAT, по-видимому, слишком длинно, поэтому проще создать отдельную процедуру:

ТО RESPI2 :SIZE :ANGLE :MAX :ALIST MAKE "COUNT 1 REPEAT :MAX [SUBSPI] RESPI2 :SIZE :ANGLE :MAX :ALIST

END

TO SUBSPI

FORWARD :SIZE * :COUNT

IF MEMBERP :COUNT :ALIST [LEFT :ANGLE] [RIGHT :ANGLE] MAKE "COUNT :COUNT+1

END

Проверьте эту процедуру со следующими параметрами:

RESPI2 5 120 6 [1 3] RESPI2 5 90 11 [3 4 5]

На рис. 1 показан пример работы процедуры.

Рис.1 RESPI2 5 90 7 [1 2 3 4]

Мы рассмотрели выше способ остановки рекуррентных процедур. Теперь интересно было бы рассмотреть, как можно остановить рекуррентную процедуру, изображающую замкнутую диаграмму (такую, в которой "черепашка" возвращается в ту точку, с которой начинала работу. Прежде всего, нам надо узнать, вернулась ли "черепашка" в точку старта. В ЛОГО есть процедура-примитив, которая вычисляет координаты "черепашки" в любой момент времени. Это процедура position, она выдает список из двух чисел. Первое число -горизонтальная координата, отмеренная от центра экрана; второе число - вертикальная координата, она тоже измеряется от центра экрана и для верхней его половины является положительной, а для нижней - отрицательной.

ЛОГО имеет три команды для перемещения "черепашки" в указанное положение:

SETX 50 - переводит ее на вертикаль, имеющую координату X, равную 50.

SETY -50 - переводит ее на горизонталь, имеющую координату Y, равную -50.

SETPOS [-20 30] - устанавливает "черепашку" в точку, с указанными в списке координатами.

Если перо "черепашки" опущено, то при исполнении каждой из этих трех операций будет прорисована линия от исходной точки к конечной.

В главе 3 мы говорили о том, что знак "минус" перед числом имеет двойственное значение. ЛОГО применяет простое правило к оценке знака "минус", если он находится в списке. Если перед знаком есть пробел или нет ничего и число следует непосредственно за знаком (без пробела), то знак «минус» рассматривается, как часть числа и это означает, что число отрицательное. Во всех прочих случаях этот знак интерпретируется, как самостоятельное слово.

Итак, [- 10 10] - это три слова, точно так же и [10 - 10] и [10-10] и ни одна из этих записей не может быть использована в качестве входного параметра для процедуры SETPOS.

С другой стороны, [10 -10] и [-10 10] - состоят из двух слов и могут быть использованы при вызове SETPOS.

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

Кроме процедур, позволяющих выставить X и Y-координаты "черепашки", существуют две функции XCOR и YCOR, позволяющие определить и выдать текущие координаты X и Y. Эти функции не могут выдать эти координаты на экран, но могут предоставлять их другим процедурам для обработки.

Если мы хотим определить момент, когда "черепашка" сделает замкнутый контур, то кроме координат нас должно интересовать еще и направление, в котором она смотрит. Этот результат может быть получен с помощью функции HEADING. - она выдает результат от 0 до 360 (можете рассматривать его, как показание стрелки компаса).

Север - 0

Восток - 90

Юг - 180

Запад - 270

Таким образом, поворачивая "черепашку" направо, Вы увеличиваете значение HEADING, а поворачивая ее налево - уменьшаете его. Направление, в котором "смотрит" "черепашка" можно не только измерить, но и задать с помощью команды SETHEADING, после которой должно идти число от 0 до 360.

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

Нам надо зафиксировать координаты "черепашки" в исходной точке и направление ее движения. Для удобства мы можем собрать эти три числа в один список:

MAKE "STATE LPUT HEADING POSITION

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

IF EQUALP :STATE LPUT HEADING POSITION [STOP]

Конечно, не следует делать такую проверку до того, как "черепашка" сделает первый

шаг.

VCOR

360

2 "70 /

I '

0

—____ HERDING 90

|

1

180

^J 127 -► XCDR

Рис. 2 Координаты "черепашки"

Можно использовать процедуры, задающие координаты X и Y и для того, чтобы рисовать на экране графики. Предположим, что у Вас есть список из двенадцати чисел (ежемесячные расходы) и его надо передать в качестве входного параметра в процедуру, которая нарисует на экране график. Пока для простоты изложения мы предположим, что все числа не превосходят 160 и не являются очень малыми, по сравнению с этим числом.

Сначала надо нарисовать пару осей. Начиная из левого нижнего угла из координаты [125 -80] нарисуем горизонтальную линию с двенадцатью вертикальными метками, соответствующими 12 месяцам.

ТО XAXIS PENUP

SETPOS [-125 -80] PENDOWN SETHEADING 90 FORWARD 5

REPEAT 12 [FORWARD 20 SETY -82 SETY -80]

END

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

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

ТО YAXIS

PENUP

SETPOS [120 -85]

PENDOWN

SETY 80

END

Рис. 3 График ежемесячных расходов.

Оси пересекаются в т. [-120 -80], поэтому это и будет исходная точка нашего графика. Просмотрим список, используя переменную COUNT и для каждого значения нарисуем линию, соединяющую предыдущую точку с текущей.

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

TO PLOT

MAKE "COUNT :COUNT + 1

MAKE "XCOR :COUNT*20-120

MAKE "YCOR ITEM :COUNT :VALUELIST-80

SETPOS LIST :XCOR :YCOR

END

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

TO GRAPH :VALUELIST XAXIS YAXIS

SETPOS [-120 -80]

PENDOWN

MAKE "COUNT 0

REPEAT 12[PLOT]

PENUP

SETPOS [-120 -80]

END

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

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

Во-первых, рассмотрим бесконечную кривую, которую называют "снежинкой". Построим равносторонний треугольник. Разделим каждую сторону на три части и на среднем отрезке построим еще по равностороннему треугольнику и т.д. (Рис. 4)

Рис. 4 "Снежинка"

Для рисования треугольника мы поступаем просто - три раза:

FORWARD :LENGTH RIGHT 120

Для того же, чтобы изобразить "снежинку", мы должны заменить процедуру FORWARD процедурой изображения треугольника с размером 1/3 предыдущего.

TO SNOWFLAKE :LENGTH

REPEAT 3[SNOWLINE :LENGTH RIGHT 120]

END

Для того, чтобы нарисовать сторону "снежинки", служит процедура SNOWLINE. "Черепашка" проходит треть текущей стороны, поворачивается влево на 60 градусов, рисует линию длиной 1/3 от предыдущей, поворачивается направо на 120 градусов и т.д.

TO SNOWLINE :LENGTH FORWARD :LENGTH/3 LEFT 60

FORWARD :LENGTH/3 RIGHT 120 FORWARD :LENGTH/3 LEFT 60

FORWARD :LENGTH/3

END

Это создает тот эффект, который нам нужен, но СТОП! Вместо каждой из сторон треугольника мы получили по четыре отрезка. Но ведь на каждом из них должен быть построен свой треугольник. Поэтому всякая команды FORWARD в процедуре SHOWLINE должна быть фактически заменена вызовом самой этой процедуры SHOWLINE.

Очевидно, что с каждым шагом стороны этих треугольников будут все меньше и меньше и их трудно будет рассмотреть.

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

TO SNOWFLAKE :LENGTH :LIMIT

REPEAT 3 [SNOWLINE :LENGTH RIGHT 120]

END

TO SNOWLINE :LENGTH

IF :LENGTH < :LIMIT [FORWARD :LENGTH STOP] SNOWLINE :LENGTH/3 LEFT 60

SNOWLINE :LENGTH/3

RIGHT 120

SNOWLINE :LENGTH/3 LEFT 60

SNOWLINE :LENGTH/3

END

Хорошим демонстрационным примером будет:

PENUP SETY -30 PENDOWN

SNOWFLAKE 100 5

И напоследок кривая, которую называют "драконом". Она рисуется двумя процедурами, которые вызывают друг друга по-очереди. Между вызовами одна процедура обеспечивает поворот направо, а другая - налево. Длина шага в рекуррентной последовательности одна и та же, поэтому здесь нам нужен иной метод для того, чтобы знать, когда остановиться. Можно, например, сделать так, что когда процедура дойдет до определенного уровня глубины рекурсии, то процесс должен прерываться. Для этого введем счетчик, который назовем LEVL. Он начнет свою работу, например со значения 5 и при каждой рекурсии будет убывать на единицу, пока не обнулится.

Вот эти процедуры:

TO LEFTDRAGON :SIZE :LEVL

IF :LEVL = 0 [FORWARD :SIZE STOP] LEFTDRAGON :SIZE :LEVL-1 LEFT 90

RIGHTDRAGON :SIZE :LEVL-1

END

TO RIGHTDRAGON :SIZE :LEVL

IF :LEVL = 0 [FORWARD :SIZE STOP] LEFTDRAGON :SIZE :LEVL-1 RIGHT 90

RIGHTDRAGON :SIZE :LEVL-1

END

He имеет значения, с какой процедуры Вы начнете работу. Рекомендуется начинать с небольших значений SIZE и LEVL. Начинать работу можно, например, так:

CLEARSCREEN PENUP SETH 90 SETX 100 PENDOWN

LEFTDRAGON 2 20

(Продолжение следует)

Стюарт Николс.




СОДЕРЖАНИЕ:


  Оставте Ваш отзыв:

  НИК/ИМЯ
  ПОЧТА (шифруется)
  КОД



Темы: Игры, Программное обеспечение, Пресса, Аппаратное обеспечение, Сеть, Демосцена, Люди, Программирование

Похожие статьи:
Поиск - поиск игр, программ.
Вступление - Оболочка.
Demoparty - nuotrauka'tm details: "Так получилось, что я стал одним из органайзеров этой парти, а потому и отмазываться от части придется мне"
Программирование - синхронизация эффектов в демках на прерываниях.
Comments on "Red Clones" article - Few comments on it.

В этот день...   21 ноября