Создание ролевой компьютерной игры 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;
// 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, показ глобальной карты.