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

Создание ролевой компьютерной игры 33) Назначаем цены на товары


Разработка ролевой игры

33) Назначаем цены на товары

Сегодня - продолжаем разработку ролевой игры. Последний выпуск на эту тему - N 91.

Массив товаров у нас сформирован. Правда, пока в товарах не хватает главного параметра - цены. Поэтому добавим его в массив ItemTypes, для каждого из восьми типов. Отметим, что цену надо будет уточнять для магических предметов, у которых существует деление на подтипы. Можно цену хранить во вспомогательном массиве Ints, а можно выделить в типе TGameItem специальное поле, предназначенное для хранения цены. Давайте так и поступим:

  type TGameItem = record
  ID: Integer;
  x,y: Integer;
  Price: Integer;
  IType: TGameItemType;
  Name : String[20];
  Ints : array[1..MaxItemInt] of Integer;
  Reals: array[1..MaxRealInt] of Real;
  end;

Поле Price теперь будет хранить цену предмета, поэтому уточним содержимое массива ItemTypes с учетом нового дополнения:

  const
  ItemTypes: array[1..MaxItemTypes] of TGameItem =
  (
  (ID:1;
   x:0; y:0;
   Price:25;
   IType:itemHandWeapon;
   Name:STR_AXE;
   Ints: (1,6,50,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0);
   Reals: (0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)),
  (ID:2;
   x:0; y:0;
   Price:50;
   IType:itemHandWeapon;
   Name:STR_SWORD;
   Ints: (2,4,80,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0);
   Reals: (0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)),
  (ID:3;
   x:0; y:0;
   Price:15;
   IType:itemArmor;
   Name:STR_HELM;
   Ints: (1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0);
   Reals: (0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)),
  (ID:4;
   x:0; y:0;
   Price:20;
   IType:itemArmor;
   Name:STR_BODYARMOR;
   Ints: (5,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0);
   Reals: (0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)),
  (ID:5;
   x:0; y:0;
   Price:25;
   IType:itemAmmo;
   Name:STR_AMMO;
   Ints: (100,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0);
   Reals: (0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)),
  (ID:6;
   x:0; y:0;
   Price:65;
   IType:itemRangedWeapon;
   Name:STR_CROSS;
   Ints: (0,5,1,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0);
   Reals: (0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)),
  (ID:7;
   x:0; y:0;
   Price:0;
   IType:itemMagik;
   Name:STR_MAGIKITEM;
   Ints: (0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0);
   Reals: (0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)),
  (ID:8;
   x:0; y:0;
   Price:1;
   IType:itemMoney;
   Name:STR_MONEYITEM;
   Ints: (0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0);
   Reals: (0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0))
  );

Что касается магии, то создадим новую функцию GetMagikItemPrice, которая будет выдавать стоимость конкретного магического предмета с учетом его подтипа.

  { ----------------- }
  function GetMagikItemPrice(itm: TGameItem): Integer;
  begin
  if itm.IType <> itemMagik then Exit;

  GetMagikItemPrice := 0;
  case itm.Ints[intMagikType] of

    mintStorm : GetMagikItemPrice := 5;
    mintHealing : GetMagikItemPrice := 3;
    mintStormStf : GetMagikItemPrice := 20;
    mintHealingStf: GetMagikItemPrice := 25;

  end;
  end;

Далее надо дополнительно сгенерировать случайный магический подтип предметов (с их ценами). Схожую задачу мы уже выполняли в процедуре генерации случайного предмета, теперь выделим ее в виде самостоятельной подпрограммы:

  { ----------------- }
  procedure GenerateRandomMagikItem( var gi: TGameItem );
  begin
   gi.Ints[intMagikType] := random(mintMax)+1;

   case gi.Ints[intMagikType] of

        mintStormStf : gi.Ints[intMagikNum] := 5;

        mintHealingStf: gi.Ints[intMagikNum] := 10;

   else gi.Ints[intMagikNum] := 1;
   end;

  gi.Price := GetMagikItemPrice(gi);

  end;

  { ----------------- }
  procedure GenerateRandomItem( var gi: TGameItem; x,y, ml: Integer );
  begin
   gi := ItemTypes[random(MaxItemTypes)+1];
   gi.x := x;
   gi.y := y;

   if gi.ID = 7 then
      begin
      GenerateRandomMagikItem(gi);
      end else
   if gi.ID = 8 then
      begin
      gi.Ints[intMoney] := random(ml*10)+1;
      end

  end;

Воспользуемся ей в нашем коде:

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

Теперь можно уточнить цены каждого из предметов, подготовленных в магазине для продажи. Как и полагается, продаваться предметы должны выше своей реальной цены. Пускай разница в ценах будет составлять 25-35%.

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

Теперь надо вывести список предметов с их ценами:

  ClrScr;
  GoToXY(1,1);

  for n := 1 to MaxShopItems do
    WriteLn(n, ' ) ' , GetItemName(ShopItems[n]), ' : ' ,
            IntToStr(ShopItems[n].Price) );
  ReadLn;

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

В Turbo Pascal имеется переменная RandSeed, которая позволяет явно задавать повторяющуюся случайную последовательность. Для этого в данную системную переменную просто надо записать некоторое число - и для одного итого же числа всегда будет генерироваться одна и та же случайная последовательность. Нам надо только, чтобы для каждого магазина такое число мы могли получить неизменным - на основании каких-то его статических характеристик. Пожалуй, единственной такой характеристикой в нашем случае будет координата маганиза на карте. Только как перевести ее в число?

Можно поступить просто - сложить координату x и y, и использовать полученное значение в качестве идентификатора случайной последовательности. Однако для координат 5,15 и 15,5 будетполучено одинаковое значение 20, хотя в этих точках, не исключено, вполне могут находиться два разных магазина. Более "продвинутый" способ - возвести первую координату в квадрат (5*5+15 или 15*15+5), и хотя в таком случае все равно не исключается шанс совпадения значений x*x+y и y*y+x, тем не менее вероятность формирования одинаковых идентификаторов очень сильно снижается. Только добавим в параметры процедуры GoToShop координаты магазина:

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

  RandSeed := x*x+y;
  ...

И исправим точку ее вызова в процедуре MoveHero:

  if GameMap[CurMap].Cells[ Heroes[CurHero].x,Heroes[CurHero].y].Tile in
  ShopTileSet then
    begin
    GoToShop(GameMap[CurMap].Cells[
  Heroes[CurHero].x,Heroes[CurHero].y].Tile,
             Heroes[CurHero].x,Heroes[CurHero].y);
    end;

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

   while true do
    begin
    ClrScr;
    GoToXY(1,2);

    i := GetMoneyNo(Heroes[CurHero]);

    for n := 1 to MaxShopItems do
     WriteLn(n, ' ) ' , GetItemName(ShopItems[n]), ' : ' ,
            IntToStr(ShopItems[n].Price) );

    ReadLn(n);

    if n <= 0 then
       Break else

    if i <= 0 then
       ShowInfo(STR_NO_MONEY) else

  if n in [1..MaxShopItems] then
     begin
     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;

Данный код требует пояснения. Главный бесконечный цикл "рисует" список товаров, запрашивая выбор игрока (переменная n). Попутно в переменную i заносится номер слота инвентаря, в котором хранятся деньги персонажа. Если в n введен ноль, значит, герой выходит из магазина. Если (через проверку i ) выяснено, что денег у героя нету, об этом выдается сообщение. Если же значение n лежит в допустимых пределах, то мы выясняем, достаточно ли у героя денег, после чего записываем в переменную k номер свободного слота инвентаря (в него будет помещен покупаемый предмет). Наконец, выполняется это перемещение, и со счета персонажа списывается необходимая сумма.

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


Исходный код текущей версии для Turbo Pascal (всегда проверен и работоспособен, главный файл - main.pas):

http://russianenterprisesolutions.com/sbo/download/2136.zip 14796 байтов


(c) 2004-2006 Сергей Бобровский bobrovsky@russianenterprisesolutions.com

Школа программирования с нуля
Все предыдущие выпуски базового курса тут:
http://russianenterprisesolutions.com/sbo/

Дизайн рассылки: Алексей Голубев - Web-дизайн и web-программирование


В избранное