|
Программирование >> Рекурсивные объекты и фрактальные узоры
более далёкие. Прямоугольная часть каждой стены, изображающая проход, видна лишь тогда, когда проход реально существует. Если же на этом месте в карте лабиринта стоит нуль, более близкая стена будет нарисована прямо поверх прямоугольника. Такой, быть может, идеологически не самый красивый подход позволяет заметно сократить программу. Мы просто рисуем стены на месте нулей, не думая об изображении проходов. Направление движения путстпсственника сохраняется в переменной типа Direction: enum Direction { LEFT = О, UP = 1, RIGHT = 2, DOWN =3}; Для хранения информации о лабиринте и о текущей позиции путешественника используются глобальные переменные: vector<string> Maze; лабиринт (массив строк из нулей и единиц) int PosX = 1, PoaY = 1; начальная позиция человека (левый верхний угол) int TargetX, TargetY; целевая позиция человека Direction Dir = RIGHT; начальное направление человека (направо) При работе с направлением движения путешественника нам потребуются четыре служебные функции: вернуть направление путешественника после пЬворота налево Direction LeftDir(Direction d) { return (d - 1 < LEFT) ? DOWN : d - 1; } вернуть направление путешественника после поворота направо Direction RightDir(Direction d) { return (d + 1 > DOWN) ? LEFT : d + 1; } вернуть смещение по оси X при каждом шаге в сторону dir int GetDX(Direction dir) int DX[] = { -1, 0, 1, 0 }; return DX[dir]; вернуть смещение по оси Y при каждом шаге в сторону dir int GetDY(Direction dir) int DY[] = { 0, -1, 0, 1 }; return DY[dir]; Поскольку лабиринт загружается из файла, напишем функцию его загрузки LoadMaze(): void LoadMazeО { В программе стена пред ставлена типом данных Wall и константами innerH, OuterH и Frontw, задающими размеры самой большой (ближайшей к глазам путешественника) стенЫ: размеры самой большой стены const int InnerH = 300; const int OuterH = 400; const int Frontw = 50; ---- ----------------------------------------------------------- struct Wall { размеры стены и полуразность высот L, упрощающая рисование int InnerH, OuterH, FrontW, L; направление enum WallType { LEFTWALL, RIGHTWALL }; создать стену с наружной высотой outerh, используя пропорции эталонной стены Wall(int outerh) { double С = outerh / double(::OuterH); OuterH = outerh; InnerH = ::InnerH * С; Frontw = ::FrontW * С; полуразность высот L = (OuterH - InnerH) /2; изобразить стену типа type (левую или правую) в точке X, Y void Draw(int X, int Y, WallType type) TCanvas *p = MainForm->DrawingArea->Canvas; TPoint points[] = { TPoint(X, Y), TPoint(X, Y + OuterH), TPoint(X + (type == LEFTWALL ? 1 : -1) * FrontW, Y + L + InnerH), TPoint(X + (type == LEFTWALL ? 1 : -1) * FrontW, Y + L) }; p->Polygon(points, 3); TRect r = (type == LEFTWALL) ? Rect(0, Y, X + 1, Y + OuterH +1) : Rect(p->ClipRect.Width(), Y, X - 1, Y + OuterH + 1); p->Rectangle(r); Обратите внимание, что все рисуемые фигуры закрашиваются белым цветом, установленным по умолчанию в качестве цвета закраски. Дело в том, что стены, расположенные ближе к человеку, частично перекрывают стены вычислить координаты локации в лабиринте, в которой путешественник окажется через Depth шагов в текущем направлении int CurrentX = PosX + Depth*GetDX(Dir); int CurrentY - PosY + Depth*GetDY(Dir); если слева от этой локации находится стена, изобразить её if(Maze[CurrentY + GetDY(LeftDir(Dir))].at(CurrentX + GetDX(LeftDir(Dir))) 0) lw.Draw(XL. Y, Wall::LEFTWALL); аналогично, no необходимости изобразить правую стену if(Maze[CurrentY + GetDY(RightDir(Dir))].at(CurrentX + GetDX(RightDir(Dir))) = 0) rw.Draw(XR, Y, Wall:,:RIGHTWALL) ; Основная функция рисования вычисляет просматриваемую глубину MaxDepth, вызывает алгоритм рисования DrawLayers (), а затем обновляет содержимое карты MazeMap: void DrawMazeO { int MaxDepth = 1; пока не встретили стену в текущем направлении while(Maze[PosY + GetDY(Dir)*MaxDepth].at(PosX + GetDX(Dir)*MaxDepth) == 1) MaxDepth+ +; нарисовать все стены (начиная с глубины 1) DrawLayers(О, MainForm->Width, (MainForm->Height - OuterH), OuterH, 1, MaxDepth); изобразить карту лабиринта char DirChars[] = { <, >, V }; MainForm->MazeMap->Lines->Clear(); for(int i = 0; i < Maze.sizeO; i + +) string s = Maz€[i]; в точке PosX, PosY обозначить путешественника if(i == PosY) s.at(PosX) = DirChars[DirJ; MainForm->MazeMap->Lines->Add(s.c str()); Осталось написать лишь две функции, выполняющие, соответственно, инициализацию программы и обработку событий клавиатуры. Инициализация выполняется в конструкторе формы: string s; загрузить лабиринт из файла maze.txt ifstream in( maze.txt ); while(in s) считать очередную строку Maze.push back(s); координаты правого нижнего угла TargetX = s.length О - 2; TargetY = Maze.size() - 2; Самая сложная часть программы - вывод на экран трёхмерного чертежа лабиринта. Дополнительная трудность заключается в том, что нам придётся рисовать, начиная с самых дальних стен, поскольку ближние стены должны их частично перекрывать. Основная работа будет выполняться функцией DrawLayers (), рисующей все стены на расстоянии Depth локаций от путешественника и дальше. На вход передаются Х-координаты левой и правой стен уровня Depth (XL и XR), их же Y-координата и высота OuterH (одинаковые для обеих стен), глубина Depth и максимальная просматриваемая человеком глубина Мах Depth. На расстоянии Мах Depth локаций выводится стена, расположенная прямо по курсу движения. Рассмотрим код функции DrawLayers (): void DrawLayers(int XL, int XR, int Y, int outerh, int Depth, int MaxDepth) { если мы добрались до самой дальней стены if(Depth MaxDepth) нарисовать её левую, правую и центральную секции (три прямоугольника) MainForm->DrawingArea->Canvas->Rectangle(XL, Y, XR, Y + outerh); MainForm->DrawingArea->Canvas->Rectangle(0, Y, XL + 1, Y + outerh); MainForm->DrawingArea->Canvas->Rectangle(XR - 1, Y, MainForm->Width, Y + outerh) ,- else { левая и правая стена уровня Depth Wall Iw(outerh), rw(outerh); изобразить все более далёкие стены DrawLayers(XL + Iw.FrontW, XR - rw.FrontW + 1, Y + Iw.L, Iw.InnerH, Depth + 1, MaxDepth); Lx. У° °У °втьвtrueзнaчeниecвoйcтвaKeyPreviewфopмы события будут относиться к Memo полю. с v х е w формы. иначе все 5.6. БУКВЫ И ЗВУКИ. ПРОСТОЙ МУЗЫКАЛЬНЫЙ РЕДАКТОР Одноголосую мелодию можно хранить в обычном текстовом файле, если воспользоваться, например, следующими обозначениями: Сочетания ОО, 01 и 02 отвечают за выбор текущей октавы (малая, первая, вторая). Команды L1, L2, L4, L8, L16, L32 и L64 задают длительность используемых нот и пауз (начиная с данного момента). Сами ноты кодируются стандартным образом: С - до, D - ре, Е - ми, F - фа, G - соль, А - ля, Н - си. Пауза обозначается буквой Р. Знаки диеза (#) и бемоля (Ь) ставятся сразу после ноты: С#, Gb. Пример мелодии: 01L8CDEFGAH02C - гамма до-мажор. Знак октавы действует до тех пор, пока в строке не встретился другой знак октавы. Аналогично работает знак длительности. Диезы и бемоли относятся лишь к ноте, непосредственно предшествующей знаку. Требуется написать простой музыкальный редактор для одноголосых мелодий. В процессе редактирования пользователь наносит ноты прямо на нотный стан; преобразование мелодии из графического представления в текстовое производится лишь при сохранении файла на диск. Разумеется, должна бьггь предусмотрена возможность прокрутки нотоносца влево и вправо, чтобы не ограничивать пользователя шириной одного экрана. 5.7. ГЕНЕАЛОГИЧЕСКОЕ ДРЕВО (ПРЕДСТАВЛЕНИЕ И ВИЗУАЛИЗАЦИЯ ДРЕВОВИДНЫХ ДАННЫХ) Требуется написать простую программу для построения генеалогических древ. В режиме редактирования пользователь вносит в систему новых людей, а также указывает отношения вида родитель-ребёнок , то есть для каждого человека выбирает его родителей из списка. Количество родителей варьируется от нуля (если родители неизвестны) до двух (оба родителя известны). Занесённая информация сохраняется в текстовом файле следующего формата. Сначала записываются числовые идентификаторы и имена людей: 1 Пётр Иванов 2 Сергей Иванов 3 Александр Петров fastcall TMainForm: :TMainForm(TCorr55onent* Owner) : TFonn(Owner) LoadMaze(); DrawMazei); Клавиши обрабатываются (событие OnKeyPress формы) довольно прямолинейно: void fastcall TMainForm::FormKeyPress{TObjееt *Sender, char &Key) используются кнопки w, a, s, d if(Key == w) движение на одну локацию вперёд если впереди свободная локация, движемся if(MazelPosY + GetDY(Dir)].at(PosX + GetDX(Dir)) == 1) { PosX += GetDX(Dir); PosY += GetDY(Dir); } else if(Key == s) разворот на 180 градусов Dir = LeftDir (LeftDir (Dir)) ; то же, что и дважды повернуть налево else if(Key == а) поворот налево Dir = LeftDir(Dir); else if(Key == d) поворот направо Dir = RightDir(Dir); очистить экран и нарисовать лабиринт DrawingArea->Canvas->Rectangle(0, О, Width, Height); DrawMaze(); если достигнута целевая локация if(PosX == TargetX && PosY == TargetY) Application->MessageBox(Целевая локация достигнута!, -Победа!, 0); Application->Tenninate();
|
© 2006 - 2024 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки. |