Давайте снабдим героя в начале игры двумя предметами - легкой броней и недорогим топором. Положим эти предметы ему в рюкзак с помощью следующих операторов процедуры InitHero:
procedure InitHero(HeroNum: Integer); var i: Integer; begin
with Heroes[HeroNum] do begin for i := 1 to MaxChars do
Chars[i] := 0; for i := 1 to MaxSkills do
Skills[i] := BaseSkill_Table[i]; for i := 1 to MaxHeroItems do
Items[i].IType := itemNone;
Items[1] := ItemTypes[1];
Items[2] := ItemTypes[4];
Первым и вторым предметами, хранящимися в рюкзаке героя, стали топор и броня. Эти предметы в дальнейшем нам надо будет перемещать из рюкзака на тело и в руки героя ("надевать").
Для этого нам также потребуется еще один массив, который мы назовем Slots, элементы которого будут соответствовать предметам, непосредственно используемым героем. Это может быть броня или оружие. В коммерческих играх список носимых предметов обычно расширяется амулетами, кольцами, цепочками, колчаном, оружейным поясом, отдельными элементами брони для рук (наручи), ног (поножи), специализированными видами оружия и т. д. Читатель может самостоятельно реализовать в игре такую возможность по описанной далее схеме.
Опишем массив Slots в структуре THero:
const MaxSlots = 2;
slotBody = 1;
slotHands = 2;
type THero = record
Chars : array[1..MaxChars] of Integer;
Skills: array[1..MaxSkills] of Integer;
Items : array[1..MaxHeroItems] of TGameItem;
Slots : array[1..MaxSlots] of TGameItem;
...
Пока мы отвели герою два слота (броня на теле и оружие в руках), доступ к которым может происходить с помощью индексов slotBody и slotHands. Проинициализируем этот массив в процедуре InitHero:
procedure InitHero(HeroNum: Integer); var i: Integer; begin
with Heroes[HeroNum] do begin for i := 1 to MaxChars do
Chars[i] := 0; for i := 1 to MaxSkills do
Skills[i] := BaseSkill_Table[i]; for i := 1 to MaxHeroItems do
Items[i].IType := itemNone;
Items[1] := ItemTypes[1];
Items[2] := ItemTypes[4]; for i := 1 to MaxSlots do
Slots[i].IType := itemNone;
Как и в случае с рюкзаком, массив слотов просто представим набором "пустых" предметов.
Для отображения носимых героем предметов подготовим процедуру ShowHeroSlots (модуль LowLevel):
procedure ShowHeroSlots; var i: Integer; begin
ClrScr;
GoToXY(1,1);
Write(STR_HERO_SLOTITEMS); for i := 1 to MaxSlots do begin
GoToXY(1,i+2);
Write(SlotName[i], ' : ' ); if Heroes[CurHero].Slots[i].IType = itemNone then Write(STR_EMPTY_ITEM) else Write(Heroes[CurHero].Slots[i].Name) end;
GoToXY(1,20);
ReadLn;
ShowGame; end;
Константа STR_HERO_SLOTITEMS должна быть описана в модуле Texts:
Массив констант SlotName (названия слотов героя) опишем также в модуле Texts:
const SlotName: array[1..MaxSlots] of string[20] =
(
' Тело ' , ' В Руках '
);
Внесение этой константы в программу может вызвать определенные трудности. Дело в том, что она использует другую константу - MaxSlots, которая описана в модуле Hero. Попытка добавить ссылку на модуль Hero в interface-заголовок модуля Texts закончится неудачно из-за того, что возникает замкнутая кольцевая цепочка ссылок interface-разделов друг на друга. Модуль Texts ссылается на модуль Hero, а тот в свою очередь - на модуль GameItem, которому снова требуется сслыка на Texts, так как в его интерфейсной части расположены
описания массива типовых предметов ItemTypes, поле Name которого требует указания константы из модуля Texts.
Сейчас мы решим эту проблему "в лоб". Переместим описание константы MaxSlots, из-за которой разгорелся весь сыр-бор, в модуль Texts (хотя это и не совсем корректно в смысловом плане):
const MaxSlots = 2;
SlotName: array[1..MaxSlots] of string[20] =
(
' Тело ' , ' В Руках '
);
Добавим в список подключенных модулей модуля Hero ссылку на модуль Texts, чтобы оставалась доступной константа MaxSlots:
unit Hero;
interface uses GameItem, Texts;
одновременно убрав ее из раздела реализации.
Теперь программа соберется без проблем. Однако, как уже говорилось, такой подход, хотя и достигает цели, но все же не может считаться оптимальным. Ведь модуль Texts предназначен для хранения только текстовых констант, и подобное смешение понятий может по мере усложнения программы привести к новым ошибкам подобного класса. Правильнее спрятать подробное описание массива ItemTypes в раздел реализации модуля GameItem, переместив туда же и ссылку на модуль Texts, а доступ к элементам ItemTypes обеспечить с помощью
дополнительной функции - например, GetItemType, возвращающей элемент массива по его номеру (параметру функции). Вы сами можете дополнить программу такой возможностью.
Вызов процедуры ShowHeroSlots введем в обработчик нажатий на клавиши в главном модуле программы Main. Пусть список надетых героем предметов показывается при нажатии на клавишу 'e':
while true do begin
ShowGame;
k := ReadKey; case k of
' e ' : ShowHeroSlots;
' i ' : ShowHeroItems;
Вызываемые функции расположены в алфавитном порядке нажимаемых клавиш - это удобно, так как позволяет быстро найти нужный обработчик определенного нажатия.
Предметы, хранящиеся в рюкзаке, и надетые на тело, надо уметь менять местами (из рук убрать в рюкзак, а из рюкзака - одеть на тело). Кроме того, предметы иногда приходится выбрасывать на землю и поднимать с земли. Разделим эту задачу на несколько частей.
Носимые предметы - в рюкзак
Расширим экран списка надетых предметов дополнительными возможностями. Позволим человеку отдавать команды - например, ввод номера слота будет означать команду "предмет из слота N убрать в рюкзак". Для этого в процедуре ShowHeroSlots добавим возможность ввода номера, а также выделим основную часть процедуры в бесконечный цикл, который будет прекращаться, когда пользователь введет в качестве номера ноль.
procedure ShowHeroSlots; var i,n: Integer; begin
while true do begin
ClrScr;
GoToXY(1,1);
Write(STR_HERO_SLOTITEMS); for i := 1 to MaxSlots do begin
GoToXY(1,i+2);
Write(i, ' ) ' ,SlotName[i], ' : ' ); if Heroes[CurHero].Slots[i].IType = itemNone then Write(STR_EMPTY_ITEM) else Write(Heroes[CurHero].Slots[i].Name) end;
GoToXY(1,20);Write( ' > ' );
ReadLn(n); if n = 0 then break; end;
ShowGame; end;
Введенное значение надо проверить. Если слот пуст, или в рюкзаке нет места, никаких действий предпринимать не надо. В противном случае (в слоте есть предмет и в рюкзаке есть свободные места) переместим предмет в рюкзак. Проверка пустоты слота несложна, а вот поиск свободного места в рюкзаке желательно реализовать в виде отдельной функции. Дело в том, что данная реализация будет в значительной степени зависеть от способа определения свободного места в рюкзаке. Этот способ может содержать анализ веса несомого
груза, поиск местоположения предмета в рюкзаке и так далее. Поэтому выделим его в функцию GetFreeBag, возвращающую номер свободного элемента в рюкзаке или ноль в случае неудачи. Запишем функцию в модуле Hero:
function GetFreeBag( var H: Thero ): Integer; var i: Integer; begin
GetFreeBag := 0; for i := 1 to MaxHeroItems do if H.Items[i].IType = itemNone then begin
GetFreeBag := i;
Exit end; end;
В ней происходит перебор предметов, хранящихся в рюкзаке, и как только обнаруживается элемент, у которого значение поля IType соответствует значению типа "пустого" предмета, функция возвращает его индекс в массиве Items, входящего в структуру Hero.
С учетом этих нововведений процедура ShowHeroSlots запишется так:
procedure ShowHeroSlots; var i,n,s: Integer; begin
while true do begin
ClrScr;
GoToXY(1,1);
Write(STR_HERO_SLOTITEMS); for i := 1 to MaxSlots do begin
GoToXY(1,i+2);
Write(i, ' ) ' ,SlotName[i], ' : ' ); if Heroes[CurHero].Slots[i].IType = itemNone then Write(STR_EMPTY_ITEM) else Write(Heroes[CurHero].Slots[i].Name) end;
GoToXY(1,20);Write( ' > ' );
ReadLn(n); if n = 0 then break;
s := GetFreeBag(Heroes[CurHero]); if (Heroes[CurHero].Slots[n].IType <> itemNone) and (s > 0) then begin
Heroes[CurHero].Items[s] := Heroes[CurHero].Slots[n];
Heroes[CurHero].Slots[n].IType := itemNone; end;
end;
ShowGame; end;
После того, как введенный номер слота проверен и выяснено, что он корректен, скопируем соответствующий элемент массива Slots в найденный незанятый элемент рюкзака (массив Items), после чего пометим элемент Slots[n] как "пустой".
Исходный код текущей версии (всегда проверен и работоспособен, главный файл- main.pas):