Отправляет email-рассылки с помощью сервиса Sendsay
  Все выпуски  

Создание ролевой компьютерной игры 56) Работа магазина


Школа программирования

56) Работа магазина

На очереди - задача кодирования работы магазина. Код оригинальной DOS-версии соответствующей процедуры GoToShop (она вызывается автоматически при вступлении на клетку с магазином) на первый взгляд весьма объемен, однако припомним, что в нем происходило. Первая часть отведена формированию списка предметов, что вообще не требует значимых усилий по ее модификации, а далее герой лишь взаимодействует с продавцом, указывая ему либо товар, который он хочет приобрести, либо номер предмета в своем инвентаре, который он хочет продать.

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

Новая форма будет называться ShopForm. Поместим на нее две кнопки BuyButton ("Купить") и SellButton ("Продать") и список ListBox.

Проблема заключается в том, что при нажатии на кнопку формы "Купить" или "Продать" должны выполняться действия по обработке массива ShopItems, локального для процедуры GoToShop, поэтому мы не сможем получить к нему доступ из кода обработчиков ShopForm. В качестве варианта решения можно выбрать перенос этого массива внутрь этой формы, сделать его одним из ее элементов. Но в таком случае все равно придется дробить текст GoToShop на несколько мелких процедур, а нам хотелось бы сохранить исходную структуру всех подпрограмм LowLevel максимально близкими к оригинальной DOS-версии - ведь мы пока не знаем, какие еще платформы будут нам интересны в будущем, поэтому "размазывать" платформно-зависимый код по нескольким модулям нельзя считать сильным решением.

Мы остановимся на таком способе - будем максимально близко придерживаться исходного алгоритма для DOS. Тогда фактически все, что нам потребуется - это как-то сымитировать работу оператора

ReadLn(n);

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

Сымитировать его можно так. Покажем на экране форму ShopForm и проконтролируем, на какую кнопку нажал пользователь. Однако обработку данного выбора продолжим опять в нашем коде GoToShop. Основной недостаток такого подхода (мы умышленно отказались от него в предыдущих процедурах) связан с тем, что форма после нажатия на кнопку закрывается, а потом показывается вновь. Но это недоработка чисто техническая, выше был предложен способ ее обхода, а читатель может самостоятельно придумать и другие более лучшие варианты взаимодействия с виртуальным магазином.

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

  // -----
  procedure TShopForm.BuyButtonClick(Sender: TObject);
  begin
  Tag := 1;
  Close;
  end; //

Специальное поле Tag присутствует у любой формы Delphi. Предназначено оно именно для удобства программиста и хранения промежуточных значений работы. Вот мы им и воспользовались. Только не забудем перед вызовом данной формы это поле обнулять.

С обработчиком кнопки продажи имущества персонажа ситуация будет посложнее. Ведь мы, как уже говорилось, должны здесь сначала выбрать предмет для продажи, а как это сделать? Обратимся к уже готовой форме работы с инвентарем. Более того, просто воспользуемся нашей процедурой ShowHeroItems - вызовем ее, и после того, как герой выберет некоторый предмет, продадим его, запомнив номер выделенного предмета. При этом, конечно, форма работы с инвентарем временно будет располагаться над формой магазина, так и должно быть. Чтобы процесс выглядел более понятно, разместим на форме инвентаря InvForm еще одну кнопку "Продать", которая ничего не будет делать, а лишь закрывать форму.

В качестве значения поля Tag запишем 2:

  // -----
  procedure TShopForm.SellButtonClick(Sender: TObject);
  begin
  ShowHeroItems;
  Tag := 2;
  Close;
  end; //

Вот как полностью запишется процедура захода в магазин (комментарии указаны непосредственно в коде):

  { ----------------- }
  procedure GoToShop(til,x,y: Integer);
  const MaxShopItems = 5;
  var ShopItems: array[1..MaxShopItems] of TGameItem;
      n,i,j,k,ni: Integer;
  begin

  RandSeed := x*x+y;

  for n := 1 to MaxShopItems do
   case til of

   tileWeaponShop:
     begin
     ni := 0;
     for i := 1 to MaxItemTypes do
       if ItemTypes[i].IType in
            [itemHandWeapon, itemArmor, itemAmmo, itemRangedWeapon] then
          inc(ni);

     ni := random(ni)+1;
     for i := 1 to MaxItemTypes do
       if ItemTypes[i].IType in
            [itemHandWeapon, itemArmor, itemAmmo, itemRangedWeapon] then
       begin
       dec(ni);
       if ni = 0 then
          begin
          ShopItems[n] := ItemTypes[i];
          break
          end;
       end;

     end;

   tileMagicShop:
     begin
     ni := 0;
     for i := 1 to MaxItemTypes do
       if ItemTypes[i].IType in [itemMagik, itemEat] then
          inc(ni);

     ni := random(ni)+1;
     for i := 1 to MaxItemTypes do
       if ItemTypes[i].IType in [itemMagik, itemEat] then
       begin
       dec(ni);
       if ni = 0 then
          begin
          ShopItems[n] := ItemTypes[i];
          break
          end;
       end;

     end;

   end;

  for n := 1 to MaxShopItems do
    begin
    if ShopItems[n].IType = itemMagik then
       begin
       GenerateRandomMagikItem(ShopItems[n]);
       end;

    ShopItems[n].Price := round(
         ShopItems[n].Price * ((125+random(11))/100) );
    end;

  // Windows-версия начинается здесь.
   ShopForm.ListBox.Clear;
   for n := 1 to MaxShopItems do
       ShopForm.ListBox.Items.Add(
            GetItemName(ShopItems[n])+ ' : ' +
            IntToStr(ShopItems[n].Price) );

  // Стартуем бесконечный цикл - как и в случае DOS-версии.
  // Только вместо ввода значения переменной n с клавиатуры
  // вызываем форму ShopForm со списком продаваемых товаров.
   while true do
    begin
    i := GetMoneyNo(Heroes[CurHero]); // номер предмета-кошелька

    ShopForm.ListBox.ItemIndex := 0;
    ShopForm.Tag := 0; // обнуляем поле, хранящее результат
    // вызова формы - чтобы знать, какая кнопка будет нажата
    ShopForm.ShowModal;

    if ShopForm.Tag = 0 then // нажат Ок - закрыть!
       Break else

    if ShopForm.Tag=2 then // нажата кнопка "Продать"
       begin

       // Обратите внимание!
       // Здесь мы обращаемся к другой форме, InvForm!

       n := InvForm.ListBox.ItemIndex+1;
       // в n - номер продаваемого премета в инвентаре

       if Heroes[CurHero].Items[n].IType in [itemHandWeapon, itemArmor,
                itemAmmo, itemRangedWeapon, itemEat, itemMagik] then
          begin
          ShowInfo(STR_SELL_ITEM +
  GetItemname(Heroes[CurHero].Items[n]));
          Heroes[CurHero].Items[n].IType := itemNone;

          if i>0 then
             Heroes[CurHero].Items[i].Ints[intMoney] :=
               Heroes[CurHero].Items[i].Ints[intMoney] +
               Heroes[CurHero].Items[n].Price else

          if i<0 then
             begin
             Heroes[CurHero].Items[-i].Ints[intMoney] :=
                 Heroes[CurHero].Items[n].Price;
             Heroes[CurHero].Items[-i].IType := itemMoney;
             end;
          end;
       end else

    if i <= 0 then // если денег для покупки нету...
       ShowInfo(STR_NO_MONEY) else

    if ShopForm.Tag=1 then // если нажата кнопка "Купить"
       begin
       n := ShopForm.ListBox.ItemIndex+1;
       if ShopItems[n].Price > Heroes[CurHero].Items[i].Ints[intMoney] then
          ShowInfo(STR_MIN_MONEY) else

          begin
          k := 0;
          for j := 1 to MaxHeroItems do
            if Heroes[CurHero].Items[j].IType = itemNone then
               begin
               k := j;
               break
                end;

          if k > 0 then
             begin
             Heroes[CurHero].Items[i].Ints[intMoney] :=
               Heroes[CurHero].Items[i].Ints[intMoney] - ShopItems[n].Price;
             Heroes[CurHero].Items[k] := ShopItems[n];
             end
          else ShowInfo(STR_NO_FREE_SLOTS);

          end;

       end

    end;

  end;

Одна из последних крупных процедур, нуждающихся в переносе - это ShowGlobalMap, показ глобальной карты.

Исходный код текущей версии:

dw270508.zip , 36635 байтов.


(c) 2004-2008 Сергей Бобровский

Школа программирования с нуля
Все предыдущие выпуски базового курса всегда тут:
http://www.infiltration.ru/p/

Неофициальный сайт поддержки (со срочными вопросами - сюда):
www.prog-begin.net.ru.


Мои книги (учебные курсы) "Технологии Delphi / C++ / C#".
http://shop.piter.com/publish/authors/17681/191180213/
Дизайн рассылки: Алексей Голубев - Web-дизайн и web-программирование


В избранное