Когда мне было 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