суббота, 3 марта 2012 г.

Давайте сделаем рогалик. Глава 13: Инвентарь персонажа

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

character.bi
'Определение типа данных характеристик персонажа.
 Type characterinfo 
   cname As String * 35 'Имя персонажа. 
   stratt(3) As Integer 'Сила (0), бонус силы (1), продолжительность действия бонуса в ходах (2)
   staatt(3) As Integer 'Выносливость
   dexatt(3) As Integer 'Ловкость
   aglatt(3) As Integer 'Проворство
   intatt(3) As Integer 'Интеллект
   currhp As Integer    'Текущее здоровье
   maxhp As Integer     'Максимальное здоровье
   currmana As Integer  'Текущая мана
   maxmana As Integer   'Максимальная мана
   ucfsk(3) As Integer  'Рукопашный бой
   acfsk(3) As Integer  'Оружие ближнего боя
   pcfsk(3) As Integer  'Дистанционное оружие
   mcfsk(3) As Integer  'Магическая атака
   cdfsk(3) As Integer  'Защита
   mdfsk(3) As Integer  'Магическая защита
   currxp As Integer    'Текущий, расходуемый опыт. 
   totxp As Integer     'Общая сумма опыта, за время жизни персонажа.
   currgold As Integer  'Текущее количество золота. 
   totgold As Integer   'Общая сумма золота за время жизни персонажа. 
   ploc As mcoord       'Ткущие x и y координаты персонажа.
   cinv(97 To 122) As invtype 'Инвентарь персонажа для индексации использует значения asii кодов.
 End Type

Последнее определение в описании персонажа, это наша коллекция предметов инвентаря. Обратите внимание на странный диапазон индексов. Это ASCII коды символов, заканчивающиеся буквой z. Персонаж будет иметь 26 слотов для предметов инвентаря, в дополнение к тем предмета, которые на нем одеты и которые он держит в руках. Так как индекс массива инвентаря у нас является кодом символов, то для выбора какого либо предмета нам необходимо только получить код символа клавиши клавиатуры, которую нажал пользователь. Здесь мы воспользуемся гибкостью языка FreeBasic и большую часть работы по выбору предметов из инвентаря за нас выполнить компилятор. Многие программисты не пользуются гибкостью языка, и, в конечном итоге, получают громоздкий, трудно поддерживаемый, код.

Что нам нужно сделать сейчас, это позволить игроку поднять предмет, находящийся на карте. Для этого мы добавляем действие «Поднять», если игрок нажимает клавишу «g».

dod.bas
'Поднять предмет с пола и положить в инвентарь персонажа.
 If ckey = "g" Then
   'Inventory type.
   Dim inv As invtype
   'Если под персонажем есть предмет.
   If level.HasItem(pchar.Locx, pchar.Locy) = TRUE Then
     'проверить на залото. Добавляем золото и сколько всего было золота.
     Dim iclass As classids = level.GetInvClassID(pchar.Locx, pchar.Locy)
     If iclass = clGold Then
       'Поднимаем золото с карты.
       level.GetItemFromMap pchar.Locx, pchar.Locy, inv
       'Добавим к золоту персонажа.
       pchar.CurrGold = pchar.CurrGold + inv.gold.amt
       pchar.TotGold = pchar.TotGold + inv.gold.amt
       'Добавим опыт.
       pchar.CurrXP = pchar.CurrXP + inv.gold.amt
       pchar.TotXP = pchar.TotXP + inv.gold.amt
       'Выведем сообщение для игрока. 
       PrintMessage inv.gold.amt & " gold coins collected."
       DrawMainScreen
     Else
       'Проверим, есть ли свободный слот в инвентаре.
       Dim As Integer cidx = pchar.GetFreeInventoryIndex
       'Если слот найден, поместим туда предмет.
       If cidx <> -1 Then
         level.GetItemFromMap pchar.Locx, pchar.Locy, inv
         'Добавим в инвентарь персонажа.
         pchar.AddInvItem cidx, inv
         PrintMessage "Item added to inventory."
       Else  
         'Нет свободных слотов.
         PrintMessage "No free inventory slots."
       EndIf
     EndIf
   Else
     PrintMessage "Nothing to get."
   EndIf
 EndIf

Когда игрок нажимает клавишу «g», мы проверяем, вначале, стоит ли персонаж на предмете. Если предмет присутствует на той же ячейке карты, что и персонаж, то, далее, мы получаем идентификатор класса этого предмета. Это нам нужно для того, что бы отличить золото от других предметов. Золото не добавляется в инвентарь персонажа а изменяет значение переменно общего количество денег персонажа, а также, в качестве бонуса, добавляет некоторое количество опыта. Если предмет не золото, то мы добавляем его в инвентарь.

При добавлении предмета в инвентарь нам нужно проверить, если ли в нем свободное место. Мы делаем это в функции GetFreeInventoryIndex.

character.bi
'Возвращает индекс свободного слота инвентаря или -1.
 Function character.GetFreeInventoryIndex() As Integer
   Dim As Integer ret = -1
 
   'Ишем пустой слот.
   For i As Integer = LBound(_cinfo.cinv) To UBound(_cinfo.cinv)
     'Прверяем идентификатор класса.
     If _cinfo.cinv(i).classid = clNone Then
        'Пустой слот.
        ret = i
        Exit For
     EndIf
   Next
 
   Return ret
 End Function

Эта функция перебирает массив предметов инвентаря и ищет clNone записанное в полн ClassID предмета, что говорит о том, что данная ячейка инвентаря пуста. Если функция находит свободный слот, то она возвращает его индекс, или -1, если все слоты заняты. Как только мы получили индекс свободного слота в инвентаре, мы можем добавить в него предмет, используя процедуру AddInvItem.

character.bi
'Добавляет предмет в слот инвентаря персонажа.
 Sub character.AddInvItem(idx As Integer, inv As invtype)
   'Проверим правильность индекса массива.
   If idx >= LBound(_cinfo.cinv) And idx <= UBound(_cinfo.cinv) Then
     'Очистим слот инвентаря.
     ClearInv _cinfo.cinv(idx)
     'Добавим предмет.
     _cinfo.cinv(idx) = inv
   End If
 End Sub

Мы проверяем, чтобы убедиться, что индекс валиден (так как данная процедура может быть вызвана из других мест программы), очистим текущий слот инвентаря (опять же, в случае, если процедура вызывается из другого места) и установим в слот инвентаря предмет, который игрок взял с карты. Чтобы сообщить игроку о том, что предмет успешно подобран, мы добавляем вывод сообщения об этом в основной код обработки команды «Поднять». Теперь, когда предмет находится в инвентаре персонажа, нам нужно иметь возможность его отобразить, а также другие предметы, которые персонаж подобрал ранее. Это подводит нас к экрану инвентаря персонажа.

dod.bas
'Нарисуем экран инвентаря.
   If ckey = "i" Then
       ManageInventory
       'Необходимо перерисовать фон.
       DrawBackground mainback()
       DrawMainScreen
   EndIf

Когда игрок наживает клавишу «i», мы вызываем подпрограмму ManageInventory, которая обрабатывает команды инвентаря. Как только игрок выходит из инвентаря, основной экран перерисовывается. Итак, все действия над инвентарем персонажа происходят в ManageInventory.

dod.bas
'Управление инвентарем персонажа.
 Sub ManageInventory()
   Dim As String kch, ich
   Dim As Integer ret
 
   DrawInventoryScreen
   Do
     kch = InKey
     kch = UCase(kch)
     'Проверим, нажата ли какая либо клавиша.
     If kch <> "" Then
        'Обработка команды опознания предмета.
        If kch = "E" Then
           ret = ProcessEval()
           'Экран изменен.
           If ret = TRUE Then
              DrawInventoryScreen
           EndIf
        EndIf
     EndIf
     Sleep 1
   Loop Until kch = key_esc
   ClearKeys
 End Sub

Вначале мы рисуем экран инвентаря персонажа, на котором и будут обрабатываться все команды работы с инвентарем. На данный момент активна только одна команда «оценить», которая вызывается при нажатии клавиши «e». После того, как игрок сделал все что ему нужно было сделать с инвентарем, мы возвращаем управление основной программе. Функцию ProcessEval() мы рассмотрим немного позже, а пока, давайте посмотрим на процедуру DrawInventoryScreen.

dod.bas
'Выводит на экран инвентарь персонажа.
 Sub DrawInventoryScreen()
   Dim As Integer col, row, iitem, ret, srow, ssrow, cnt, i
   Dim As String txt, txt2, desc
   Dim As invtype inv
   Dim As UInteger clr
 
   ScreenLock
       'Установим фон для экрана инвентаря.
       DrawBackground leatherback()
       'Добавим заголовок.
       txt = "Current Inventory for " & Trim(pchar.CharName)
   col = CenterX(txt)
   row = 1
   'Выведем заголовок с тенью.
   PutTextShadow txt, row, col, fbYellowBright
   'Добавми текущую экипировку.
   col = 2
   row += 3
   txt = "1 Primary: "
   PutTextShadow txt, row, col, fbWhite
   col = txcols / 2
   txt = "4 Neck: "
   PutTextShadow txt, row, col, fbWhite
   col = 2
   row += 2
   txt = "2 Secondary: "
   PutTextShadow txt, row, col, fbWhite
   col = txcols / 2
   txt = "5 Ring Right: "
   PutTextShadow txt, row, col, fbWhite
   col = 2
   row += 2
   txt = "3 Armor: "
   PutTextShadow txt, row, col, fbWhite
   col = txcols / 2
   txt = "6 Ring Left: "
   PutTextShadow txt, row, col, fbWhite
   'Разделительная черта.
   row += 2
   col = 1 
   txt = String(80, Chr(205))
   Mid(txt, 2) = " Equipment  (*) = Not Evaluated "
   txt2 = " Gold: " & pchar.CurrGold & " "
   Mid(txt, 80 - Len(txt2)) = txt2
   PutTextShadow txt, row, col, fbYellowBright
   row += 2
   col = 2
   srow = row
   'Выведем предметы в инвентаре.
   For i = pchar.LowInv To pchar.HighInv
     'Проверим, есть ли предмет в слоте.
     iitem = pchar.HasInvItem(i)
     If iitem = TRUE Then
        'Получим предмет из слота.
        pchar.GetInventoryItem i, inv
        'Проверим, опознан или нет.
        ret = IsEval(inv)
        'Получим описание предмета.
        desc = GetInvItemDesc(inv)
        'Получим цвет предмета.
        clr = inv.iconclr
        'Создадим строку с описанием.
        txt = Chr(i) & " " & desc & " "
        'Если предмет не опознан, то отметить его, чтобы игрок знал, что данный предмет нужно опознать.
        If ret = FALSE Then
           txt &= "(*)"
        EndIf
     Else
        txt = Chr(i)
        clr = fbWhite
     EndIf
     'Перейдем на другой столбец, если достигли середины списка инвентаря.
     cnt += 1
     If cnt =  14 Then
        col = txcols / 2
        'Сохраним номер последней строки, чтобы потом наисовать разделитель.
        ssrow = row
        row = srow
     EndIf
     'Выведем текст.
     PutTextShadow txt, row, col, clr
     row += 2
   Next
   'Выведем разделительную линию.
   row = ssrow + 1
   col = 1
   txt = String(80, Chr(205))
   Mid(txt, 2) = " Spells Learned "
   PutTextShadow txt, row, col, fbYellowBright
   'Нарисуем слоты заклинаний.
   col = 2
   row += 2
   cnt = 0
   srow = row
   For i = 65 To 78
     txt = Chr(i)
     'Перейдем на следующую колонку если достигли середины заклинаний.
     cnt += 1
     If cnt =  8 Then
        col = txcols / 2
        'Сохраним номер последней строки. Чтобы знать, откуда продолжить.
        ssrow = row
        row = srow
     EndIf
     'Выведем текст.
     PutTextShadow txt, row, col, clr
     row += 2
   Next
   'Разделитель.
   row = ssrow + 1
   col = 1
   txt = String(80, Chr(205))
   Mid(txt, 2) = " Commands "
   PutTextShadow txt, row, col, fbYellowBright
   'Выведем список команд
   row += 2
   txt = "(D)rop - (E)val - (W)ield/Wear - (R)ead - (E)at/Drink - (I)spect"
   col = CenterX(txt)
   PutTextShadow txt, row, col, fbWhite
   ScreenUnLock
 End Sub

Как вы видите, процедура аналогична процедуре вывода главного экрана. Экран разделен на 3 области. В верхней части отображаются предметы одеты на персонаж, и те, что он держит в руках. Средняя часть, это инвентарь персонажа (представьте себе рюкзак, и вещи лежащие в нем), и, наконец, нижняя секция отображает заклинания, которые выучил персонаж читая книги заклинаний. Единственный раздел, который содержит какую либо информацию на данном этапе разработки, это рюкзак, отображающий предметы из инвентаря персонажа.

dod.bas:DrawInventoryScreen
'Проверим, есть ли предмет в слоте.
     iitem = pchar.HasInvItem(i)
     If iitem = TRUE Then
        'Получим предмет из слота.
        pchar.GetInventoryItem i, inv
        'Проверим, опознан или нет.
        ret = IsEval(inv)
        'Получим описание предмета.
        desc = GetInvItemDesc(inv)
        'Получим цвет предмета.
        clr = inv.iconclr
        'Создадим строку с описанием.
        txt = Chr(i) & " " & desc & " "
        'Если предмет не опознан, то отметить его, чтобы игрок знал, что данный предмет нужно опознать.
        If ret = FALSE Then
           txt &= "(*)"
        EndIf
     Else
        txt = Chr(i)
        clr = fbWhite
     EndIf

При заполнения экрана, мы проверяем, есть ли в слоте инвентаря предмет, используя функцию персонажа HasInvItem, и, если она возвращает «истина», получаем предмет из данного слота инвентаря при помощи функции GetInventoryItem. Далее, необходимо проверить, опознан ли данный предмет, используя функцию IsEval, так как неопознанные предметы мы будем отмечать при их отображении, а также получаем описание предмета при помощи функции GetInvItemDesc. Цвет, используемый для вывода описания предмета, соответствует цвету символа предмета при отображении на карте. Это улучшит у игрока связь между предметом и его описанием. Мы рисуем предмет на экране в соответствующем слоте инвентаря. Если слот пуст, то будет отображен только номер слота, в нашем случае, это одна из букв, от 'a' до 'z'. Давайте пройдемся по каждой из функций, которые мы здесь называли.

character.bi
'Возвращает «истина», если в слоте инвентаря есть предмет.
 Function character.HasInvItem(idx As Integer) As Integer 
   'Проверим на правильность индекса.
   If idx >= LBound(_cinfo.cinv) And idx <= UBound(_cinfo.cinv) Then
     'Проверим идентификатор класса предмета.
     If _cinfo.cinv(idx).classid = clNone Then
        Return FALSE
     Else
        Return TRUE
     EndIf
   Else
     Return FALSE
   End If   
 
 End Function

Здесь необходимо небольшое пояснение. Мы проверяем, равен ли идентификатор класса предмета clNone и если это верно, то возвращаем «ложь», иначе «истина».

inv.bi
'Возвращает «истина», если предмет опознан.
 Function IsEval(inv As invtype) As Integer
   Dim As Integer ret
 
   'Если нечего помечать, как неопознанное.
   If inv.classid = clNone Then
     ret = TRUE
   Else
     'Проверим предметы различного типа.
     Select Case inv.classid 'Золото ненужно распознавать.
        Case clGold
           ret = TRUE
        Case clSupplies  'Вернуть значение поля eval.
           ret = inv.supply.eval
     End Select
   EndIf
 
   Return ret
 End Function

Функуия IsEval просто возвращает значение флага eval предмета из инвентаря. Так как сейчас у нас есть только предметы класса Supply, то мы проверяем только их. Позже мы будем расширять данную функцию.

character.bi

'Получить предмет из слота инвентаря.
 Sub character.GetInventoryItem(idx As Integer, inv As invtype)
 
   'Очистим переменную для предмета.
   ClearInv inv
   'Проверим правильность индекса.
   If idx >= LBound(_cinfo.cinv) And idx <= UBound(_cinfo.cinv) Then
     inv = _cinfo.cinv(idx)
   End If   
 End Sub

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

inv.bi
'Возвращает описание предмета, находящегося по координатам x,y.
 Function GetInvItemDesc(inv As invtype) As String
   Dim As String ret = "None"
 
   'Если идентификатор класса предмета clNone, то ничего не делаем.
   If inv.classid <> clNone Then
     'Возбмем описание для золота.
     If inv.classid = clGold Then
        ret = inv.desc
     EndIf
   EndIf
   'Описание для «ады». 
   If inv.classid = clSupplies Then
     'Если не опознано, то вернуть основное описание.
     If inv.supply.eval = FALSE Then
        ret = inv.desc
     Else
        'Вернем секретное описание.
        ret = inv.supply.sdesc
     EndIf
   EndIf
 
   Return ret
 End Function

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

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

dod.bas
'Команда распознания предмета.
 Function ProcessEval() As Integer
   Dim As String res, mask, desc
   Dim As Integer i, iret, iitem, evaldr, pint, rollp, rolle, ret = FALSE
   Dim As invtype inv
   Dim As tWidgets.btnID btn
   Dim As tWidgets.tInputbox ib
 
   'Убедимся, что у нас есть что распознавать.
   For i = pchar.LowInv To pchar.HighInv
     iitem = pchar.HasInvItem(i)
     If iitem = TRUE Then
        'получим предмет инвентаря.
        pchar.GetInventoryItem i, inv
        'распознан ли он.
        iret = IsEval(inv)
        'необходимо распознать.
        If iret = FALSE Then
           'Построим маску слотов.
           mask &= Chr(i)
        End If
     EndIf
   Next
   If Len(mask) = 0 Then
     ShowMsg "Evaluate", "Nothing to evaluate.", tWidgets.MsgBoxType.gmbOK
   Else
     'Написовать строку ввода на экране.
     ib.Title = "Evaluate"
     ib.Prompt = "Select item(s) to evaluate (" & mask & ")"
     ib.Row = 39
     ib.EditMask = mask
     ib.MaxLen = Len(mask)
     ib.InputLen = Len(mask)
     btn = ib.Inputbox(res)
     'Распознаем каждый жлемент в списке.
     If (btn <> tWidgets.btnID.gbnCancel) And (Len(res) > 0) Then
        'Список на распознание.
        For i = 1 To Len(res)о
          iitem = Asc(res, i) 'Получим индекс слота в инвентаре персонажа.
          'Получим предмет из инвентаря
          pchar.GetInventoryItem iitem, inv
          'Получим сложность распознавания.
          evalDR = GetEvalDR(inv)
          'Используя интеллект получим возможность распознавания.
           pint = pchar.CurrInt + pchar.BonInt
           'Случайное число для сложности распознавания.
           rolle = RandomRange(0, evalDR)
           'Случайное сичло для возможности распознавания игроком.
           rollp = RandomRange(0, pint)
           'Получим описание предмета.
           desc = GetInvItemDesc(inv)
          'Если случайное число у игрока > числу необходимому для распознания - распознаем
          If rollp > rolle Then
            desc &= " was succesfully evaluated."
            ShowMsg "Evaluate", desc, tWidgets.MsgBoxType.gmbOK    
            SetInvEval inv, TRUE 'Set eval to true.
            pchar.AddInvItem iitem, inv 'Put item back into inv.
            ret = TRUE 'Flag caller that screen has changed.
          Else
            desc &= " was not evaluated."
            ShowMsg "Evaluate", desc, tWidgets.MsgBoxType.gmbOK    
          EndIf
        Next
     EndIf
   EndIf
 
   Return ret
 End Function

Во первых, вы заметите некоторые определения из пространства имен tWidgets. Они содержатся в файле tWidgets.bi. Twidgets содержит в себе набор виджетов, которые реализуют GUI (Графические Интерфейс Пользователя) в текстовом видео режиме. Я не буду обсуждать здесь виджеты, но они полностью описаны в разделе приложений.

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

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

  • Получить evalDR (рейтинг сложности) предмета для распознания: evalDR = GetEvalDR (INV).
  • Получить значение интеллекта персонажа вместе с бонусом: pint = pchar.CurrInt + pchar.BonInt
  • Получить случайное число от 0 до evalDR: rolle = RandomRange(0, evalDR)
  • Получить случайное число от 0 до полученного интеллекта персонажа с бонусом: rollp = RandomRange(0, pint)
  • Если число, выпавшее персонажу от его интеллекта больше числа, выпавшего от рейтинга сложности, распознавание прошло успешно. If rollp > rolle
  • Если распознавание успешно, то выставим у предмета поле eval в состояние «истина» и информируем об этом игрока. SetInvEval inv, TRUE
  • Если распознавание прошло не удачно, то сообщаем об этом игроку.

Мы получаем Рейтинг сложности используя функцию GetEvalDR

inv.bi
'Возвразает рейтинг сложности распознания предмета.
 Function GetEvalDR(inv As invtype) As Integer
   Dim As Integer ret
 
   'Если нечего распознавать, то вернем 0.
   If inv.classid = clNone Then
     ret = 0
   Else
     'Выберем тип предмета.
     Select Case inv.classid
        Case clGold 'Золото не нужно распознавать .
           ret = 0
        Case clSupplies
           ret = inv.supply.evaldr 'Вернем значение evaldr для clSupplies.
     End Select
   EndIf
 
   Return ret
 End Function

Мы просто возвращаем значение поля evaldr если есть предмет, требующий распознавания, иначе возвращаем 0. После того, как предмет распознан, нам нужно изменить у него значение поля evaldr, для чего мы используем процедуру SetInvEval.

inv.bi
'Устанавливает значение eval, для предмета inv.
 Sub SetInvEval(inv As invtype, state As Integer)
 
   'проверим, есть ли предмет
   If inv.classid <> clNone Then
     'Выберем предмет.
     Select Case inv.classid
        Case clSupplies
           inv.supply.eval = state 'Установим значение eval.
     End Select
   EndIf
 End Sub

Здесь мы просто устанавливаем значение поля eval в то, которое передали в процедуру. Это дает нам возможность установить значение в «не распознано», если мы захотим реализовать эту функциональность. Мы могли бы это сделать, как результат заклинания монстра или воздействия чего либо еще. Мы пока еще не решили, нужно ли нам это, но лучше оставить свободу выбора, если позже данный функционал нам понадобиться.

Функция распознавания возвращает «истина» в ManageInventory, для того, чтобы мы могли перерисовать экран для отображения обновленного состояния предметов. Это даст игроку все время видеть в инвентаре текущее состояние всех предметов.

dod.bas
'Управление инвентарем персонажа.
 Sub ManageInventory()
   Dim As String kch, ich
   Dim As Integer ret
 
   DrawInventoryScreen
   Do
     kch = InKey
     kch = UCase(kch)
     'Проверим, была ли нажата клавиша.
     If kch <> "" Then
        'Команда распознавания предметов.
        If kch = "E" Then
           ret = ProcessEval()
           'Экран изменен.
           If ret = TRUE Then
              DrawInventoryScreen
           EndIf
        EndIf
     EndIf
     Sleep 1
   Loop Until kch = key_esc
   ClearKeys
 End Sub

Есть и другие команды для инвентаря, которые мы пока не реализовали, так как у нас пока ограниченный набор предметов. Например команда «Взять в руки/Одеть» относится только к оружию, броне, ожерельям и кольцам. Команда «Прочитать» действительна только для свитков и книг с заклинаниями, которых у нас также нет на данный момент. Тем не менее, команды «Выбросить», «Съесть/Выпить» и «Осмотреть» мы можем реализовать уже с теми предметами, которые у нас есть на данный момент. Этим мы и займемся в следующей главе.

Комментариев нет:

Отправить комментарий