[an error occurred while processing this directive]
2.3.6 Игра "Угадай название животного".
Игра заключается в следующем: вы загадываете какое-то животное, а компьютер пытается его отгадать. Если компьютер не отгадал, вы подсказываете компьютеру какой вопрос он должен задать, чтобы получить правильный ответ. Компьютер запоминает этот вопрос и название вновь загаданного животного и может использовать это в дальнейшей игре. Базу данных программы, содержащую информацию по зоологии, можно записать на диск с помощью оператора SAVE и вызвать в память с помощью оператора LOAD, таким образом файл данных можно наращивать. После того как в игру поиграли несколько раз, обнаруживаются удивительные вещи! Чтобы вызвать игру необходимо набрать функцию (ANIMAL), которая представляет собой бесконечный цикл:
(DEFUN ANIMAL NIL (LOOP (SAY-HELLO) (SETQ KNOWN-ANIMALS (GUESS KNOWN-ANIMALS)))) (DEFUN SAY-HELLO NIL (PRINT) (LPRI '(THINK OF AN ANIMAL - I WILL GUESS IT)) (PRINT))
Данные и БД животных хранятся под именем переменной KNOWN-ANIMALS, команда LPRI выводит слово в строке LISP, опуская угловые скобки. KNOWN-ANIMALS будет деревом, отличное от использованного в примере древовидной структуры. А на листьях этого дерева будут атомы-названия животных; в противном случае это будут строковые структуры, содержащие следующее:
(а) вопрос; (б) поддерево ДА; (в) поддерево НЕТ.
После того как закончит работу оператор GUESS, компьютер возвращает копию данных дерева, дополнив их информацией, которая была получена во время игры. Для дополнения данных дерева используются функции RPLACA и RPLACD, которые оставляют мало места по сравнению с техническими средствами, использующимися в древовидной структуре:
(DEFUN GUESS KNOWN-ANIMALS) (COND ((ATOM KNOWN-ANIMALS) (I GUESS KNOWN-ANIMALS)) (T (PRINTC (CAR KNOWN-ANIMALS)) (COND ((YESP (READ)) (RPLACA (CDR KNOWN-ANIMALS) (GUESS (CADR KNOWN-ANIMALS)))) (T (RPLACD (CDR KNOWN-ANIMALS) (GUESS (CDR KNOWN-ANIMALS))))) KNOWN-ANIMALS)))
Когда оператор GUESS доходит до листа дерева известного животного, он вызывает подпрограмму I-GUESS, которая сообщает название животного, которое задумал играющий. И если игрок подтверждает предположение компьютера, то на экране появляется сообщение "УРА!", в противном случае компьютер должен "сдаться".
(DEFUN I-GUESS (CREATURE) (LPRI (LIST 'IS 'IT 'A CREATURE)) (COND ((YESP (READ)) (PRINTC 'HURRAH) CREATURE) (T (GIVE-UP CREATURE))))
Структура модуля GIVE-UP - прямолинейная, исключая используемую в нем функцию READLINE. Частое обращение к ней необходимо, чтобы очистить строку с предыдущим названием животного и напечатать новое.
(DEFUN GIVE-UP (I THOUGT (NEW-ANIMAL) (NEW-QUESTION)) (LPRI '(I GIVE UP)) (LPRI '(WHAT WAS IT?)) (SETQ NEW-ANIMAL (READ)) (LPRI '(PLEASE TYPE IN A QUESTION THAT WOULD)) (LPRI (LIST 'DISTINGUISH 'A NEW-ANIMAL 'FROM 'A I-THOUGHT)) (READLINE) (SETQ NEW-QUESTION (READLINE)) (LPRI '(THANK YOU)) (CONS NEW-QUESTION (CONS NEW-ANIMAL I-THOUGHT)))
Итак, работу по ветке YES (или Y) осущствляет функция YESP. Было бы эффективно использовать ORDINAL или EXPLODE для выбора первого символа в ответе пользователя с проверкой на заглавную и прописную букву "Y"....
(DEFUN YESP (A) (OR (EQ A 'YES) (EQ A 'Y)))
Как уже говорилось, LPRI выводит на печать списком атомы, разделенные пробелами без скобок.
(DEFUN LPRI (L) (LOOP (UNTIL (NULL L) (PRINT)) (PRINC (CAR L) BLANK) (SETQ L (CDR L))))
В итоге мы можем присвоить начальное значение переменной KNOWN-ANIMALS. Данный фрагмент был получен при выполнении игры несколько раз и выводе на печать базы данных. Заметьте, что атомы, содержашие пробелы в именах при вводе функции READ, должны печататься со знаком перехода (!) перед побелом. При выполнении игры READLINE берет на себя функцию ввода и эти разделители не нужны.
(SETQ KNOWN-ANIMALS '(DOES! IT! HAVE! A! LONG! NECK? (DOES! IT! LIVE! IN! AFRICA GIRAFFE . SWAN) DOES! IT! HAVE! BIG! EARS? (DOES! IT! HAVE! A! NOSE ELEPHANT . RABBIT) . CROCODILE))
23.7 Программа нахождения пути.
Описанная программа позволяет пользователю создавать базу данных, отражающую расстояние между парами городов. При обращении к данным программа определяет кратчайший путь между любыми заданными пунктами. Используемый здесь метод поиска, является примером поиска методом "чернильного пятна" или "сначала вширь". От исходного пункта программа накапливает информацию о расстоянии и направлении до других пунктов в порядке их удаления от начального. Это можно сравнить с тем, как распространяется чернильное пятно, разлитое в пункте отсчета, и окрашивает постепенно всю сеть городов, к которым оно приближается. Самым коротким путем к месту назначения будет тот путь, который совершит первым достигший его поток.
Чтобы применить метод "чернильного пятна", нам необходимо так составить список путей, которые проходит чернильный поток, чтобы первой обрабатывалась информация о городе, который первым был достигнут. Список такого рода можно рассматривать как приоритетную очередь, и одним из методов представления его является использование структуры данных "куча" (неупорядоченный массив данных).
"Кучи" - это нечто вроде деревьев, используемых в древовидных программах, описанных ранее. Однако, в этом массиве минимальный элемент находится сверху и единственным требованием, предъвяляемым к двум "подкучам", является то, что они должны оставаться приблизительного одинаковые по размеру. Что касается древовидных программ, то модуль-куча, который здесь используется, должен быть написан с помощью функций доступа, чтобы определить подполя в структурах данных LISP, которые образуют эти массивы (кучи). Функция ADD-TO- HEAP сохраняет равными две половинки составляющие массив, периодически переставляя их, и дополняя каждый подмассив поочередно:
(DEFUN NEW-MODE (ITEM LEFT RIGHT) (CONS LEFT RIGHT))) (DEFUN GET-ITEM (HEAP) (CAR NEAP)) (DEFUN LEFT-SUBHEAP (HEAP) (CADR HEAP)) (DEFUN RIGHT-SUBHEAP (HEAP) (CADR HEAP)) (DEFUN ADD-TO-HEAP (ITEM HEAP) (COND ((NULL HEAP) NEW-NODE ITEM NIL NIL)) ((IS-SMALLER ITEM (GET-ITEM HEAP)) (ADD-TO-HEAP (GET-ITEM HEAP) (REPLACE-ITEM HEAP ITEM))) (T (NEW-NODE (GET-ITEM HEAP) (RIGHT-SUBHEAP HEAP) (ADD-TO-HEAP ITEM (LEFT-SUBHEAP HEAP)))) ))
Заметьте, что минимальный элемент множества находится сверху, и поэтому к нему легче всего осуществить доступ. Это напоминает ситуацию со следующим, который зальют чернила, городом.
Как только верхний элемент обработан, он исключается из массива. Это должно происходить таким образом, чтобы обе половинки оставались равными, а верхним становился опять минимальный из оставшихся элемент массива. Программная реализация выглядит довольно сложно, но сложность возрастает медленно, только по мере увеличения массива. На первый взгляд представляется легким удалить самый правый элемент массива, отменив операцию ADD-HEAP следующим образом:
(DEFUN REMOVE-RIGHTMOST (HEAP) (COND ((NULL (RIGHT-SUBHEAP HEAP)) (SETQ RIGHTMOST (GET-ITEM HEAP)) NIL) (T (NEW-NODE (GET-ITEM HEAP) (REMOVE-RIGHTMOST (RIGHT-SUBHEAP HEAP)) (LEFT-SUBHEAP HEAP))) )
Удаленный элемент хранится в рабочей переменной RIGHTMOST. Конечно, нас интересует не самый правый элемент. Мы хотим удалить самый верхний элемент "кучи". Если пренебречь необходимостью хранить малые элементы массива наверху кучи, мы можем достичь этого, поменяв верхний элемент с полученной копией самого правого. Потом переместить минимальный элемент наверх измененного массива:
(DEFUN REMOVE-TOP-ITEM (HEAP (RIGHTMOST)) (COND ((NULL (RIGHT-SUBHEAP HEAP)) NIL) (T (SETQ HEAP (REMOVE-RIGHTMOST HEAP)) (RESTORE-HEAP (REPLACE-ITEM HEAP RIGHTMOST))))) (DEFUN REPLACE-ITEM (HEAP ITEM) (CONS ITEM (CDR HEAP)))
Функциия RESTORE-HEAP работает, выполняя тройное сравнение между самым верхним элементом кучи и верхними элементами левой и правой подкучки. Минимальный из этих трех станет верхним элементом новой кучи. В случае, если верхний элемент кучи минимальный, никаких перестановок не надо делать. В противном случае, три элемента можно поменять местами, оставляя один из элементов подкучи для дальнейшей обработки:
(DEFUN RESTORE-HEAP (HEAP) (COND ((TOP-ITEM-IS-SMALLEST HEAP) HEAP) ((LEFT-ITEM-IS-SMALLEST HEAP) (PERCOLATE-LEFT HEAP)) (T (PERCOLATE-RIGHT HEAP))))
Общая идея тройного сравнения очень проста, однако может привести к программе невероятной длины. Это случается иногда потому, что такие сравнения необходимы в особых случаях, когда одна или обе подкучи пустые. В данной ситуации сложность уменьшается за счет того, что все функции имеют такую программную реализацию, что если куча содержит пустую подкучу, то она является левой:
(DEFUN TOP-ITEM-IS-SMALLEST (HEAP) (OR (NULL (RIGHT-SUBHEAP HEAP)) (AND (IS-SMALLER (GET-ITEM HEAP) (GET-ITEM (RIGHT-SUBHEAP HEAP))) (OR (NULL (LEFT-SUBHEAP HEAP)) (IS-SMALLER (GET-ITEM HEAP) (GET-ITEM (LEFT-SUBHEAP HEAP)))) ))) (DEFUN LEFT-ITEM-IS-SMALLEST (HEAP) (AND (LEFT-SUBHEAP HEAP) (IS-SMALLER (GET-ITEM (LEFT-SUBHAP HEAP)) (GET-ITEM HEAP)) (IS-SMALLER (GET-ITEM (LEFT-SUBHEAP HEAP)) (GET-ITEM (RIGHT-SUBHEAP HEAP)))) )
Используя функцию (IS-SMALLER) для общего сравнения, мы избавились от необходимости определять точный формат для элемента, хранящегося в массиве. Теперь можно ввести функции RECOLATE-RIGHT и RECOLATE-LEFT. Каждая из них просто перестраивает массив так, чтобы верхним оказался минимальный элемент кучи:
(DEFUN RECOLATE-RIGHT (HEAP) (NEW-NODE (GET-ITEM (RIGHT-SUBHEAP HEAP)) (LEFT-SUBHEAP HEAP) (RESTORE-HEAP (REPLACE-ITEM (RIGHT-SUBHEAP HEAP) (GET-ITEM HEAP)))) ) (DEFUN RECOLATE-LEFT (HEAP) (NEW-NODE (GET-ITEM (LEFT-SUBHEAP HEAP)) (RESTORE-HEAP (REPLACE-ITEM (LEFT-SUBHEAP HEAP) (GET-ITEM HEAP))) (RIGHT-SUBHEAP HEAP)))
Функции перестановки элементов множества, описанные выше, могут быть легко использованы как основные для программы общей сортировки. Вероятно, эта программа работает медленнее, чем древовидная сортиировка, потому что процесс уничтожение массива требует больших затрат, чем разветвленное пересечение, которое завершает древовидную сортировку. Однако, для типичного ввода списков эти два метода будут различаться по скорости, независимо от количества хранящихся элементов. Более того, оба метода в худшем случае, будут постоянным фактором, более медленным, чем любая программа общего назначения, в основе которой - попарное сравнение элементов. Структура-куча имеет одно преимущество перед разветвляющей и поэтому иногда может быть предпочтительней: затраченное время не зависит от начального порядка элементов сортировки.Разветвляющая, напротив, не подходит для определенного ввода, в частности, для такого, когда список вводится в правильной убывающей или возрастающей последовательности.
Устроив дополнение массива так, что мы можем установить последовательность событий, программа нахождения пути становится довольно простой. Она выстраивает очередь событий из городов близлежащих к начальному в зависимости от их расстояния до него. Главный цикл программы выделяет ближайший город из этой очереди, и если вы его еще не посетили (отмеченный как начальный через PUT и GET), можно записать информацию о маршруте. Более того, все города непосредственно связанные с вновь введенным, ставятся в очередь событий. Поиск обычно завершается когда достигнут пункт назначения. Если очередь событий становится пустой до завершения поиска, программа сигнализирует, что не существует пути от начального пункта до пункта назначения:
(DEFUN FIND-ROUTE (SOURCE DESTINATION (PRIORITY-QUEUE) (SITY) (DISTANCE) (VIA) (W)) (SETQ PRIORITY-QUEUE (ADD-TO-QUEUE SOURCE 0 NIL)) (LOOP (UNTIL (GET DESTINATION SOURCE) (REPORT-ROUTE SOURCE DESTINATION) (UNTIL (NULL PRIORITY-QUEUE) (REPORT-FAILURE)) (SETQ W (GET-ITEM PRIORITY-QUEUE)) (SETQ CITY (CAR W)) (SETQ DISTANCE (CADR W) (SETQ VIA (CADR W)) (SETQ PRIORITY-QUEUE (REMOVE-TOP-ITEM PRIORITY-QUEUE)) (COND ((NULL (GET SITY SOURCE)) (PUT CITY SOURCE (CONS DISTANCE VIA)) (SETQ PRIORITY-QUEUE (ADD-TO-QUEUE CITY DISTANCE PRIORITY-QUEUE)))) ))
Только на этой стадии становится необходимым решить как записать базу данных, которая определяет близлежащие города и расстояние между ними. В выбранной здесь схеме каждый пункт имеет характеристику NEIGHBOURS (соседи) и хранящийся под этой характеристикой список, где содержится информация о близлежащих городах и растоянии до них. Полезно организовать функцию, которая будет создавать такие характеристики:
(DEFUN NPUT (SOURCE NEIGHBOURS) (PUT SOURCE 'NEIGHBOURS NEIGHBOURS))
Примеры списков соседних городов будут приведены ниже. Программа, которая просматривает список соседних городов и дополняет записи в очередь событий, приведена ниже:
(DEFUN ADD-TO-QUEUE (CITY BASE-DISTANCE QUEUE (NEIGHBOURS)) (SETQ NEIGHBOURS (GET CITY 'NEIGHBOURS)) (LOOP (WHILE NEIGHBOURS QUEUE) (SETQ QUEUE (ADD-TO-HEAP (LIST (CAAR NEIGHBOURS) (PLUS BASE-DISTANCE (CDAR NEIGHBOURS)) CITY) QUEUE)) (SETQ NEIGHBOURS (CDR NEIGHBOURS))))
Программа осуществляет запись в список очереди, где дорога через город является финишной прямой путешествия всего пути - от начального до пункта назначения. Возвращаясь к программе "куча", мы видим, что этот формат обращается к оператору сравнения следующего вида:
(DEFUN IS-SMALLER (A B) (LESSP (CADR B)))
Не считая вывода и печати результатов, это конец программы. Т.к поиск по методу "чернильного пятна" ведется от начального пункта, и каждый посещенный пункт получил указатель (VIA) города на пути до этого города, конечный путь должен быть проделан в обратной последовательности - от конечного до начального. Изменив поиск в обратном направлении, можно устранить это маленькое неудобство. При большой сети поиска было бы целесообразнее двигаться одновременно от начального и конечного пункта (представьте разлитые красные - для одного и голубые - для другого чернила) и остановиться когда рассматриваемые области пересекуться (станут лиловыми?):
(DEFUN REPORT-ROUTE (SOURCE DESTINATION (VIA)) (PRINT 'DISTANCE= (CAR (GET DESTINATION SOURCE))) (LOOP (SETQ VIA (CONS DESTINATIN VIA)) (UNTIL (EQ SOURCE DESTINATION) (PRINT 'VIA: VIA) (PRINT (SETQ DESTINATION (CDR (GET DESTINATION SOURCE))))) (DEFUN REPORT-FAILURE () '(NO ROUTE EXISTS))
Программа, приведенная выше, разработана для эффективной работы с большой сетью, хотя приведенный пример ее использования включает 6 городов. Не претендуя на географическую точность, база данных составлена с использованием последовательности вызовов функции NPUT:
(NPUT 'CAMBRIDGE '((WEDFORD .15) (ROYSTON .20))) (NPUT 'ROYSTON '((CAMBRIDGE .20) (WATFORD .30) (LONDON .50))) (NPUT 'LONDON '((ROYSTON .20) (WATFORD . 25) (OXFORD .50))) (NPUT 'BEDFORD '((CAMBRIDGE .15) (WATFORD .30))) (NPUT 'WATFORD '((BEDFORD .30) (ROYSTON .30) (LONDON .25) (OXFORD .40))) (NPUT 'OXFORD '((ROYSTON . 50) (WATFORD . 25) (LONDON .50)))
Пути можно будет потом определить вызвав FIND-ROUTE:
(FIND-ROUTE 'CAMBRIDGE 'OXFORD) DISTANCE=85 VIA:(CAMBRIDGE WEDFORD WATFORD OXFORD)
Программа оставляет различные характеристики идентифиикаторов, и все они должны быть уничтожены между обращениями к функции FIND-ROUTE, если меняется любая информация о расстоянии, установленная NPUT. Нетрудно разработать функцию просмотра всех атомов в функции (OBLIST), которая будет стирать списки характеристик.
23.8 Редактор LISP
Обращение к редактору LISP происходит по имени EDIT. Функция EDIT не содержит аргумента (т.е. используется форма записи (EDIT SED), а не (EDIT 'SED)). EDIT оформляет печать определения функции, а затем использует функцию SET, чтобы заменить это определение на то, которое получится в результате работы SED:
(DEFUN EDIT L (SPRINT (EVAL (CAR L))) (PRINTC) (SET (CAR L) (SED (EVAL (CAR L)))))
SED определяет команды, к которым обращается редактор. Это символы, полученные при обращении к функции GETCHAR. Наиболее важные из них A, D и В. A и D вызывают повторное действие SED, вводят в редактор соответственно CAR и CDR предыдущего выражения. В вызывает возврат на начало выражения. Если А команда или D команда удаляет SED из конца списка, то вводится символ *, а команда игнорируется:
(DEFUN SED (A (Q)) (LOOP (SETQ Q (PRINC (GETCHAR)) (UNTIL (EQ Q 'B) A) (SETQ A (COND ((EQ Q 'R) (PRINTC) (READ)) ((EQ Q CR) (SPRINT A) (PRINTC) A) ((EQ Q 'C) (PRINTC) (CONS (READ) A)) ((ATOM A) (PRINC '*) A) ((EQ Q 'D) (CONS (CAR A) (SED (CDR A)))) ((EQ Q 'A) (CONS (SED (CAR A)) (CDR A))) ((EQ Q 'X) (CDR A)) (T (PRINC '?) A)))))
Команда R позволяет заменить выражение, которое рассматривает SED в данный момент. C и X вставляют и удаляют строки в списках. SED печатает предварительно текущее выражение в конце каждой строки на запрос редактора. Существует много дополнительных команд, которые могли бы войти в структуру редактора: команды поиска и выполнения общих изменений для перемещения вверх и вниз по дереву более крупными шагами, чем это позволяют существующие команды. Этот основной редактор можно использовать как таковой, а также расширенный с использованием дополнительных необходимых команд.
23.9 Вывод графики в языке LISP.
Если картинки могут быть представлены строковыми структурами, то LISP можно использовать как язык, с помощью которого можно составлять графические программы. В ОС ОНИКС все виды графического изображения можно достичь путем передачи последовательности контрольных символов драйверу дисплея. Последовательно используя фугкцию VDU, все графические возможности микрокомпьютера могут быть реализованы в рамках языка LISP. В демонстрационной программе графики, описанной здесь, пользователь пишет функции языка LISP, которые строят структуры данных, описывающие картинки, а специальная программа печати отображает их в цвете в нижней части экрана.
Чтобы использовать эту программу, необходимо войти в графический режим LISP. Для ввода экранной карты в память необходим компьютер памятью 96К, и до тех пор, пока не используется модуль "СПРУТ", доступными являются графические режимы 3 и 4. Заметьте, что эта программа меняет графические режимы путем пересылки контрольных кодов драйверу дисплея; в нормальных условиях лучше использовать функцию MODE, но в данном случае это неудобно, т.к. происходит задержка времени.
Первые две приведенные функции просто вызывают функцию VDU для пересылки нескольких байтов драйверу дисплея микрокомпьютера. SET-GRAPHICS устанавливает текстовое окно, ограничивающее вывод символа в верхние несколько строк экрана и устанавливает графический режим 3 с 8-ми цветным изображением. END-GRAPHICS устанавливает режим 4, очищая экран с 40 символами в строке:
(DEFUN SET-GRAPHICS () (VDU 22 5 28 0 5 19 0)) (DEFUN END-GRAPHICS () (VDU 22 4))
Цикл чтение-вычисление-печать в графике называется GSUPER. Он использует ERRORSET для предостережения от ошибок всех видов. Единственное существенное различие между GSUPER и циклом с нормальным завершением в том, что GSUPER вызывает функцию печати графики GPRINT (которую еще нужно определить), чтобы показать результаты вычислений:
(DEFUN GSUPER ((U)) (SET-GRAPHICS) (LOOP (LOOP (LOOP (PRINT 'Eval:) (SETQ U (ERRORSET (READ))) (WHILE (ATOM U))) (SETQ U 'ERRORSET (EVAL (CAR U)))) (WHILE (ATOM U))) (UNTIL (EQ (SETQ U (CAR U)) 'FIN)) (ERRORSET (GPRINT U))) (END-GRAPHICS))
Условные выражения (WHILE (ATOM U)) во внутренних циклах отражают тот факт, что функция ERRORSET возвращает результат атома если не удаются защищенные вычисления, в противном случае результат вложенных вычислений может быть восстановлен как CAR от значения, полученного при работе ERRORSET.
GPRINT должен перевести списковые структуры в картинки (графические). В данной программе любая строка, имеющая атом PIC в качестве первого элемента, будет представлять графику:
(DEFUN PICP (X) (AND (NOT (ATOM X)) (EQ (CAR X) 'PIC))) (DEFUN GPRINT (X) (COND ((PICP X) (CLEAR-SCREEN) (DRAW (CDR X))) (T (PRINT X)))) (DEFUN CLEAR-SCREEN () (VDU 18 0 7 18 128 16 24 4 0 2 0 2))
Из руководства пользователю по программированию на Бейсике в среде ОС ОНИКС вы узнаете, что контрольные коды, данные в CLEAR-SCREEN, устанавливают драйвер VDU в режим белого текста на черном фоне с очищенным экраном и графическим курсором в центре экрана. Следующей функцией, которая будет разработана в языке LISP, будет функция DRAW, она будет прослеживать структуры данных, выделяя контрольные коды VDU, чтобы сохранить свое имя. Программная реализация функции DRAW очень характерна для языка LISP:
(DEFUN DRAW (X) (COND ((ATOM X) NIL) (T ( (GET (CAR X) 'DRAW) (CDR X)) )))
Атомы рассматриваются как пустые картинки и не выводятся на экран. CAR модули могут быть картинками и DRAW восстанавливает любую функцию из списка характеристик такого типа, используя оператор GET. Эту функцию можно применить и для остальных данных изображения. Лишние пробелы, в примере выше, использованы для того, чтобы показать, что в последнем условном блоке выражение (GET (CAR X) 'DRAW) выполняет роль функции. Типичным аргументом DRAW будет выражение типа (BOX 100 200) - программа списка характеристик BOX следующая:
(PUT 'BOX 'DRAW '(LAMBDA (X (Y)) (SETQ Y (CADR X)) (SETQ X (CAR X)) (PLOT 0 (MINUS (QUOTIENT X 2)) (MINUS (QUOTIENT Y 2))) (PLOT 1 X 0) (PLOT 1 0 Y) (PLOT 1 (MINUS X) 0) (PLOT 1 0 (MINUS Y)) (PLOT 0 ) QUOTIENT X 2) (QUOTIENT Y 2))))
Выражение-лямбда вычисляется при Х в пределах (100 200), и первое, что необходимо сделать,- это разбить этот список на 2 составляющих числа и хранить их в Х и Y. Последовательность обращений к PLOT посылает необходимые байты блоку управления дисплеем, чтобы изобразить прямоугольник нужного размера с центром в позиции, близкой к позиции графического курсора. Основное руководство по ВВС компьютерам снова проконсультирует вас о подробностях использования контрольных кодов. Это создает функция PLOT:
(DEFUN PLOT (N X Y) (VDU 25 N (REMAINDER (SETQ X (PLUS X 16384)) 256) (DIFFERENCE (QUOTIENT X 256) 64) (REMAINDER (SETQ Y (PLUS Y 16384)) 256) (DIFFERENCE (QUOTIENT Y 256) 64)))
Прибавление числа 16384 (= 2^14) необходимо, чтобы убедиться, что полученные остатки имеют нужный знак. Рассмотрим другие свойства DRAW для отличных от прямоугольников объектов, и для изменения и комбинирования изображений. Вот несколько из них:
(PUT 'ADD 'DRAW '(LAMBDA (A) (LOOP (WHILE A) (DRAW (CAR A)) (SETQ A (CDR A))))) (PUT 'SHIFT 'DRAW '(LAMBDA (A) (PLOT 0 (CADR A) (CADR A)) (DRAW (CAR A)) (PLOT 0 (MINUS (CADR A)) (MINUS (CADR A))))) (PUT 'CIRCLE 'DRAW '(LAMBDA (X (W) (Z)) (SETQ X (CAR X)) (SETQ W (QUOTIENT (TIMES X 7) 10)) (SETQ Z (DIFFERENCE X W)) (PLOT 0 X 0) (PLOT 1 (MINUS Z) W) (PLOT 1 (MINUS W) Z (PLOT 1 (MINUS W) (MINUS Z)) (PLOT 1 (MINUS Z) (MINUS W) (PLOT 1 Z (MINUS W)) (PLOT 1 W (MINUS Z)) (PLOT 1 W Z) (PLOT 1 Z W) (PLOT 0 (MINUS X) 0))) (PUT 'COLOUR 'DRAW '(LAMBDA (X) (VDU 18 0 (CDR X)) (DRAW (CAR X))))
.сс
[an error occurred while processing this directive]