Когда мне было 17 лет, Terrapin опубликовала мой первый коммерческий код на своем диске с утилитами C64 Logo: программу Logo Adventure, простую неграфическую игру, которая продемонстрировала возможности обработки списков и функционального программирования Logo.
Обожаю Terrapin Logo! Мне удалось избавиться от необходимости писать синтаксический анализатор, просто используя цикл чтения-оценки-печати верхнего уровня Logo в качестве синтаксического анализатора и определив такие слова логотипа, как LOOK, N , S, E, W, TAKE, EXAMINE и т. д. Итак действительно легко обмануть, исследуя и изменяя состояние мира, но это поможет вам изучить Logo!
Слово ПРИКЛЮЧЕНИЕ запускает игру, переключаясь в текстовый режим (NODRAW), распечатывая введение, настраивая все структуры данных, вызывая INIT, и распечатать описание текущей комнаты, вызвав СМОТРЕТЬ. Сначала он устанавливает [ПРИКЛЮЧЕНИЕ] как функцию запуска, поэтому игра запускается автоматически при ее загрузке.
TO ADVENTURE NODRAW PR [WELCOME TO LOGO ADVENTURE] PR [WRITTEN BY DON HOPKINS] PR [] PR [TYPE HELP FOR HELP] PR [] INIT LOOK END MAKE "STARTUP [ADVENTURE]
Хорошим местом для начала нашего изучения кода будет слово HELP, которое просто выводит кучу полезных советов и желает вам удачи, что должно дать вам представление о том, что будет дальше:
TO HELP PR [TO MOVE, TYPE] PR [N, S, E, W] PR [FOR NORTH, SOUTH, EAST, WEST] PR [] PR [TYPE LOOK TO SEE WHAT ROOM YOU] PR [ARE IN. YOU CAN GET AND DROP ITEMS.] PR [INVENT SHOWS YOUR INVENTORY.] PR [THE WORD "IT" MEANS THE LAST THING YOU] PR [REFERRED TO.] PR [] PR [THERE ARE SOME SPECIAL THINGS YOU CAN] PR [DO, LIKE SAYING EXAMINE SOMETHING.] PR [] PR [TYPE SCORE TO SEE YOUR SCORE, AND] PR [DONE TO QUIT.] PR [GOOD LUCK!] CMD END
Слово INIT формирует нашу модель вселенной Adventure, которая очень проста: некоторые комнаты, некоторые предметы и игрок.
TO INIT MAKE "ITEMS [[1 0 SWORD] [1 0 HATCHET] [1 0 SHIELD] [2 100 GOLD] [2 100 DIAMOND] [2 50 AMULET] [3 0 SCREWDRIVER] [4 0 MACHINE] [0 100 WAND] [5 200 CROWN]] MAKE "RMOVES [[0 2 3 0] [0 0 4 1] [1 4 0 0] [2 0 0 3] [0 0 0 0]] MAKE "RNAMES [[YOU ARE IN THE WEAPON SHOP.] [THIS IS THE VAULT.] [THIS ROOM IS THE TOOLSHED.] [THIS IS THE ALTAR ROOM.] [YOU ARE IN A SECRET INCANTING ROOM.]] MAKE "RNUM 1 INITITEMS :ITEMS 1 END
Комнаты пронумерованы от 1 (для соответствия индексам списка логотипов на основе 1). Информация о каждой комнате хранится в списках, индексированных по номеру комнаты.
Список RNAMES - это просто список названий комнат, которые представляют собой списки слов, описывающих комнату. Список УДАЛЕНИЯ содержит список из четырех номеров для каждой комнаты. Эти числа обозначают номер комнаты, в которую вы можете двигаться в любом направлении (в порядке [север, восток, юг, запад]), или 0, если вы не можете двигаться в этом направлении.
Таким образом можно определить односторонние или кривые двери, но если вам нужна обычная двусторонняя дверь, каждая комната должна указывать вперед и назад друг к другу в противоположных направлениях. Но если вы действительно хотите создать извилистый лабиринт из коридоров, тогда выбейте себя! Но, пожалуйста, дайте своим игрокам много вещей, которые они могут оставить, чтобы они могли нанести на карту наши лабиринты.
Список ЭЛЕМЕНТЫ содержит набор списков, описывающих номер комнаты, оценку (число) и название каждого элемента (строку). Первым в списке идет номер комнаты, в которой находится предмет, или -1, если предмет находится в инвентаре игрока, или 0, если нигде нет. Вторая вещь в списке - это оценка предмета: сколько очков вы получаете за наличие этого предмета в вашем инвентаре. Третье в списке - название предмета.
Самая важная, но самая простая часть модели - это номер комнаты игрока, RNUM. Слова N, S, E и W перемещаются из комнаты в комнату, изменяя номер комнаты игрока, звоня МОВЕДИР (и взмахом жезла переносит вас в секретную комнату колдовства, волшебным образом изменяя RNUM).
Теоретически, если бы номер комнаты был 0, игрока бы вообще нигде не было, а если бы было -1, игрок бы носил себя в своем собственном инвентаре! Но не волнуйтесь: этого никогда не произойдет, если вы не примените магию или не взломаете систему, что довольно просто, поскольку система является интерпретатором логотипа верхнего уровня.
Наконец, INIT вызывает INITITEMS, передавая ему список ЭЛЕМЕНТОВ как : I и номер 1 как : F. Это рекурсивно определяет набор магических функций, относящихся к каждому из элементов по имени, которые также запоминают последний элемент, который вы упоминали как местоимение IT.
Эти магические функции выполняют две задачи: мы можем ссылаться на элементы по их имени, не вводя дополнительных кавычек (путем определения функции, названной для каждого элемента), и он автоматически запоминает последний элемент, на который мы ссылались в глобальной переменной IT , вызвав слово SETIT.
TO INITITEMS :I :F IF :I = [] STOP TEST :F = 1 IFT DEFINE LAST FIRST :I LPUT LPUT WORD "" LAST FIRST :I [OP SETIT] [[]] IFF DEFINE LAST FIRST :I [] INITITEMS BF :I :F END TO SETIT :THING MAKE "IT :THING OP :THING END
Теперь мы по-настоящему занимаемся обработкой списков и определением функций! Параметр : I - это список элементов для рекурсии, а параметр : F определяет, следует ли инициализировать элементы (1) или очистить их (0). Выражение ПЕРВЫЙ: I - это текущий элемент, а ПОСЛЕДНИЙ ПЕРВЫЙ: I - это имя текущего элемента.
Первое, что обычно должна делать рекурсивная функция, - это проверять, выполнена ли она. Итак, IF: I = [] STOP означает остановку, если мы находимся в конце списка элементов.
Следующее, что он делает, это проверяет, равен ли параметр : F 1 или нет. Если это правда (IFT), он определяет магическую функцию, а если нет (IFF), он очищает это определение функции.
Наконец, INITITEMS выполняет рекурсию для обработки остальной части списка, BF: I, что означает (не хихикайте) но сначала, то есть: все элементы : I, но первый.
Чтобы объяснить, что делают магические функции, я приведу пример: когда элемент называется МЕЧ, он будет определять функцию с именем МЕЧ без параметров, тело которой - [OP SETIT МЕЧ], который выводит результат вызова SETIT с параметром «МЕЧ. Слово SETIT устанавливает глобальную переменную IT в МЕЧ и возвращает МЕЧ. Итак, мы можем сказать ПОЛУЧИТЬ МЕЧ, а затем сказать БРОСИТЕ.
Полезно знать определение LPUT, которое принимает два параметра вещь и список и возвращает копию списка с добавленным в конце словом вещь. А также WORD, который берет две вещи и объединяет их в слово, которое в данном случае просто используется для преобразования строки в слово (например, символ LISP). А DEFINE принимает два аргумента: имя функции и список. Первый элемент списка - это список параметров (в нашем случае пустой список). Последующие элементы - это выражения списка для оценки (в нашем случае [OP SETIT «SWORD]).
Давайте распакуем эти выражения в предложении IFT, вставив отступы, скобки и комментарии, чтобы он выглядел как LISP (поскольку Logo - это, по сути, LISP без скобок, синтаксический анализатор обязательно знает количество параметров каждой функции. принимает) и ссылку на документацию по функциям логотипа.
(IFT (DEFINE (LAST (FIRST :I)) ; function name to define is the item name (LPUT ; function body is a list of lists (LPUT ; expression is a list like [OP SETIT "SWORD] (WORD ; concatenate the two parameters to get word "SWORD "" ; this just converts the string to a word (LAST (FIRST :I))) ; the word is the item name [OP SETIT]) ; output result of SETIT with item name appended [[]]))) ; empty function with zero parameters to append to
Фу! Но если вам кажется, что это сложно понять, взгляните на FORTH-код, который я писал в то время!
Наконец, если параметр : F для INITITEMS не равен 1, то выполняется предложение IFF, которое удаляет определение функции, путем перехода ОПРЕДЕЛИТЬ ПОСЛЕДНИЙ ПЕРВЫЙ: Я []. По-видимому, этого на самом деле никогда не называют, но вот и все.
Последний хитрый трюк для интеграции игры с интерпретатором верхнего уровня логотипа - это слово CMD, которое выводит запрос «КОМАНДА» и переходит к логотипу TOPLEVEL для чтения и интерпретировать команду от игрока. Каждое слово Adventure может, наконец, вызвать CMD в конце, чтобы показать игроку подсказку.
TO CMD PR [] PRINT1 "COMMAND TOPLEVEL END
Мой кофе заканчивается, поэтому я собираюсь сделать перерыв, чтобы кое-что объяснить, но вы можете прочитать остальную часть программы здесь:
TO N MOVEDIR 1 END TO E MOVEDIR 2 END TO S MOVEDIR 3 END TO W MOVEDIR 4 END TO MOVEDIR :DIR MAKE "TRYMOVE ITEM :DIR ITEM :RNUM :RMOVES TEST :TRYMOVE = 0 IFT PR [YOU CAN'T GO THAT WAY.] IFT CMD PR "OK. MAKE "RNUM :TRYMOVE LOOK END TO INVENT PITEMS - 1 CMD END TO LOOK PR ITEM :RNUM :RNAMES PITEMS :RNUM CMD END TO PITEMS :LOC PITEMS2 :LOC :ITEMS END TO PITEMS2 :LOC :I IF :I = [] STOP IF FIRST FIRST :I = :LOC PRINT LAST FIRST :I PITEMS2 :LOC BF :I END TO IT OP :IT END TO EVERYTHING OP "EVERYTHING END TO GET :ITEM TEST :ITEM = "EVERYTHING IFT GETALLITEMS :ITEMS IF IHAVE? :ITEM ( PR [YOU ALREADY HAVE] PERIOD :ITEM ) CMD IF NOT HERE? :ITEM SEENO :ITEM PUTITEM :ITEM ( - 1 ) PR SE :ITEM "TAKEN. CMD END TO TAKE :THING GET :THING END TO GETALL GETALLITEMS :ITEMS END TO GETALLITEMS :I IF :I = [] CMD TEST :RNUM = ITEMLOC LAST FIRST :I IFT PUTITEM LAST FIRST :I ( - 1 ) IFT PR SE LAST FIRST :I "TAKEN. GETALLITEMS BF :I END TO DROP :ITEM TEST :ITEM = "EVERYTHING IFT DROPALLITEMS :ITEMS IF NOT IHAVE? :ITEM PR SE [YOU'RE NOT CARRYING THE] WORD :ITEM "! CMD PUTITEM :ITEM :RNUM PR SE :ITEM "DROPPED. CMD END TO DROPALL DROPALLITEMS :ITEMS END TO DROPALLITEMS :I IF :I = [] CMD TEST ITEMLOC LAST FIRST :I = ( - 1 ) IFT PUTITEM LAST FIRST :I :RNUM IFT PR SE LAST FIRST :I "DROPPED. DROPALLITEMS BF :I END TO HERE? :ITEM LOCAL "LOC MAKE "LOC ITEMLOC :ITEM OP ANYOF - 1 = :LOC :RNUM = :LOC END TO ITEMLOC :ITEM OP ITEMLOC2 :ITEM :ITEMS END TO ITEMLOC2 :ITEM :I IF :I = [] OP 0 IF LAST FIRST :I = :ITEM OP FIRST FIRST :I OP ITEMLOC2 :ITEM BF :I END TO PUTITEM :ITEM :LOC MAKE "ITEMS PUTITEM2 :ITEM :LOC :ITEMS END TO PUTITEM2 :ITEM :LOC :LIST IF :LIST = [] OP [] IF LAST FIRST :LIST = :ITEM OP FPUT FPUT :LOC BF FIRST :LIST BF :LIST OP FPUT FIRST :LIST PUTITEM2 :ITEM :LOC BF :LIST END TO SEENO :I PR SE [I SEE NO] SE :I "HERE! CMD END TO IHAVE? :ITEM OP - 1 = ITEMLOC :ITEM END TO PERIOD :WORD OP WORD :WORD ". END TO WAVE :ITEM IF NOT IHAVE? :ITEM PR SE [YOU ARE HOLDING NO] PERIOD :ITEM CMD IF NOT :ITEM = "WAND NOTHING IF ALLOF NOT :RNUM = 4 NOT :RNUM = 5 PR [NOTHING HAPPENS.] CMD PR [POOF! THE SCENE CHANGES!] IF :RNUM = 4 MAKE "RNUM 5 ELSE MAKE "RNUM 4 LOOK END TO FIX :ITEM IF IHAVE? :ITEM PR [YOU HAVE TO DROP IT TO FIX IT!] CMD IF NOT HERE? :ITEM SEENO :ITEM IF NOT :ITEM = "MACHINE PR [YOU CAN'T FIX THAT!] CMD IF NOT ITEMLOC "WAND = 0 PR [THE MACHINE IS NOT BROKEN!] CMD IF NOT IHAVE? "SCREWDRIVER PR [YOU DON'T HAVE THE PROPPER TOOLS TO] PR [FIX IT] CMD PR [YOU FIX THE MACHINE WITH YOUR TRUSTY] PR [SCREWDRIVER. UPON BEING FIXED, THE] PR [MACHINE STARTS UP AND PRODUCES A WAND!] PUTITEM "WAND 4 CMD END TO EXAMINE :ITEM IF NOT HERE? :ITEM ( PR [I SEE NO] :ITEM [HERE!] ) CMD IF :ITEM = "WAND PR [IT BEARS A FADED INSCRIPTION:] PR ["WAVE ME AND YOU'LL BE GLAD."] CMD IF NOT :ITEM = "MACHINE PR SE [I SEE NOTHING SPECIAL ABOUT THE] PERIOD :ITEM CMD IF NOT 0 = ITEMLOC "WAND PR [IT SEEMS TO BEAR THE MARKS OF A HASTY] PR [REPAIR JOB.] CMD PR [IT IS BROKEN! YOU COULD FIX IT WITH] PR [THE RIGHT TOOL.] CMD END TO SCORE PR ( SE [YOUR SCORE IS] SCORE2 :ITEMS [POINTS.] ) CMD END TO SCORE2 :LIST IF :LIST = [] OP 0 IF NOT FIRST FIRST :LIST = - 1 OP SCORE2 BF :LIST OP ( ITEM 2 FIRST :LIST ) + SCORE2 BF :LIST END TO NOTHING PR [NOTHING HAPPENS.] CMD END TO GAME OP "ADVSAVE END TO DONE IF NOT :RNUM = 5 NOTHING LOCAL "SCORE MAKE "SCORE SCORE2 :ITEMS IF :SCORE = 0 NOTHING PR SE [YOUR SCORE IS] :SCORE IF :SCORE = 550 PR [PERFECT!] ELSE PRINT [THERE'S MORE TREASURE, THOUGH.] DONE END