Создание ролевой компьютерной игры 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 с учетом нового дополнения:
Что касается магии, то создадим новую функцию GetMagikItemPrice, которая будет выдавать стоимость конкретного магического предмета с учетом его подтипа.
{ ----------------- } function GetMagikItemPrice(itm: TGameItem): Integer; begin if itm.IType <> itemMagik then Exit;
GetMagikItemPrice := 0; case itm.Ints[intMagikType] of
Далее надо дополнительно сгенерировать случайный магический подтип предметов (с их ценами). Схожую задачу мы уже выполняли в процедуре генерации случайного предмета, теперь выделим ее в виде самостоятельной подпрограммы:
{ ----------------- } 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%.
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):