/
Author: Рубанцев В. Рубанцева Л.
Tags: программирование компьютерная графика компьютерные технологии язык программирования pascal
ISBN: ㅤ
Year: 2023
Text
Черепашья графика на занимательных примерах
1
Бесплатное издание
Все права защищены. Никакая часть этой книги не может
быть воспроизведена в любой форме без письменного разрешения правообладателей.
Автор книги не несёт ответственности за возможный вред
от использования информации, составляющей содержание
книги и приложений.
Copyright 2023 Валерий Рубанцев
Лилия Рубанцева
2
От автора
Народная мудрость гласит: лучше один раз увидеть, чем сто раз услышать. Наглядность – один
из дидактических принципов, основанных на достоверном факте, что большую часть информации человек получает от зрения.
Современная компьютерная графика должна
быть очень быстрой, ведь программы генерируют сложные сцены с множеством объектов.
Поэтому мы получаем на экране картинки уже в
готовом виде. Процесс построения изображений скрыт от нас.
Мы не знаем, как программа рисует линии, квадраты или круги, не говоря уже о
более сложных фигурах. Во многих языках программирования, а теперь и в паскале, есть Черепашка – исполнитель простых команд для вычерчивания линий.
3
Черепашка наглядно показывает пошаговый процесс графических построений.
Если Черепашка чертит линию, то мы видим, как она приползает в начало этой
линии, поворачивается в сторону конца линии и медленно ползёт «из пункта А в
пункт Б», оставляя за собой след – всё увеличивающийся в длине отрезок прямой.
Конечно, построение прямых линий и других геометрических примитивов мы
вполне можем представить себе мысленно. Но последовательность построения
сложных фигур далеко не столь очевидна. Тем более это относится к фрактальным
и рекурсивным кривым. Здесь наглядность просто необходима, и без Черепашки
нам не обойтись.
Но черепашья графика не только познавательна, она интересна и с художественной точки зрения, ведь Черепашка может рисовать очень красивые картинки: спирали Архимеда и Ферма, лемнискату Бернулли, улитку Паскаля, фигуры Лиссажу,
астроиды, кардиоиды, эпи- и гипотрохоиды. Мы научим Черепашку рисовать клевер, розочки, ромашки, кустики и деревья, параметрические и полярные кривые,
снежинку Коха, кривые Пеано, Пеано-Госпера и Пенроуза. И это ещё не всё! Мы
отправим нашу учёную Черепашку собирать цели и бродить по Лабиринту.
В книге много занимательных проектов, но самое главное – она даёт вам в руки и
в головы замечательный инструмент для реализации вашего творческого потенциала. И Черепашка поможет вам воплотить в жизнь самые смелые художественные замыслы и помыслы.
Поскольку книга рассказывает о компьютерной графике, то в ней много картинок,
иллюстрирующих работу программ. К сожалению, наша Черепашка не умеет рисовать автопортреты, и тут на помощь ей, и мне, и вам приходит сайт FUSION
BRAIN по адресу: https://editor.fusionbrain.ai/
Умело формулируя запросы, вы сможете бесплатно получить качественные иллюстрации к своим книгам, рефератам, статьям и дневникам. Дополнительно вы можете задать стиль изображения, чтобы выбрать подходящие по теме картинки.
Искусственный интеллект ещё далёк от совершенства, но иногда он выдаёт настоящие шедевры. Образцы творчества искусственного разума вы найдёте дальше, в
наших проектах, но вот некоторые примеры работы этого разума по запросу Черепашка-художница.
4
5
Черепашка живёт медленно, зато долго. Берегите себя!
Валерий Рубанцев
6
Условные обозначения, принятые в книге:
Дополнение
Указание
Замечание
Исходный код:
uses GraphWPF, TurtleWPF;
Задание для самостоятельного решения
Заголовок проекта:
Черепашья мигалка
Исходные коды всех проектов находятся в папке _Projects
7
Оглавление
Черепашья графика на занимательных примерах ............... 1
От автора ............................................................... 3
Оглавление ............................................................. 8
Исполнитель Черепашка ............................................. 11
Точки ............................................................................................................................ 23
Мышка .......................................................................................................................... 25
Многоугольники ....................................................................................................... 29
Черепашьи загогулины........................................................................................... 35
Рекурсивная Черепашка .......................................................................................... 38
Звёздчатая Черепашка ............................................................................................. 48
Черепашка из SmallBasicLibrary ..................................... 51
Знакомство с Черепашкой ..................................................................................... 52
Черепашья мигалка .................................................................................................. 59
Черепаший брейк...................................................................................................... 61
Черепаший пунктир ................................................................................................. 64
Черепашьи полоски................................................................................................. 66
Черепашьи полоски 2 ............................................................................................. 71
Шахматная доска ...................................................................................................... 75
Фрактальная Кибер-Черепашка....................................... 83
Кибер-Черепашка ...................................................................................................... 83
Фракталы ..................................................................................................................... 99
Снежинка Коха ..........................................................................................................101
Фрактальная Черепашка ........................................................................................ 102
L-системы ................................................................................................................. 123
Черепаший зоопарк ................................................. 129
Очень БОЛЬШАЯ Черепашка............................................................................... 129
8
Библиотечная Черепашка .................................................................................... 136
Блуждающие Черепашки ..................................................................................... 157
Черепашка из Small Visual Basic ......................................................................... 164
Многоугольники ..................................................................................................... 165
Полярные кривые .................................................................................................. 176
Улитка Паскаля ...................................................................................................... 189
Спирали ..................................................................................................................... 199
Лютики-цветочки: клевер, розочки, ромашки ............................................. 222
Лемниската Бернулли .......................................................................................... 240
Фигуры Лиссажу .................................................................................................... 252
Трохоиды................................................................................................................. 263
Спирограф ............................................................................................................... 277
Побочные эпитрохоиды ..................................................................................... 279
Немыслимые мысленные эксперименты .................................................... 284
Звёздный конструктор.......................................................................................... 290
Разноцветные окружности .................................................................................. 305
Параметрические кривые.................................................................................... 317
Разноцветные диагонали ..................................................................................... 341
Одноцветные диагонали .................................................................................... 358
Диагональные разрезы ......................................................................................... 361
Диагональный круг............................................................................................... 366
Разноцветные многоугольники ......................................................................... 370
Вращающиеся многоугольники ........................................................................ 379
Цветные вращающиеся многоугольники ...................................................... 388
Зубчатые многоугольники ................................................................................. 398
Звёздчатые многоугольники ............................................................................. 407
Рекурсивные квадраты ......................................................................................... 426
Слоёный торт.......................................................................................................... 430
Рекурсивные круги ................................................................................................ 434
Рекурсивные круграты и квадруги ................................................................... 438
Снежинка Коха ........................................................................................................ 444
Триколор .................................................................................................................. 449
Компьютерная радуга .......................................................................................... 459
9
Астроида .................................................................................................................. 464
Астроида 2 ............................................................................................................... 469
Астроида 3 ............................................................................................................... 474
Цветные линии ...................................................................................................... 490
Новые розетки ........................................................................................................ 494
Вращающиеся квадраты ...................................................................................... 508
Движение объекта к цели.................................................................................... 517
Все в сборе............................................................................................................... 522
Все в сборе 2 ........................................................................................................... 526
Лабиринт .................................................................................................................. 529
Задания для самостоятельного решения ...................................................... 545
Справочник .......................................................... 547
Таблица <Класс Desktop> .................................................................................... 547
Таблица <Класс GraphicsWindow> .................................................................... 547
Таблица <Класс Timer> ........................................................................................ 554
Таблица <Класс Turtle> ....................................................................................... 555
Таблица <Класс Tortoise> ................................................................................... 557
Литература .......................................................... 561
10
Исполнитель Черепашка
Этот новый исполнитель появился в
PascalABC.NET версии 3.9.0. Заданий для него
пока нет, поэтому мы сами озадачим себя занимательными проектами, чтобы получить удовольствие и познакомиться с Черепашкой.
Начинаем знакомиться с Черепашкой. Для этого
импортируем в программу модуль TurtleWPF и
запускаем программу:
## uses TurtleWPF;
На экране тут же появляется окно с размерами по умолчанию – 800 х 600 пикселей, с заголовком Исполнитель Черепаха WPF. Фон окна чисто белый, а в самом
центре окна ждёт команд героиня наших экспериментов – Черепашка (Рис. 1). А
это портрет Черепашки во весь рост (Рис. 2).
В запущенной программе размеры окна можно изменять,
как обычно, мышкой.
Она смотрит вверх, и это важно – Черепашка двигается в том направлении, куда
она смотрит (или пятится в обратную сторону).
Важная процедура MoveTo переносит Черепашку в точку с координатами (x, y) в
пикселях (без вычерчивания линии):
procedure MoveTo(x, y: real);
11
Начало координат находится в левом верхнем углу клиентской области окна. Ось
Х направлена вправо, а ось Y – вниз.
Рис. 1. Черепашка в натуральную величину
Давайте перенесём Черепашку ближе к началу координат:
MoveTo(100, 100);
12
За размеры окна (клиентской части) программы отвечают свойства Width (ширина) и Height (высота). Если вы хотите изменять размеры окна в запущенной
программе, то добавьте модуль GraphWPF:
## uses GraphWPF, TurtleWPF;
property Width: real read GetWidth write SetWidth;
property Height: real read GetHeight write SetHeight;
Рис. 2. Черепашка в полный пиксель
Нам пока достаточно небольшого окна, поэтому уменьшаем его:
Window.Width := 400;
Window.Height := 300;
MoveTo(100, 100);
Запускаем программу. Окно программы ужалось, но Черепашку прекрасно видно
(Рис. 3).
13
Рис. 3. Черепашка в пределах видимости
Неизменный заголовок окна не даёт полезной информации для размышления, но
свойства окна Title и Caption помогут нам озаглавить окно по своему желанию и
хотению (Рис. 4):
property Title: string read GetCaption write SetCaption;
property Caption: string read GetCaption write SetCaption;
Window.Title := 'Привет, Черепашка!';
Процедура Clear без параметров окрашивает фон окна в белый цвет:
procedure Clear;
14
Эта же процедура с параметром с заливает окно заданным цветом с (Рис. 5):
procedure Clear(c: Color);
Рис. 4. Озаглавленное окно
Предопределённые цвета удобно выбирать из выпадающего списка, который появляется на экране, когда вы поставите точку после слова Colors (Рис. 6).
На официальном сайте системы программирования PascalABC.NET можно найти
пример работы Черепашки:
Down;
SetSpeed(11);
SetColor(Colors.Red);
for var i:=1 to 450 do
begin
15
SetColor(RGB(128+i,0,i));
Forw(i);
Turn(96);
end;
Рис. 5. Затонированное окно
Рис. 6. Цвета на выбор
Я добавил в эту программу комментарии:
16
## uses GraphWPF, TurtleWPF;
Window.Title := 'Привет, Черепашка!';
Window.Clear(Colors.SeaShell);
// Черепашка опускает перо:
Down;
// скорость перемещения Черепашки:
SetSpeed(11);
// цвет линии:
SetColor(Colors.Red);
for var i := 1 to 450 do begin
// цвет линии:
SetColor(RGB(128+i,0,i));
// Черепашка ползёт вперёд:
Forw(i);
// Черепашка поворачивается на
// заданный угол в градусах по часовой стрелке:
Turn(96);
end;
Запускаем программу, и сразу получаем готовую картинку (Рис. 7).
Картинка красивая – слов нет. Но Черепашка двигается так быстро, что мы не успеваем проследить за её работой.
Скорость Черепашки задаёт процедура SetSpeed:
procedure SetSpeed(sp: integer);
Вы можете задавать любые целые значения, но программа всё равно приведёт их
к диапазону 1..10. Чем больше это значение, тем резвее Черепашка бегает по
17
полю. По умолчанию скорость Черепашки равна 10, то есть максимально возможной. Однако в примерной программе скорость Черепашки равна 11, то есть
больше 10. В этом случае Черепашка двигается без анимации - прыжками.
Рис. 7. Стремительная Черепашка
18
У Черепашки есть пёрышко, которое прикреплено к её хвосту. Не все это знают,
но у настоящей черепахи тоже есть хвостик. Правда, без карандаша и пера (Рис.
8).
Рис. 8. Хвостатая Черепашка
Если перо опущено, то Черепашка оставляет за собой след – отрезок прямой. Если
перо поднято, то Черепашка двигается «бесследно».
По умолчанию перо поднято, поэтому Черепашку можно легко перенести в любое
место окна без ненужной линии.
Как вы уже догадались, процедура Down опускает перо:
procedure Down;
Процедура Up, наоборот, поднимает перо:
procedure Up;
Процедура SetColor обмакивает перо Черепашки в чернила заданного цвета с:
procedure SetColor(c: GColor)
19
Сначала это красный цвет, который выбирается из списка готовых цветов, а затем
изменяется по ходу верчения спиралей при посредстве функции RGB, которой
нужно передать составляющие цвета. На Рис. 7⬆ отчётливо видно, что цвет линий
плавно переходит из красного в фиолетовый.
Значения составляющих цвета в функции RGB изменяются в диапазоне 0..255:
function RGB(r,g,b: byte): Color;
Функция RandomColor возвращает случайный цвет линий:
function RandomColor: Color;
Процедура Forw гонит Черепашку вперёд (по направлению взгляда) на заданное
расстояние r в пикселях:
procedure Forw(r: real);
Процедура Back действует на Черепашку аналогично, но заставляет её пятиться,
то есть ползти в направлении, обратном её взгляду:
procedure Back(r: real);
Процедура Turn поворачивает Черепашку на угол da в градусах по часовой
стрелке:
procedure Turn(da: real);
20
Процедура TurnLeft поворачивает Черепашку на угол da в градусах по часовой
стрелке:
procedure TurnLeft(da: real);
На самом деле Черепашка поворачивается вправо.
Процедура TurnRight поворачивает Черепашку на угол da в градусах против часовой стрелки:
procedure TurnRight(da: real);
На самом деле Черепашка поворачивается влево.
Чтобы не запутаться в этих поворотах, всегда используйте процедуру Turn. Если
угол положительный, то Черепашка повернётся по часовой стрелке. Если угол отрицательный, то против часовой стрелки.
По умолчанию Черепашку видно на экране, но иногда её нужно спрятать с глаз
долой. Процедура Hide легко справляется с этой задачей:
procedure Hide;
21
Процедура Show возвращает скрытную Черепашку на экран:
procedure Show;
По умолчанию толщина линий равна 1 пикселю, но может быть изменена процедурой SetWidth:
procedure SetWidth(w: real);
И последняя черепашья процедура – Save – делает снимок клиентской части окна
и записывает его в файл на диске в форматах .png, .jpg, .bmp, .gif или .tiff:
procedure Save(fname: string);
fname – это строка с названием файла и расширением.
22
Точки
Начинаем наш первый черепаший проект.
Создаём окно подходящих размеров:
## uses GraphWPF, TurtleWPF;
Window.Width := 400;
Window.Height := 400;
Window.Title := 'Точки';
Window.Clear(Colors.SeaShell);
Переносим Черепашку в центр окна:
// координаты центра окна:
var CX := Window.Width / 2;
var CY := Window.Height / 2;
var r := 180;
// скорость Черепашки:
SetSpeed(2);
// размер точки - 8 пикселей:
SetWidth(8);
//SetColor(Colors.Green);
// переносим Черепашку в центр окна:
MoveTo(CX,CY);
// опускаем перо:
Down;
Из центра окна Черепашка ползёт вперёд на расстояние r-8, где r – это радиус
окружности, на которой Черепашка ставит точки:
23
for var i := 1 to 8 do begin
// случайный цвет чернил:
SetColor(RandomColor);
// поднимаем перо:
Up;
// вперед без линии,
// чтобы начать чертить точку:
Forw(r-8);
Прибыв к месту назначения, Черепашка ползёт вперёд на 8 пикселей, чтобы прочертить отрезок длиной 8 пикселей:
Down;
// вперёд на 8 пикселей,
// чтобы поставить точку
Forw(8);
После этого Черепашка телепортируется в центр окна:
Up;
MoveTo(CX,CY);
И поворачивается здесь на угол 45 градусов по часовой стрелке, чтобы отправиться к следующей точке:
Turn(360/8);
end;
На Рис. 1 вы видите первые результаты дрессировки нашей Черепашки. Даже с
первого раза художества у неё получились изрядно хорошо!
24
Рис. 1. Точечная Черепашка
Мышка
В этом проекте мы научим Черепашку подчиняться мышке.
Сама Черепашка не сообщает нам свои текущие координаты и угол поворота её скорлупы,
поэтому мы запоминаем их в глобальных переменных:
uses GraphWPF, TurtleWPF, System;
25
// координаты Черепашки:
var TurtleX : real;
var TurtleY : real;
// угол поворота:
var TurtleA : real;
В главном блоке программы создаём окно приложения:
begin
Window.Width := 400;
Window.Height := 400;
Window.Title := 'Мышка';
Window.Clear(Colors.SeaShell);
Задаём толщину и цвет линий, а также скорость ползания Черепашки:
// толщина линий:
SetWidth(2);
// скорость Черепашки:
SetSpeed(7);
// цвет линий:
SetColor(Colors.Green);
Устанавливаем Черепашку в центре окна:
// координаты центра окна:
var CX := Window.Width / 2;
var CY := Window.Height / 2;
// переносим Черепашку в центр окна:
MoveTo(CX,CY);
// запоминаем её координаты:
26
TurtleX := CX;
TurtleY := CY;
По умолчания Черепашка смотрит вверх, то есть угол её поворота равен -90 градусов:
// Черепашка смотрит вверх:
TurtleA := -90;
// опускаем перо:
Down;
Если нажата кнопка мышки, программа переходит в процедуру MouseDown:
// процедура для обработки события
// нажатия кнопки мышки:
OnMouseDown := MouseDown;
end.
В процедуре MouseDown мы пользуемся всеми премудростями тригонометрии,
чтобы правильно сориентировать Черепашку в окне программы:
procedure MouseDown(x, y: real; mousebutton: integer);
begin
// координаты Черепашки:
var tx := TurtleX;
var ty := TurtleY;
var ta := TurtleA;
// расстояние от курсора до Черепашки:
var dx := x - tx;
var dy := y - ty;
// вычисляем направление Черепашки:
var a := Math.Atan2(dy, dx) * 180 / PI;
27
var angle := a - ta;
// поворачиваем Черепашку:
Turn(angle);
// запоминаем угол поворота:
TurtleA := a;
// и координаты Черепашки:
TurtleX := x;
TurtleY := y;
// расстояние до мышки:
var dist := Sqrt(dx**2 + dy**2);
// вперёд!
Forw(dist);
end;
Теперь вы можете – не без помощи мышки! – гонять Черепашку вдоль, поперёк и
под любым углом (Рис. 1).
Рис. 1. Черепашьи каракули
28
Многоугольники
Давайте отдохнём от ручной работы в предыдущем проекте и погоняем Черепашку программно.
В главном блоке программы создаём окно,
настраиваем Черепашку на работу и вызываем
процедуру Triangle:
begin
Window.Width := 320;
Window.Height := 320;
Window.Title := 'Многоугольники';
Window.Clear(Colors.SeaShell);
// толщина линий:
SetWidth(2);
// скорость Черепашки:
SetSpeed(7);
// цвет линий:
SetColor(Colors.Green);
// опускаем перо:
Down;
Triangle;
end.
Внутренние углы правильных многоугольников легко вычислить, поэтому нам
нужно только правильно установить Черепашку на старте и дать ей надлежащего
пинка на всю дистанцию.
Вспоминаем, что у треугольника 3 стороны, а внешние углы равны по 120 градусов:
29
uses GraphWPF, TurtleWPF;
// треугольник
procedure Triangle;
begin
MoveTo(160,40);
Turn(-30);
var r := 300;
loop 3 do begin
Turn(-120);
Forw(r);
end
end;
Запускаем программу – Черепашка отлично справляется с чертёжными работами
(Рис. 1).
Раскинув мозгами и транспортиром, вы научите Черепашку чертить шести- и восьмиугольники (Рис. 1):
// шестиугольник
procedure Hexagon;
begin
MoveTo(235,30);
Turn(-30);
var r := 150;
loop 6 do begin
Turn(-60);
Forw(r);
end
end;
// восьмиугольник
procedure Octagon;
30
begin
MoveTo(306,100);
Turn(-0);
var r := 120;
loop 8 do begin
Turn(-45);
Forw(r);
end
end;
Нетрудно догадаться, что если задать достаточно большое число вершин, то многоугольник превратится в окружность (Рис. 1):
// окружность
procedure Circle;
begin
MoveTo(312,155);
Turn(-0);
var r := 12;
var n := 80;
loop n do begin
Turn(-360/n);
Forw(r);
end
end;
Для построения любого правильного многоугольника нужно написать всего несколько строк кода и при этом – никаких расчётов координат вершин!
31
Рис. 1. Черепашьи многоугольники
32
Черепашке нетрудно вычертить и спираль (Рис. 2):
// спираль
procedure Spiral;
begin
MoveTo(320,320);
Turn(-0);
var r := 0.1;
var n := 1100;
loop n do begin
Forw(r);
Turn(10);
r += 0.05;
end
end;
Если при повороте Черепашки задать отрицательный угол
Turn(-10),
то вместо правой спирали получится левая (она закручена
в противоположном направлении).
33
Рис. 2. Черепашья спираль
34
Черепашьи загогулины
А теперь давайте перейдём к более сложным фигурам, которые наверняка убедят
вас в том, что вы не напрасно познакомились с Черепашкой.
Для начала мы заставим многоугольники вращаться вокруг одной из своих вершин:
uses GraphWPF, TurtleWPF;
var povorot, angle1, angle2, side : integer;
procedure PolyStop();
begin
repeat
Forw(side);
Turn(angle1);
povorot += angle1;
until (povorot mod 360 = 0);
end;
// Вращающиеся фигуры
procedure PolyRoll();
begin
loop 12 do begin
PolyStop();
Turn(angle2);
end;
end;
Изменяя значения переменных angle1 и angle2, вы получите разные многоугольники, вращающиеся вокруг центра окна (Рис. 1):
35
// Вращающиеся многоугольники:
procedure RotatePolygons;
begin
povorot := 0;
angle1 := 90;
angle2 := 30;
side := 100;
PolyRoll();
end;
procedure RotatePolygons2;
begin
povorot := 0;
angle1 := 60;
angle2 := 45;
side := 80;
PolyRoll();
end;
begin
Window.Width := 320;
Window.Height := 320;
Window.Title := 'Черепашьи загогулины';
Window.Clear(Colors.SeaShell);
// устанавливаем Черепашку в центре окна:
var CX := Window.Width / 2;
var CY := Window.Height / 2;
MoveTo(CX, CY);
// толщина линий:
SetWidth(2);
// скорость Черепашки:
SetSpeed(10);
// цвет линий:
36
SetColor(Colors.Green);
// опускаем перо:
Down;
//RotatePolygons;
RotatePolygons2;
end.
Рис. 1. Черепашья вертиугольники
37
Рекурсивная Черепашка
Необыкновенно простая рекурсивная процедура PolySpi при разных значениях переменных даёт потрясающее многообразие великолепных спиралей (Рис. 1-6)!
uses GraphWPF, TurtleWPF;
const WIDTH = 640;
HEIGHT = 640;
CX = WIDTH / 2;
CY = HEIGHT / 2;
var angle, side, maxSide : real;
inc : integer;
// Рекурсивные спирали
procedure PolySpi();
begin
if (side > maxSide) then exit;
Forw(side);
Turn(angle);
side += inc;
PolySpi();
end;
// рекурсивная спираль 1
procedure Spiral1();
begin
// устанавливаем Черепашку:
var x := CX - 30;
var y := CY + 30;
MoveTo(x,y);
maxSide := 470;
inc := 1;
38
side := 60;
angle := 95;
PolySpi();
end;
// квадратная спираль
procedure QSpiral();
begin
var x := CX - 0;
var y := CY + 0;
MoveTo(x,y);
maxSide := 600;
inc := 3;
angle := 90;
PolySpi();
end;
// квадратная спираль 2
procedure QSpiral2();
begin
var x := CX - 0;
var y := CY + 0;
MoveTo(x,y);
maxSide := 470;
inc := 3;
angle := 87;
PolySpi();
end;
// треугольная спираль 1
procedure TSpiral1();
begin
var x := CX - 80;
var y := CY + 0;
39
MoveTo(x,y);
maxSide := 600;
inc := 8;
angle := 120;
PolySpi();
end;
// треугольная спираль 2
procedure TSpiral2();
begin
var x := CX + 20;
var y := CY - 20;
MoveTo(x,y);
inc := 4;
angle := 117;
maxSide := 560;
PolySpi();
end;
// СПИРАЛЬ 2
procedure Spiral2();
begin
var x := CX;
var y := CY - 10;
MoveTo(x,y);
inc := 1;
angle := 30;
maxSide := 170;
side := 3;
PolySpi();
end;
begin
Window.Width :=
WIDTH;
40
Window.Height := HEIGHT;
Window.Title := 'Рекурсивная Черепашка';
Window.Clear(Colors.SeaShell);
// толщина линий:
SetWidth(1.6);
// скорость Черепашки:
SetSpeed(11);
// цвет линий:
SetColor(Colors.Green);
// опускаем перо:
Down;
//Spiral1();
//QSpiral();
//QSpiral2();
//TSpiral1();
//TSpiral2();
Spiral2();
end.
41
Рис. 1. Рекурсивная спираль
42
Рис. 2. Квадратная спираль
43
Рис. 3. Рекурсивная спираль
44
Рис. 4. Треугольная спираль
45
Рис. 5. Рекурсивная треугольная спираль
46
Рис. 6. Многоугольная спираль
47
Звёздчатая Черепашка
В этом проекте Черепашка крутит звёздчатые многоугольники (Рис. 1):
uses GraphWPF, TurtleWPF;
const WIDTH = 320;
HEIGHT = 320;
CX = WIDTH / 2;
CY = HEIGHT / 2;
var angle, side : real;
// Звёздчатые многоугольники
procedure NewPoly(n : integer);
begin
loop n do begin
Forw(side);
Turn(angle);
Forw(side);
Turn(2 * angle);
end;
end;
procedure Star();
begin
// устанавливаем Черепашку:
var x := CX - 35;
var y := CY - 30;
MoveTo(x,y);
Turn(18);
// звезда:
side := 110;
angle := 144;
48
NewPoly(5);
end;
procedure Star2();
begin
// устанавливаем Черепашку:
var x := CX - 73;
var y := CY - 104;
MoveTo(x,y);
Turn(0);
// зубчатое колесо:
side := 36;
angle := 125;
NewPoly(24);
end;
begin
Window.Width := WIDTH;
Window.Height := HEIGHT;
Window.Title := 'Звёздчатая Черепашка';
Window.Clear(Colors.SeaShell);
// толщина линий:
SetWidth(2);
// скорость Черепашки:
SetSpeed(11);
// цвет линий:
SetColor(Colors.Green);
// опускаем перо:
Down;
Star();
//Star2();
end.
49
Рис. 1. Звёздчатые многоугольники
50
Черепашка из SmallBasicLibrary
Черепаха - русский
Чарапаха - белорусский
Tortoise, turtle - английский
Galápago - испанский
Testudo - латинский
Tartaruga - португальский
Впервые Черепашка появилась в
языке программирования Лого
(Logo), который был разработан
в 1967 году Сеймуром Пейпертом и Идит Харель. Лого создавался как средство обучения детей дошкольного и младшего
школьного возраста основам
программирования. Главными
объектами языка Лого были
слова и предложения, что и
определило выбор названия для
самого языка – лого по-гречески значит слово. Но известность этому языку программирования принесла маленькая Черепашка (Turtle), которая умела выполнять несложные команды и вычерчивать линии. Именно благодаря Черепашке
этот язык стал очень популярным в 70-80-е годы прошлого века, а Черепашка появилась и в других языках программирования – LISP, SCHEME и многих версиях
бейсика. Есть Черепашка и в библиотеке SmallBasicLibrary - она «спрятана» в
классе Turtle.
В разных системах программирования Черепашки отличаются по форме. Обычно это стилизованные черепахи, но
51
встречаются и примитивные виды Черепашек – равносторонние треугольники и наконечники стрелок (Рис. 1). Их
объединяет желание ползать по экрану и возможность указывать направление их перемещения.
Рис. 1. Черепашки разных видов
Знакомство с Черепашкой
Морская черепашка
По имени Наташка,
С очками из Китая Такая вот крутая.
Натали, Морская черепашка
Чтобы увидеть Черепашку на экране, достаточно выполнить метод Turtle.Show (Рис. 1).
Она всегда появляется в центре окна и смотрит вверх. Направление её взгляда знать
очень важно, потому что Черепашка может
ползти только туда, куда глаза глядят!
52
Рис. 1. Привет, Черепашка!
А теперь пора познакомится и с кодом, который познакомил нас с Черепашкой.
Комментарий не обязателен, но он подсказывает нам название и назначение программы:
// Знакомство с Черепашкой
Чтобы использовать библиотеку SmallBasicLibrary, нужно указать полный путь к
ней, если она находится не в папке с программой:
53
{$reference 'LIB/SmallBasicLibrary.dll'}
В разделе uses прописываем используемые библиотеки:
uses
Microsoft.SmallBasic.Library;
Чтобы не печатать длинное имя класса Графического окна, создаём короткий
псевдоним:
type gw = GraphicsWindow;
Все подготовительные работы оформляем в процедуре Prepare:
procedure Prepare();
begin
Процедура Hide Графического окна прячет его, пока мы переносим окно в центр
экрана:
gw.Hide();
Свойство Title устанавливает и возвращает заголовок Графического окна:
gw.Title := 'Знакомство с Черепашкой';
Процедура Show Графического окна показывает его на экране:
54
gw.Show();
Свойства Left и Top задают координаты верхнего левого угла Графического окна
на экране:
gw.Left := (Desktop.Width - gw.Width) / 2;
gw.Top := (Desktop.Height - gw.Height) / 2;
Свойство CanResize разрешает или запрещает изменять размеры окна в работающей программе. По умолчанию значение свойства CanResize равно true, поэтому
размеры окна можно изменять мышкой.
gw.CanResize := false;
Свойство BackgroundColor окрашивает фон окна в заданный цвет:
gw.BackgroundColor := 'SeaShell';
end;
Теперь главный блок программы получился коротким, простым и понятным:
begin
Prepare;
// создаём Черепашку:
Turtle.Show;
end.
55
Размеры окна по умолчанию слишком велики для наших простых экспериментов,
но их легко изменить. Желаемые размеры окна лучше задавать константами,
чтобы при необходимости можно было бы легко исправить их:
// размеры окна:
const GW_WIDTH = 320;
const GW_HEIGHT = 320;
Свойства Графического окна Width и Height устанавливают и возвращают текущие
размеры окна:
gw.Width := GW_WIDTH;
gw.Height := GW_HEIGHT;
Но при изменении размеров окна Черепашка остаётся на прежнем месте. Давайте вернём Черепашку на её законное место – в центр окна.
При неизменных размерах окна координаты его центра не изменяются, поэтому
мы также запоминаем их в константах:
const CX = GW_WIDTH div 2;
const CY = GW_HEIGHT div 2;
Свойства Черепашки X и Y устанавливают и возвращают текущие координаты Черепашки:
// устанавливаем Черепашку в центре окна:
Turtle.X := CX;
Turtle.Y := CY;
56
Имейте в виду, что координаты Черепашки отсчитываются в системе координат канвы, то есть начало координат находится в левом верхнем углу, а ось Y направлена
вниз!
Код программы слегка удлинился, но зато стал более универсальным:
// Знакомство с Черепашкой
{$reference 'LIB/SmallBasicLibrary.dll'}
uses
Microsoft.SmallBasic.Library;
type gw = GraphicsWindow;
// размеры окна:
const GW_WIDTH = 320;
const GW_HEIGHT = 320;
const CX = GW_WIDTH div 2;
const CY = GW_HEIGHT div 2;
procedure Prepare();
begin
gw.Hide();
gw.Title := 'Знакомство с Черепашкой';
gw.Width := GW_WIDTH;
gw.Height := GW_HEIGHT;
gw.Show();
gw.Left := (Desktop.Width - gw.Width) / 2;
gw.Top := (Desktop.Height - gw.Height) / 2;
gw.CanResize := false;
gw.BackgroundColor := 'SeaShell';
end;
57
procedure Show();
begin
// создаём Черепашку:
Turtle.Show();
// устанавливаем Черепашку в центре окна:
Turtle.X := CX;
Turtle.Y := CY;
end;
begin
Prepare;
Show;
end.
58
Черепашья мигалка
Процедура Turtle.Show показывает Черепашку на экране, а процедура
Turtle.Hide прячет её от нас. Мы можем
использовать эти процедуры, чтобы заставить Черепашку мигать.
Также нам понадобится таймер, чтобы
отсчитывать время между миганиями. В
библиотеке SmallBasic.Library есть простой класс Timer, скромных возможностей которого вполне достаточно для
наших целей. Добавьте в процедуру Prepare следующий код:
// Таймер:
Timer.Interval := 250;
Timer.Pause;
Timer.Tick += OnTick;
Timer.Resume;
Свойство Timer.Interval задаёт промежуток времени между срабатываниями таймера. Событие Timer.Tick происходит при каждом срабатывании таймера, и тогда
вызывается процедура, которую мы добавили к этому событию, то есть OnTick.
Процедура Timer.Pause приостанавливает работу таймера, а процедура Resume
возобновляет её.
Чтобы попеременно скрывать и показывать Черепашку, определяем переменную
n:
59
// счётчик миганий:
var n := 0;
А вот и процедура OnTick, которую вызывает таймер:
procedure OnTick();
begin
if n mod 2 = 0 then
Turtle.Hide
else Turtle.Show;
n += 1;
end;
Запускаем программу и попеременно видим на экране эти картинки (Рис. 1).
Рис. 1. Черепашья мигалка
60
Черепаший брейк
Три процедуры класса Turtle:
Turtle.TurnLeft();
Turtle.TurnRight();
Turtle.Turn(угол : double);
поворачивают Черепашку вокруг собственной оси. Первая процедура – на 90 градусов против часовой стрелки, вторая – на 90 градусов по часовой стрелке, третья
– на произвольный угол. В последнем случае Черепашка повернётся против часовой стрелки, если угол отрицательный, и по часовой стрелке – если положительный.
В процедуре Show поворачиваем Черепашку влево на 45 градусов:
procedure Show();
begin
// создаём Черепашку:
Turtle.Show();
// устанавливаем Черепашку в центре окна:
Turtle.X := CX;
Turtle.Y := CY;
// поворачиваем Черепашку:
Turtle.Turn(-45);
end;
61
В процедуре Prepare задаём интервал между срабатываниями таймера близким
к 1000 миллисекунд, чтобы Черепашка успела повернуться:
Timer.Interval := 800;
В процедуре OnTick крутим Черепашку туда-сюда. Так:
procedure OnTick();
begin
if n mod 2 = 0 then
Turtle.TurnRight
else Turtle.TurnLeft;
n += 1;
end;
Или так:
procedure OnTick();
begin
if n mod 2 = 0 then
Turtle.Turn(90)
else Turtle.Turn(-90);
n += 1;
end;
Для неуклюжей Черепашки танец она
исполняет вполне себе зажигательный (Рис. 1).
62
Рис. 1. Черепаший брейк
63
Черепаший пунктир
За положение карандаша (или пера) отвечают процедуры Черепашки PenDown и
PenUp. Первая опускает карандаш, а вторая
– поднимает его. Соответственно этому Черепашка либо чертит линию, либо просто
перемещается в новое положение.
Черепашка занимает стартовую позицию у
нижней границы окна:
procedure Show();
begin
// создаём Черепашку:
Turtle.Show();
// устанавливаем Черепашку:
Turtle.X := CX;
Turtle.Y := CY + GW_HEIGHT div 2;
end;
Таймерный интервал можно уменьшить:
Timer.Interval := 250;
В процедуре Prepare задаём свойства линий:
// цвет линий:
gw.PenColor := 'Green';
// толщина линий:
gw.PenWidth := 4;
64
Свойство Графического окна PenColor выбирает цвет карандаша, а свойство
PenWidth определяет толщину линий.
Все черепашьи художества переносим в процедуру OnTick, где Черепашка попеременно поднимает и опускает хвостик с карандашом:
procedure OnTick();
begin
if n mod 2 = 0 then
Turtle.PenDown
else Turtle.PenUp;
Turtle.Move(12);
n += 1;
end;
Процедура Turtle.Move двигает Черепашку вперёд на указанное число пикселей.
Если карандаш поднят, то Черепашка просто ползёт, а если опущен – марает
экран. В итоге черепахоползания получается прекрасная пунктирная линия (Рис.
1).
Рис. 1. Чересполосица
65
Черепашьи полоски
Черепашка при изрядном старании может вычерчивать полоски
двух чередующихся цветов. Это
могут быть горизонтальные полоски (Рис. 1):
{$reference
'LIB/SmallBasicLibrary.dll'}
uses
Microsoft.SmallBasic.Library;
type gw = GraphicsWindow;
// размеры окна:
const GW_WIDTH = 640;
const GW_HEIGHT = 640;
const CX = GW_WIDTH div 2;
const CY = GW_HEIGHT div 2;
procedure Prepare();
begin
gw.Hide();
gw.Title := 'Черепашьи полоски';
gw.Width := GW_WIDTH;
gw.Height := GW_HEIGHT;
gw.Show();
gw.Left := (Desktop.Width - gw.Width) / 2;
gw.Top := (Desktop.Height - gw.Height) / 2;
gw.CanResize := false;
gw.BackgroundColor := 'SeaShell';
Turtle.Show;
66
Turtle.PenDown;
end;
// ГОРИЗОНТАЛЬНЫЕ ПОЛОСКИ
procedure ПолоскиГориз(n : integer);
begin
// отступ от левого края:
var dx := 10;
// длина полоски:
var w := GW_WIDTH - dx;
// отступ от верхнего края:
var dy := 10;
// толщина полоски:
var h := (GW_HEIGHT - dx * 1) / n;
var offX := dx;
var offY := dy + h / 2;
var color1 := 'Red';
var color2 := 'Yellow';
gw.PenWidth := h;
var x := offX;
var y := offY;
Turtle.Speed := 9;
Turtle.X := x;
Turtle.Y := y;
Turtle.Angle := 90;
for var row := 0 to n - 1 do begin
// цвет полоски:
var color := row mod 2 = 0 ? color1 : color2;
gw.PenColor := color;
Turtle.Move(w);
y += h;
Turtle.Y := y;
Turtle.X := offX;
end;
end;
67
Рис. 1. Лежачие полоски
И вертикальные полоски (Рис. 2):
// ВЕРТИКАЛЬНЫЕ ПОЛОСКИ
68
procedure ПолоскиВерт(n : integer);
begin
// отступ от верхнего края:
var dy := 10.0;
// длина полоски:
var w := GW_HEIGHT - dy;
// отступ от левого края:
var dx := 10;
// толщина полоски:
var h := (GW_WIDTH - dx) / n;
var offX := dx + h / 2;
var offY := dy;
var color1 := 'Red';
var color2 := 'Yellow';
gw.PenWidth := h;
var x := offX;
var y := offY;
Turtle.Speed := 9;
Turtle.X := x;
Turtle.Y := y;
Turtle.Turn(180);
for var row := 0 to n - 1 do begin
// цвет полоски:
var color := row mod 2 = 0 ? color1 : color2;
gw.PenColor := color;
Turtle.Move(w);
x += h;
Turtle.Y := offY;
Turtle.X := x;
end;
end;
begin
Prepare;
69
//ПолоскиГориз(12);
ПолоскиВерт(24);
end.
Рис. 2. Стоячие полоски
70
Черепашьи полоски 2
Давайте наделим нашу Черепашку небольшими художественными способностями,
чтобы она самостоятельно выбирала цвет и
длину полосок:
{$reference
'LIB/SmallBasicLibrary.dll'}
uses
Microsoft.SmallBasic.Library;
type gw = GraphicsWindow;
// размеры окна:
const GW_WIDTH = 640;
const GW_HEIGHT = 640;
const CX = GW_WIDTH div 2;
const CY = GW_HEIGHT div 2;
procedure Prepare();
begin
gw.Hide();
gw.Title := 'Черепашьи полоски';
gw.Width := GW_WIDTH;
gw.Height := GW_HEIGHT;
gw.Show();
gw.Left := (Desktop.Width - gw.Width) / 2;
gw.Top := (Desktop.Height - gw.Height) / 2;
gw.CanResize := false;
gw.BackgroundColor := 'Black';
Turtle.Show;
Turtle.PenDown;
end;
71
// ПОЛОСКИ
procedure ПолоскиГориз(n : integer);
begin
// отступ от левого края:
var dx := 10.0;
// отступ от верхнего края:
var dy := 10;
// толщина полоски:
var h := (GW_HEIGHT - dx) / n;
var offX := dx;
var offY := dy + h / 2;
gw.PenWidth := h;
var x := offX;
var y := offY;
Turtle.Speed := 9;
Turtle.X := x;
Turtle.Y := y;
Turtle.Angle := 90;
for var row := 0 to n - 1 do begin
// общая длина полоски:
var w := GW_WIDTH - dx;
while w > 0 do begin
// цвет полоски:
gw.PenColor := gw.GetRandomColor;
// длина полоски:
var len := Random(24.0, 120);
if w - len < 24 then len := w;
Turtle.Move(len);
w -= len;
end;
y += h;
Turtle.Y := y;
Turtle.X := offX;
end;
end;
begin
Prepare;
ПолоскиГориз(20);
end.
72
Рис. 1. Полосатые полоски
Если вам больше по душе вертикальные полоски, то добавьте к программе ещё
одну процедуру. Или просто поверните картинку на 90 градусов (Рис. 2).
73
Рис. 2. Универсальная абстрактная
живопись
Курица, посмотрев на художества Черепашки (Рис. 1⬆),
сказала бы: Сносно!
74
Шахматная доска
Клетки шахматной доски окрашены в 2 цвета, которые чередуются по горизонтали и по вертикали.
Все подготовительные операции вам известны:
{$reference 'LIB/SmallBasicLibrary.dll'}
uses
Microsoft.SmallBasic.Library;
type gw = GraphicsWindow;
// размеры окна:
const GW_WIDTH = 640;
const GW_HEIGHT = 640;
const CX = GW_WIDTH div 2;
const CY = GW_HEIGHT div 2;
procedure Prepare();
begin
gw.Hide();
gw.Title := 'Шахматная доска';
gw.Width := GW_WIDTH;
gw.Height := GW_HEIGHT;
gw.Show();
gw.Left := (Desktop.Width - gw.Width) / 2;
gw.Top := (Desktop.Height - gw.Height) / 2;
gw.CanResize := false;
gw.BackgroundColor := 'SeaShell';
end;
Вычисление координат и размеров клеток потребует от вас некоторых математических умений на уровне начальной школы:
75
procedure ChessBoard(c, r : integer);
begin
// отступ от левого края:
var dx := 10.0;
// отступ от верхнего края:
var dy := 10.0;
// длина клетки:
var w := (GW_WIDTH - dx) / c;
// высота клетки:
var h := (GW_HEIGHT - dy) / r;
var offX := dx;
var offY := dy + h / 2;
var color1 := 'Red';
var color2 := 'Yellow';
gw.PenWidth := h;
var x := offX;
var y := offY;
Turtle.Show;
Turtle.PenDown;
Turtle.Speed := 10;
Turtle.X := x;
Turtle.Y := y;
Turtle.TurnRight;
for var row := 0 to r - 1 do begin
for var col := 0 to c - 1 do begin
// цвет клетки:
var color := (row + col) mod 2 = 0 ? color1 : color2;
gw.PenColor := color;
Turtle.Move(w);
end;
y += h;
Turtle.Y := y;
Turtle.X := offX;
end;
end;
76
begin
Prepare;
ChessBoard(8, 8);
//ChessBoard(6, 120);
//ChessBoard(180, 2);
//RandomBoard(30, 30);
end.
Зато шахматная доска получилась изумительно красивой (Рис. 1).
Наша программа достаточно универсальная, чтобы вычерчивать горизонтальные
(Рис. 2) и вертикальные (Рис. 3) полоски разной ширины и высоты.
Пары цветов вы можете выбирать по своему вкусу. Ежели вкус отсутствует или
просто лень, то пусть компьютер сам выбирает цвет очередной клетки:
procedure RandomBoard(c, r : integer);
begin
// отступ от левого края:
var dx := 10.0;
// отступ от верхнего края:
var dy := 10.0;
// длина клетки:
var w := (GW_WIDTH - dx) / c;
// высота клетки:
var h := (GW_HEIGHT - dy) / r;
var offX := dx;
var offY := dy + h / 2;
var color1 := 'Red';
var color2 := 'Yellow';
gw.PenWidth := h;
77
var x := offX;
var y := offY;
Turtle.Show;
Turtle.PenDown;
Turtle.Speed := 10;
Turtle.X := x;
Turtle.Y := y;
Turtle.TurnRight;
for var row := 0 to r - 1 do begin
for var col := 0 to c - 1 do begin
// цвет клетки:
var color := gw.GetRandomColor;
gw.PenColor := color;
Turtle.Move(w);
end;
y += h;
Turtle.Y := y;
Turtle.X := offX;
end;
end;
Черепашка запросто нарисует абстрактные картинки, которые могут украсить интерьер современного дома (Рис. 4).
78
Рис. 1. Пожароопасная шахматная доска
79
Рис. 2. Горизонтальная растяжка шахматной доски
80
Рис. 3. Вертикальная растяжка шахматной доски
81
Рис. 4. Искусственный разум творит искусство
82
Фрактальная Кибер-Черепашка
Мы научили Черепашку выполнять наши команды, записанные на языке паскаль. Но ведь
в каждом живом существе, в
его геноме записана информация о том, как оно должно
расти и развиваться, а также
инстинкты, управляющие поведением достаточно примитивных организмов. Черепашка
тоже умеет выполнять не только внешнюю программу, но и внутреннюю – записанную в её геноме и реализованную через инстинкты. Следующие проекты мы
посвятим моделированию такой кибернетической Черепашки.
Кибер-Черепашка
83
Черепашка рождается в условленном месте экрана, обозначенном координатами
x0 и y0, а её голова направлена в сторону света, заданную начальным углом a0:
// Кибер-Черепашка
// ПРОГРАММА ДЛЯ МОДЕЛИРОВАНИЯ
// КИБЕРЧЕРЕПАШКИ
{$reference 'LIB/SmallBasicLibrary.dll'}
uses
Microsoft.SmallBasic.Library;
type gw = GraphicsWindow;
// размеры окна:
const GW_WIDTH = 320;
const GW_HEIGHT = 320;
const CX = GW_WIDTH div 2;
const CY = GW_HEIGHT div 2;
// начальное положение Черепашки:
var
x0 := 0.0;
y0 := 0.0;
a0 := 0.0;
Основное предназначение Черепашки (судьба) - перемещение по плоскости и вычерчивание линий, поэтому нам необходимо задать её основные свойства:
// длина единичного перемещения Черепашки:
size := 0.0;
// угол единичного поворота Черепашки:
teta := 0;
// скорость движения Черепашки:
speed := 9;
// цвет линии:
84
penColor := 'Green';
// толщина линии:
penWidth := 2;
// список команд, которые должна выполнить Черепашка:
instinct: string;
Здесь всё просто и понятно, кроме, пожалуй, инстинкта, который и определяет
поведение Черепашки. Дальше мы зададим ей жизненную программу (инстинкт),
и вы сможете сами программировать Черепашку.
Настройка окна приложения не должна вызвать у вас вопросов:
procedure Prepare();
begin
gw.Hide();
gw.Title := 'Кибер-Черепашка';
gw.Width := GW_WIDTH;
gw.Height := GW_HEIGHT;
gw.Show();
gw.Left := (Desktop.Width - gw.Width) / 2;
gw.Top := (Desktop.Height - gw.Height) / 2;
gw.CanResize := false;
gw.BackgroundColor := 'SeaShell';
end;
Теперь мы вполне можем перейти к программированию инстинкта Черепашки.
Пусть её жизненный путь начнётся с прямой линии. Задаём место рождения Черепашки:
procedure Line();
begin
// начальная позиция Черепашки:
x0 := CX;
y0 := CY + 100;
85
Её направление:
a0 := 0;
Длину «ползка»:
size := 200;
И самое главное – инстинкт:
// прямая линия:
instinct := 'F';
Процесс рождения Черепашки не зависит от самой Черепашки, а полностью лежит на совести её родителей, то есть на нас с вами:
// Черепашка занимает исходное положение
procedure Start();
begin
Turtle.Speed := speed;
gw.PenColor := penColor;
gw.PenWidth := penWidth;
Turtle.PenDown;
Turtle.X := x0;
Turtle.Y := y0;
Turtle.Angle := a0;
Turtle.Show;
end;
В большинстве языков программирования, поддерживающих черепашью графику, движение Черепашки вперёд обозначается командой Forward. Первую
86
букву этого слова мы используем для этой же цели. После того как Черепашка
заняла исходное положение, она должна выполнить те действия, которые ей
предписывает инстинкт. Для этого у неё имеется мозг, который умеет обрабатывать и исполнять команды инстинкта:
// Черепашка выполняет инстинкт
procedure Execute();
begin
// Черепашка считывает команды:
for var i := 1 to instinct.Length do begin
// очередная команда:
var cmd := instinct[i];
// двигаться вперед на один шаг:
case cmd of
'F': Turtle.Move(size);
end;
end
end;
Пока Черепашка маленькая, она может только двигаться вперёд, но и этого вполне
достаточно, чтобы дать ей путёвку в жизнь:
begin
Prepare;
Line;
end.
Запускаем программу, и Черепашка проводит вертикальный отрезок длиной в
size пикселей (Рис. 1).
87
Рис. 1. Черепашка в ползунковом возрасте
На следующем этапе своей черепашьей жизни наша Киберчерепашка сможет выполнять команды + и -, то есть поворачиваться направо и налево на заданный угол
teta. В её окрепшем мозгу появляются новые шишки для обработки более сложного инстинкта:
// Черепашка выполняет инстинкт
procedure Execute();
begin
// Черепашка считывает команды:
for var i := 1 to instinct.Length do begin
// очередная команда:
var cmd := instinct[i];
// двигаться вперед на один шаг:
case cmd of
'F': Turtle.Move(size);
88
'+': Turtle.Turn(teta);
'-': Turtle.Turn(-teta);
end;
end
end;
Треугольник мы уже вычерчивали с помощью обычной Черепашки. Как вы
помните, для этого Черепашка должна вычертить одну сторону, повернуться на
угол 120 градусов, вычертить вторую сторону, опять повернуться на 120 градусов
и, наконец, вычертить третью сторону. Отсюда следует, что угол единичного поворота равен 120 градусам, а инстинкт запишется строкой:
// Треугольник:
procedure Triangle();
begin
// начальная позиция Черепашки:
x0 := CX - 140;
y0 := CY + 160;
a0 := 0;
teta := 120;
size := 300;
speed := 5;
instinct := 'F+F+F+';
Start();
Execute();
end;
Для проверки нашей гипотезы запускаем программу. Черепашка нас не подвела
– треугольник удался на славу (Рис. 2)!
89
Рис. 2. Черепашковый треугольник
Совершенно аналогично вы можете сформировать инстинкты для вычерчивания
любых правильных многоугольников (Рис. 3):
// квадрат
procedure Quadrat();
begin
// начальная позиция Черепашки:
x0 := CX - 140;
y0 := CY + 160;
a0 := 0;
size := 300;
teta := 90;
instinct := 'F+F+F+F+';
Start();
Execute();
90
end;
// пятиугольник
procedure Pentagon();
begin
// начальная позиция Черепашки:
x0 := CX - 95;
y0 := CY + 160;
a0 := -18;
size := 200;
teta := 72;
instinct := 'F+F+F+F+F+';
Start();
Execute();
end;
// шестиугольник
procedure Shestigon();
begin
// начальная позиция Черепашки:
x0 := CX - 120;
y0 := CY + 80;
a0 := 0;
size := 150;
teta := 60;
instinct := 'F+F+F+F+F+F+';
Start();
Execute();
end;
91
// восьмиугольник
procedure Vosmigon();
begin
// начальная позиция Черепашки:
x0 := CX - 150;
y0 := CY + 70;
a0 := 0;
size := 130;
teta := 45;
instinct := 'F+F+F+F+F+F+F+F+';
Start();
Execute();
end;
92
Рис. 3. Черепашковые многоугольники
Как вы видите, инстинкт становится всё более
длинным, при этом одна и та же пара команд F+
просто повторяется n раз, если через n обозначить число вершин правильного многоугольника. Давайте научимся выращивать инстинкты!
Пусть при рождении Черепашка получает простейший инстинкт, который принято называть
аксиомой, а затем, с каждым годом инстинкт
развивается и становится всё более сложным
благодаря правилам роста. Для треугольника и других правильных многоугольников аксиома самая простая:
axiom := 'F';
93
Она означает, что Черепашка должна двигаться вперёд на расстояние size. Наша
первая Черепашка только это и умела это делать.
Поскольку все многоугольники строятся по одному и тому же сценарию, то и правило для них одно и то же:
newF := 'F+F';
А действует оно так. Через год (возраст Черепашки определяется переменной iter) каждая команда F, в соответствии с правилом роста, заменяется значением
переменной newF, то есть F+F:
iter = 1 axiom := 'F+F'
Это значит, что через год (а у Черепашек год короткий!) наша Черепашка научится
вычерчивать угол (Рис. 4).
Рис. 4. Молодая Черепашка - «угловатая»
94
На второй год с поведение Черепашки усложнится:
iter = 2 axiom := '(F)+(F)' '(F+F)+(F+F)' 'F+F+F+F'
Опять в аксиоме каждая буква F заменяется выражением F+F. Для удобства преобразований мы воспользовались скобками, но самой Черепашке они не нужны.
Вот теперь Черепашка может вычертить настоящий треугольник (Рис. 5).
Рис. 5. Двухлетняя Черепашка умеет чертить треугольник
Для построения треугольника достаточно инстинкта 'F+F+F+', то есть одну сторону Черепашка вычертит дважды. Увы, все инстинкты несовершенны, но зато
довольно просты.
Инстинкт Черепашки формируется, конечно, не вручную - она имеет для этого в
мозгу специальную процедуру:
95
// Черепашка формирует инстинкт
procedure CreateInstinct();
begin
loop iter do begin
instinct := string.Empty;
for var j := 1 to axiom.Length do begin
// очередная команда аксиомы:
var cmd := axiom[j];
if (cmd = 'F') then
instinct += newF
else
instinct += cmd;
end;
axiom := instinct;
end
end;
Её действие мы уже разобрали. Каждая буква F заменяется значением переменной newF, а все остальные символы (команды поворота + и - ) переходят в новый
инстинкт без изменения. Так как мы привыкли использовать при моделировании
поведения Черепашки переменную instinct, то результатом работы процедуры
CreateInstinct пусть будет значение этой переменной, хотя мы могли бы заменить
её и переменной axiom.
Выделим «треугольный» инстинкт в отдельную процедуру:
procedure Triangle();
begin
// начальная позиция Черепашки:
x0 := CX - 140;
y0 := CY + 160;
a0 := 0;
teta := 120;
size := 300;
96
speed := 5;
iter := 2;
axiom := 'F';
newF := 'F+F';
CreateInstinct();
Start();
Execute();
end;
Вы научились формировать простейший инстинкт, повинуясь которому
Черепашка рисует треугольник (см. Рис. 5⬆). Достаточно изменить значение угла
teta, и новый инстинкт – для построения квадрата – готов:
procedure Quadrat();
begin
// начальная позиция Черепашки:
x0 := CX - 140;
y0 := CY + 160;
a0 := 0;
size := 300;
teta := 90;
//speed := 10;
iter := 2;
axiom := 'F';
newF := 'F+F';
CreateInstinct();
Start();
Execute();
end;
На третий год жизни Черепашка сумеет вычертить пятиугольник, шестиугольник и
восьмиугольник:
procedure Pentagon();
begin
97
// начальная позиция Черепашки:
x0 := CX - 95;
y0 := CY + 160;
a0 := -18;
size := 200;
teta := 72;
iter := 3;
axiom := 'F';
newF := 'F+F';
CreateInstinct();
Start();
Execute();
end;
procedure Shestigon();
begin
// начальная позиция Черепашки:
x0 := CX - 120;
y0 := CY + 80;
a0 := 0;
size := 150;
teta := 60;
iter := 3;
axiom := 'F';
newF := 'F+F';
CreateInstinct();
Start();
Execute();
end;
procedure Vosmigon();
begin
// начальная позиция Черепашки:
x0 := CX - 150;
y0 := CY + 70;
a0 := 0;
98
size := 130;
teta := 45;
iter := 3;
axiom := 'F';
newF := 'F+F';
CreateInstinct();
Start();
Execute();
end;
Мы могли бы продолжать этот процесс и дальше, но совершенно понятно, что с
годами Черепашка сможет выстраивать любые правильные многоугольники, поэтому давайте усложним поведение Черепашки с помощью более изощрённых
инстинктов и научим её чертить фракталы.
Фракталы
Фракталы – это замечательные геометрические фигуры, составленные из точно
таких же фигур, но меньшего размера. Например, из маленьких отрезков вы можете составить отрезок большей длины, из него – ещё более длинный, и так до
бесконечности (Рис. 1).
Рис. 1. Фрактальные отрезки
Аналогично из квадратиков вы можете построить большой квадрат (Рис. 2).
99
Рис. 2. Фрактальные квадраты
Примерами фракталов в природе могут служить: кровеносная система, береговая линия, горный рельеф, перистые
облака (Рис. 3), разряд молнии, системы рек с притоками,
трещины в почве, ветвистые растения, фьорды, прожилки
на листьях (Рис. 4), ледяные узоры на стёклах…
Рис. 3. Перистые облака
100
Рис. 4. Жилкование листа
Снежинка Коха
Более сложный пример фрактальной кривой придумал в 1904 году Гельг фон Кох.
Построение начинается с отрезка, который можно условно разделить на три равные части (Рис. 1а). Среднюю часть отрезка заменяем двумя отрезками, каждый
из которых равен трети исходного отрезка (Рис. 1б). Затем мы повторяем эту операцию сколько угодно раз (Рис. 1в).
Рис. 1. Построение снежинки Коха
101
Фрактальная Черепашка
Если вы и дальше продолжите построение кривой Коха, то быстро убедитесь, что вручную её строить весьма
утомительно. Поэтому лучше научить
этому занятию Черепашку. Только
строить она будет не кривую, а снежинку Коха, которая отличается от кривой только тем, что её звенья не выстраиваются в прямую линию, а поворачиваются на 60 градусов, вследствие чего кривая становится замкнутой. Итак, с
углом teta мы определились. С аксиомой тоже можно справиться, а вот правило,
порождающее инстинкт, придётся позаимствовать у самого господина Коха:
// Снежинка Коха:
procedure Koch();
begin
x0 := CX - 75;
y0 := CY + 135;
a0 := 0;
speed := 10;
teta := 60;
size := 90;
iter := 1;
axiom := 'F++F++F';
newF := 'F-F++F-F';
CreateInstinct();
Start();
Execute();
end;
102
С возрастом Черепашка рисует всё более и более сложные узоры, так что в четырёхлетнем возрасте снежинка у неё получается не хуже настоящей (Рис. 1,
справа)!
// Снежинка Коха:
procedure Koch();
begin
a0 := 0;
speed := 10;
teta := 60;
x0 := CX - 76;
y0 := CY + 150;
size := 3.5;
iter := 4;
axiom := 'F++F++F';
newF := 'F-F++F-F';
CreateInstinct();
Start();
Execute();
end;
В 1890 году Джузеппе Пеано построил функцию, график которой, названный кривой Пеано, покрывает квадратами (или, если угодно, ромбами) всю плоскость. И
хотя кривая Пеано не является фракталом в полном смысле этого слова, но всё
равно очень забавно наблюдать, как Черепашка её вычерчивает (Рис. 3). С годами
(то есть с увеличением значения iter) она «квадрирует» все большую и большую
поверхность:
// Кривая Пеано:
procedure Peano();
begin
x0 := CX - 300;
y0 := CY + 310;
a0 := 45;
103
teta := 90;
size := 32;
iter := 3;
speed := 10;
axiom := 'F';
newF := 'F-F+F+F+F-F-F-F+F';
CreateInstinct();
Start();
Execute();
end;
Рис. 1. Кривая Коха при iter=1 и iter=4
Вообще говоря, настоящие снежинки также имеют 6 лучей,
но более сложной формы (Рис. 2).
104
Рис. 2. Стилизованные снежинки
Если вы хотите получить большие картинки, то удвойте размеры окна программы:
// размеры окна:
const GW_WIDTH = 320 * 2;
const GW_HEIGHT = 320 * 2;
Ещё более усложнив инстинкт, мы научим Черепашку выделывать очень сложную
кривую (Рис. 4):
// 32-сегментная кривая
procedure SC32();
begin
x0 := CX - 130;
y0 := CY + 135;
a0 := 0;
teta := 90;
size := 4;
iter := 2;
speed := 10;
axiom := 'F+F+F+F';
newF := '-F+F-F-F+F+FF-F+F+FF+F-F-FF+FF-FF+F+F-FF-F-F+FF-F-F+F+F-F+';
CreateInstinct();
Start();
Execute();
end;
105
Рис. 3. Кривая Пеано при iter= 3
106
Рис. 4. 32-сегментная кривая
107
Добавим к инстинкту Черепашки новую команду, которая в генетическом коде
обозначается буквой b. Получив такую команду, Черепашка переползает вперёд,
но линию за собой не чертит.
Для обработки нового гена мы прибавим в процедуру СreateInstinct одну строку:
procedure CreateInstinct();
begin
loop iter do begin
instinct := string.Empty;
for var j := 1 to axiom.Length do begin
// очередная команда аксиомы:
var cmd := axiom[j];
case cmd of
'F': instinct += newF;
'b': instinct += newb;
else instinct += cmd;
end;
end;
axiom := instinct;
end
end;
То есть новый ген b входит в состав инстинкта аналогично гену F. А вот в процедуре Execute Черепашка должна сначала поднять карандаш, а после перемещения снова опустить его:
// Черепашка выполняет инстинкт
procedure Execute();
begin
// Черепашка считывает команды:
for var i := 1 to instinct.Length do begin
// очередная команда:
var cmd := instinct[i];
// двигаться вперед на один шаг:
case cmd of
'F': Turtle.Move(size);
108
'+': Turtle.Turn(teta);
'-': Turtle.Turn(-teta);
'b': begin
Turtle.PenUp;
Turtle.Move(size);
Turtle.PenDown;
end;
end;
end
end;
С помощью этого гена Черепашка может рисовать фигуры, состоящие из нескольких не связанных между собой частей (Рис. 5):
// Мозаика
procedure Mozaic();
begin
x0 := CX + 160;
y0 := CY + 160;
a0 := 0;
teta := 90;
size := 9;
iter := 2;
speed := 10;
axiom := 'F-F-F-F';
newF := 'F-b+FF-F-FF-Fb-FF+b-FF+F+FF+Fb+FFF';
newb := 'bbbbbb';
CreateInstinct();
Start();
Execute();
end;
Обратите внимание: каждое вхождение нового гена b в инстинкт заменяется значением переменной newb!
109
Рис. 5. Мозаика при iter= 2
Вы можете добавлять к инстинкту Черепашки и другие гены, которые обозначаются буквами латинского алфавита. Они участвуют в формировании инстинкта, но
при его выполнении игнорируются.
110
Для каждого нового гена должно быть записано правило, по которому он заменяется в инстинкте другими генами. Нашей Черепашке вполне достаточно четырёх
неисполняемых генов, которые формируют инстинкт в процедуре CreateInstinct:
var
newb:
newW:
newX:
newY:
newZ:
string;
string;
string;
string;
string;
procedure CreateInstinct();
begin
loop iter do begin
instinct := string.Empty;
for var j := 1 to axiom.Length do begin
// очередная команда аксиомы:
var cmd := axiom[j];
case cmd of
'F': instinct
'b': instinct
'W': instinct
'X': instinct
'Y': instinct
'Z': instinct
else instinct
end;
end;
axiom := instinct;
end
end;
+=
+=
+=
+=
+=
+=
+=
newF;
newb;
newW;
newX;
newY;
newZ;
cmd;
В процедуру Execute никаких изменений вносить не следует, поскольку эти команды Черепашка не выполняет. Зато новые гены могут настолько усложнить инстинкт Черепашки, что она начнёт вычерчивать кривые удивительной красоты!
Сейчас мы научим нашу КиберЧерепашку вычерчивать Треугольный треугольник:
111
// Треугольники
procedure Triangles();
begin
x0 := CX - 55;
y0 := CY + 95;
a0 := 30;
teta := 120;
size := 36;
iter := 6;
speed := 10;
axiom := 'bX';
newb := 'b';
newF := 'F';
newX := '--FXF++FXF++FXF--';
CreateInstinct();
Start();
Execute();
end;
Хотя задача оказалась непростой, но КиберЧерепашка справилась с заданием отлично (Рис. 6).
С двумя новыми генами Черепашка построит кривую Пеано-Госпера, которая, как
и кривая Коха, также напоминает снежинку, но заполненную внутри замысловатыми линиями (Рис. 7):
// Кривая Пеано-Госпера:
procedure PeanoGosper();
begin
x0 := CX - 262;
y0 := CY - 75;
a0 := 0;
teta := 60;
size := 10;
iter := 4;
112
speed := 10;
axiom := 'FX';
newF := 'F';
newX := 'X+YF++YF-FX--FXFX-YF+';
newY := '-FX+YFYF++YF+FX--FX-Y';
CreateInstinct();
Start();
Execute();
end;
А вот чтобы научить Черепашку рисовать почти настоящую снежинку, мы должны
наделить её памятью. По команде [ она запомнит свои координаты и угол поворота, а по команде ] возвратится в это состояние.
Поскольку эти гены не влияют на формирование инстинкта, то процедура
СreateInstinct останется без изменений, а в процедуру Execute нужно добавить
строчки:
// стек:
_stack := 'Stack';
// Черепашка выполняет инстинкт
procedure Execute();
begin
// Черепашка считывает команды:
for var i := 1 to instinct.Length do begin
// очередная команда:
var cmd := instinct[i];
// двигаться вперед на один шаг:
case cmd of
'F': Turtle.Move(size);
'+': Turtle.Turn(teta);
'-': Turtle.Turn(-teta);
'b': begin
Turtle.PenUp;
Turtle.Move(size);
Turtle.PenDown;
113
end;
'[': begin
Stack.PushValue(_stack, Turtle.Angle);
Stack.PushValue(_stack, Turtle.X);
Stack.PushValue(_stack, Turtle.Y);
end;
']': begin
Turtle.Y := Stack.PopValue(_stack);
Turtle.X := Stack.PopValue(_stack);
Turtle.Angle := Stack.PopValue(_stack);
end
end;
end
end;
Как видите, память Черепашки устроена по принципу стека: Черепашка вспоминает последние запомненные события, после чего они стираются из её памяти.
Теперь Черепашка не только умеет выполнять команды инстинкта, но и обладает
памятью. А вот так она строит почти настоящую снежинку (Рис. 8):
// Снежинка:
procedure Snowflake();
begin
x0 := CX;
y0 := CY;
a0 := 0;
size := 12;
iter := 2;
teta := 60;
speed := 10;
// цвет линии:
penColor := 'Blue';
axiom := '[F]+[F]+[F]+[F]+[F]+[F]';
newF := 'F[++F][-FF]FF[+F][-F]FF';
CreateInstinct();
Start();
114
Execute();
// цвет линии:
penColor := 'Green'
end;
С помощью очень простого инстинкта и памяти Черепашка нарисует очень красивую картинку, состоящую из ромбов (Рис. 9):
// Ромбы
procedure Rhombes();
begin
x0 := CX+5;
y0 := CY-264;
a0 := 0;
teta := 60;
size := 20;
iter := 6;
speed := 10;
axiom := 'F';
newF := '-F+F+[+F+F]-';
CreateInstinct();
Start();
Execute();
end;
Кривая Пенроуза (Рис. 10) также состоит из ромбов, но для её построения Черепашке понадобится и очень сложный инстинкт, и крепкая память:
// Кривая Пенроуза
procedure Penrose();
begin
x0 := CX + 5;
y0 := CY - 0;
a0 := 0;
teta := 36;
115
size := 36;
iter := 4;
speed := 10;
axiom := '[Y]++[Y]++[Y]++[Y]++[Y]';
newW := 'YF++ZF----XF[-YF----WF]++';
newX := '+YF--ZF[---WF--XF]+';
newY := '-WF++XF[+++YF++ZF]-';
newZ := '--YF++++WF[+ZF++++XF]--XF';
newF := '';
CreateInstinct();
Start();
Execute();
end;
О мозаиках Пенроуза вы можете прочитать в книге Мартина Гарднера [ГМ93].
И последнее, чему мы научим нашу Черепашку, - это строить кривую, которая
очень напоминает настоящее растение (Рис. 11):
// Куст
procedure Bush();
begin
x0 := CX + 175;
y0 := CY + 321;
a0 := 0;
size := 14;
iter := 4;
teta := 180 div 8;
speed := 10;
axiom := 'F';
newF := '-F+F+[+F-F-]-[-F+F+F]';
CreateInstinct();
Start();
Execute();
end;
116
Рис. 6. Треугольники при iter= 6
117
Рис. 7. Кривая Пеано-Госпера при iter= 4
118
Рис. 8. Снежинка при iter= 2
119
Рис. 9. Ромбы при iter= 6
120
Рис. 10. Кривая Пенроуза при iter= 4
121
Рис. 11. Куст при iter= 4
122
L-системы
И вот, глядя на этот очаровательный «куст», мы можем, наконец, раскрыть
тайну поведения нашей
Черепашки. Оказывается,
инстинкт Черепашки описывается с помощью L-системы, которая была предложена шведским биологом Аристидом Линденмайером (поэтому иначе
они называются системами Линденмайера) для описания растений. Пример с кустом и следующий – с деревом (Рис. 1) - убедительно доказывают, что с помощью
этой системы можно конструировать весьма правдоподобные растения (в некоторых графических редакторах они генерируются практически так же).
// Дерево
procedure Tree();
begin
x0 := CX;
y0 := CY + 320;
a0 := 0;
size := 10;
iter := 5;
teta := 180 div 6;
speed := 10;
axiom := 'F';
newF := 'F[-F]F[+F][F]';
CreateInstinct();
Start();
123
Execute();
end;
Рис. 1. Дерево
124
Все древовидные структуры, а также многие рассмотренные нами фракталы описываются аксиомой и несколькими правилами, которые с увеличением числа итераций iter могут создать кривые любой степени детализации (в том числе и бесконечной, если у вас есть бесконечно много времени и других ресурсов). Неожиданно оказалось, что построение кривых по системе
Линденмайера очень удобно реализовать с
помощью черепашьей графики, поскольку
Черепашка умеет выполнять все команды,
используемые в L-системах.
И ещё парочка деревьев (Рис. 2 и 3):
// Дерево2
procedure Tree2();
begin
x0 := CX;
y0 := CY + 320;
a0 := 0;
size := 7.5;
iter := 4;
teta := 180 div 6;
speed := 10;
axiom := 'F';
newF := 'F[+F]F[-F]F';
CreateInstinct();
Start();
Execute();
end;
125
// Дерево3
procedure Tree3();
begin
x0 := CX + 80;
y0 := CY + 310;
a0 := 0;
size := 11.5;
iter := 4;
teta := 180 div 6;
speed := 10;
axiom := 'F';
newF := 'FF-[-F+F+F]+[+F-F-F]';
CreateInstinct();
Start();
Execute();
end;
126
Рис. 2. Дерево от дерева недалеко падает
127
Рис. 3. Кустистое дерево
128
Черепаший зоопарк
Вы познакомились с двумя Черепашками. Самая молодая Черепашка пришла к
нам прямиком из программы PascalABC.NET. Второй Черепашке уже больше десятка лет, но она до сих пор находится в прекрасной физической форме! Но мир
полон и другими Черепашками. С некоторыми из них вы познакомитесь в следующих проектах.
Очень БОЛЬШАЯ Черепашка
Седлай черепаху!
Со времён языка Лого Черепашка используется как наглядное средство при обучении детей программированию. Эта славная традиция не увяла и до настоящего
времени. Например, в книге Python für Kids (Рис. 1), которая выдержала в Германии уже 4 издания, всё обучение программированию строится вокруг этого милого животного (это я о Черепашке, а не о Питоне – хотя упомянутая недобрым
словом змея не имеет прямого отношения к одноимённому языку).
129
Рис. 1. Обложка книги
Справедливости ради следует отметить, что немецкая Черепашка (Schildkröte) и
бойчее, и мощнее Смоллбейсиковской. Например, наша Черепашка только и
умеет что вычерчивать линии, а немецкая - ещё и заливные фигуры (Рис. 2).
Рис. 2. Заливные квадраты
130
Наша Черепашка всегда пребывает в одиночестве, а немецких черепах можно
плодить целыми стадами (Рис. 3) и беспощадно топтать ими одновременно всю
канву окна приложения.
Рис. 3. 36 Черепашек, разбегающихся во все стороны
Благодаря невероятному совпадению или везению Черепашка в качестве средства обучения попала не в суп, а ещё и в американскую книжку – и тоже по программированию на Питоне, и тоже для детей (Рис. 4).
Американская Черепашка так же одинока, как и наша, и даже на настоящую черепаху не похожа, но зато умеет рисовать закрашенные фигуры (Рис. 5).
Не обошёлся без Черепашки и видеокурс PluralSight - Teaching Kids Programming
with C# (Рис. 6), посвящённый обучению детей, начиная с 10-летнего возраста,
программированию на языке Си-шарп.
Надо отметить, что видеокурс очень короткий, поэтому пользы от него будет немного. Впрочем, нам никто и ничто не мешает использовать Черепашку-курсистку
в своих проектах (Рис. 7)!
131
Рис. 4. Другая книга для детей
Рис. 5. Закрашенная звёздочка
132
Рис. 6. Всё лучшее – детям!
Рис. 7. Ломаные спирали
133
Для этого добавьте ссылку на динамическую библиотеку SmallBasicFun.dll:
{$reference 'LIB/SmallBasicLibrary.dll'}
{$reference 'LIB/SmallBasicFun.dll'}
uses
Microsoft.SmallBasic.Library, SmallBasicFun;
Вообще говоря, в библиотеке SmallBasicFun не одна, а две Черепашки – БОЛЬШАЯ
и маленькая. Большая Черепаха, хотя и большая, но довольно глупая: она умеет
рисовать только квадраты и правильные многоугольники (Рис. 8). Для этого она
имеет 2 процедуры:
GiantTortoise.DrawSquare(color : string);
GiantTortoise.DrawShape(sides : integer; color : string);
Рис. 8. Многоугольники большой Черепашки
134
Процедура DrawSquare помогает вычертить Черепахе квадрат со сторонами заданного цвета. Обратите внимание, что можно задать случайный цвет 'random', и
тогда все стороны квадрата будут вычерчены линиями случайного цвета. Ни размеры квадрата, ни его положение на канве изменить нельзя.
Эта процедура использует для рисования квадрата другую процедуру DrawShape, которой она передаёт значение параметра sides = 4.
Вы можете задать другое значение этому параметру, и Черепаха послушно вычертит правильный многоугольник:
{$reference 'LIB2/SmallBasicLibrary.dll'}
{$reference 'LIB2/SmallBasicFun.dll'}
uses
Microsoft.SmallBasic.Library, SmallBasicFun;
type gw = GraphicsWindow;
// размеры окна:
const GW_WIDTH = 320;
const GW_HEIGHT = 320;
const CX = GW_WIDTH div 2;
const CY = GW_HEIGHT div 2;
procedure Prepare();
begin
gw.Hide();
gw.Title := 'Очень БОЛЬШАЯ Черепашка';
gw.Width := GW_WIDTH;
gw.Height := GW_HEIGHT;
gw.Show();
gw.Left := (Desktop.Width - gw.Width) / 2;
gw.Top := (Desktop.Height - gw.Height) / 2;
gw.BackgroundColor := 'SeaShell';
end;
// Квадрат
procedure Quadrat();
135
begin
// рисуем квадрат:
GiantTortoise.DrawSquare(Colors.Red);
GiantTortoise.DrawSquare('random');
end;
// Многоугольник
procedure Polygon();
begin
// рисуем многоугольник:
GiantTortoise.DrawShape(9, 'random');
end;
begin
Prepare;
Quadrat();
//Polygon();
end.
Библиотечная Черепашка
Вторая Черепашка – маленькая, да удаленькая! Она может выполнять всё, что умеет делать и стандартная Черепашка, и кое-что у
неё есть в запасе!
Чтобы Черепашка появилась на экране, нужно выполнить процедуру Show:
Tortoise.Show();
Процедура Hide, наоборот, скрывает Черепашку, но при этом она продолжает выполнять все команды:
136
Tortoise.Hide();
Процедура Reset возвращает Черепашке параметры по умолчанию:
Tortoise.Reset();
Две процедуры – SetX и SetY – переносят Черепашку в указанную точку клиентской
области окна без вычерчивания линии:
Tortoise.SetX(x : double);
Tortoise.SetY(y : double);
Процедура SetPosition одновременно изменяет обе координаты Черепашки:
Tortoise.SetPosition(x, y : integer);
Две других функции – GetX и GetY – сообщают нам текущие координаты Черепашки:
Tortoise.GetX() : double;
Tortoise.GetY() : double;
Теперь мы можем создать Черепашку и поставить её перед фактом в самом центре окна (Рис. 1).
137
Рис. 1. Маленькая Черепашка в центре внимания
{$reference 'LIB2/SmallBasicLibrary.dll'}
{$reference 'LIB2/SmallBasicFun.dll'}
uses
Microsoft.SmallBasic.Library, SmallBasicFun;
type gw = GraphicsWindow;
// размеры окна:
const GW_WIDTH = 640;
const GW_HEIGHT = 640;
const CX = GW_WIDTH div 2;
const CY = GW_HEIGHT div 2;
procedure Prepare();
begin
gw.Hide();
gw.Title := 'Библиотечная Черепашка';
gw.Width := GW_WIDTH;
gw.Height := GW_HEIGHT;
gw.Show();
138
gw.Left := (Desktop.Width - gw.Width) / 2;
gw.Top := (Desktop.Height - gw.Height) / 2;
gw.BackgroundColor := 'SeaShell';
// создаём Черепашку:
Tortoise.Show();
// устанавливаем Черепашку в центре окна:
Tortoise.SetX(CX);
Tortoise.SetY(CY);
end;
. . .
begin
Prepare;
// треугольная спираль:
Spiral(600, 120, 8);
end.
Зная, что все Черепашки любят выделывать спирали, мы не можем отказать и
нашей Черепашке в этом удовольствии!
Процедура Move заставляет двигаться её вперёд на lengthInPixels пикселей:
Tortoise.Move(lengthInPixels : double);
Процедура MoveTo перемещает Черепашку в указанную точку с координатами
(x,y):
Tortoise.MoveTo(x, y : double);
Процедура Turn поворачивает Черепашку на degrees градусов: по часовой
стрелке, если угол положительный, или против часовой стрелки, если угол отрицательный:
Tortoise.Turn(degrees : double);
139
Если предыдущая процедура отсчитывает угол от текущего поворота Черепашки,
то процедура SetAngle поворачивает Черепашку точно в заданном направлении.
Если angle = 0, то Черепашка смотрит вверх, если angle = 180, то Черепашка смотрит вниз:
Tortoise.SetAngle (angle : double);
Функция GetAngle возвращает текущий угол поворота Черепашки:
Tortoise.GetAngle() : double;
Функция SetOrientation объединяет усилия двух методов – SetPosition и SetAngle:
Tortoise. SetOrientation( x, y, angle : integer) : double;
Однако вернёмся к спиралям и напишем две простые процедуры для их вычерчивания:
procedure Step(len, angle: double);
begin
Tortoise.Move(len);
Tortoise.Turn(angle);
end;
procedure Spiral(len, angle, _step: double);
begin
// устанавливаем Черепашку:
Tortoise.SetX(70);
Tortoise.SetY(CY + 300);
Tortoise.SetSpeed(10);
Tortoise.SetPenColor('Green');
var i := len;
while(i >= 10) do begin
Step(i, angle);
i -= _step;
140
end;
end;
Задавая разные значения параметру angle, вы будете получать очень красивые
многоугольные спирали (Рис. 2-5):
// треугольная спираль:
Spiral(600, 120, 8);
// квадратная спираль:
Spiral(620, 90, 4);
// пятиугольная спираль:
Tortoise.SetAngle(-18);
Spiral(380, 72, 2);
// шестиугольная спираль:
Spiral(320, 60, 1.5);
Ещё более интересные спирали получаются, если задавать «неправильный» угол.
Тогда спирали дополнительно закручиваются относительно центра (Рис. 6-10):
// треугольная спираль с закруткой:
Spiral(620, 120 + 1, 8);
// квадратная спираль с закруткой:
Spiral(620, 90+0.5, 4);
// квадратная спираль
Spiral(620, 90+1, 4);
с закруткой:
141
// квадратная спираль
Spiral(580, 90+2, 8);
с закруткой:
Рис. 2. Треугольная спираль
142
Рис. 3. Квадратная спираль
143
Рис. 4. Пятиугольная спираль
144
Рис. 5. Шестиугольная спираль
145
Рис. 6. Треугольная спираль с закруткой
146
Рис. 7. Квадратная спираль с закруткой
147
Рис. 8. Вторая квадратная спираль с закруткой
148
Рис. 9. Третья квадратная спираль с закруткой
// шестиугольная спираль с закруткой:
Spiral(320, 60.3, 1.5);
149
Рис. 10. Шестиугольная спираль с закруткой
150
Процедура SetPenColor устанавливает цвет линий:
Tortoise. SetPenColor(color : string);
А процедура SetPenWidth – их толщину:
Tortoise. SetPenWidth(widthInPixels : double);
Функции GetPenColor и GetPenWidth возвращают текущий цвет линий и их толщину:
Tortoise.GetPenColor() : string;
Tortoise.GetPenWidth() : double;
Процедура PenDown опускает карандаш, и тогда Черепашка оставляет за собой
след, а процедура PenUp поднимает его, и Черепашка заметает следы:
Tortoise.PenDown();
Tortoise.PenUp();
Процедура SetSpeed задаёт перцу и скорости Черепашке. Самая медленная скорость – при значении параметра speed, равном 1, самая быстрая – при значении
speed, равном 10:
Tortoise. SetSpeed(speed : integer);
151
Когда нужно максимально ускорить Черепашку, следует воспользоваться функцией InstantSpeed с параметром goFast = true:
Tortoise.InstantSpeed( goFast : boolean);
Функция GetSpeed возвращает текущую скорость Черепашки:
Tortoise.GetSpeed() : integer;
А теперь давайте вычертим фрезу:
// фреза
procedure Squaggle(zoom, z: double);
begin
Tortoise.Move(50*zoom);
Tortoise.Turn(150/z);
Tortoise.Move(60*zoom);
Tortoise.Turn(100/z);
Tortoise.Move(30*zoom);
Tortoise.Turn(90/z);
end;
procedure Squaggle(n: integer; zoom: double; z: double);
begin
Tortoise.SetSpeed(10);
Tortoise.SetX(CX-180);
Tortoise.SetY(CY);
Tortoise.SetPenColor('Green');
loop n do
Squaggle(zoom,z);
end;
Малозубая фреза (Рис. 11):
// фреза:
Squaggle(20, 5, 1);
152
Рис. 11. Фреза с зубами
// фреза:
153
Squaggle(77, 5.5, 1.01);
Рис. 12. Зубастая фреза
154
А можно и немного почеркаться прямо в окне приложения. Запускаем программу
и водим мыша за нос (Рис. 13).
Рис. 13. Черепашьи почеркушки
155
// почеркушки:
Inspi();
// почеркушки
procedure Inspi(side, angle, inc: double);
begin
for var i := 0 to 10 - 1 do begin
Tortoise.Move(side);
Tortoise.Turn(angle + inc * i);
end;
end;
procedure Inspi();
begin
Tortoise.SetX(CX);
Tortoise.SetY(CY);
Tortoise.SetSpeed(10);
while(true) do begin
Tortoise.SetX(gw.MouseX);
Tortoise.SetY(gw.MouseY);
Tortoise.SetPenColor(gw.GetRandomColor());
Inspi(100, 150, 1.5);
end;
end;
156
Блуждающие Черепашки
Обе Черепашки – и большая, и маленькая – ведут свой род от совершенно дикой Черепашки из класса MyTurtle. Она плохо поддаётся дрессировке, но у неё есть одна способность,
которая напрочь отсутствует у её одомашненных родственниц, - она умеет
плодиться. И при создании каждой новой Черепашки ей можно задать свой
любимый размерчик.
Давайте создадим для начала пару
разнокалиберных Черепашек:
{$reference 'LIB2/SmallBasicLibrary.dll'}
{$reference 'LIB2/SmallBasicFun.dll'}
uses
Microsoft.SmallBasic.Library, SmallBasicFun;
type gw = GraphicsWindow;
// размеры окна:
const GW_WIDTH = 320;
const GW_HEIGHT = 320;
const CX = GW_WIDTH div 2;
const CY = GW_HEIGHT div 2;
procedure Prepare();
begin
gw.Hide();
gw.Title := 'Блуждающие Черепашки';
gw.Width := GW_WIDTH;
gw.Height := GW_HEIGHT;
gw.Show();
157
gw.Left := (Desktop.Width - gw.Width) / 2;
gw.Top := (Desktop.Height - gw.Height) / 2;
gw.BackgroundColor := 'SeaShell';
// создаём первую Черепашку:
var turtle1 := new MyTurtle();
turtle1.Show();
// устанавливаем Черепашку в центре окна:
turtle1.X := CX;
turtle1.Y := CY;
// создаём вторую Черепашку:
var turtle2 := new MyTurtle(1.6);
turtle2.Show();
turtle2.X := CX + 20;
turtle2.Y := CY + 20;
end;
begin
Prepare;
end.
Как вы видите, дело это совершенно нехитрое – и вот уже новорождённые Черепашки предстали перед нашими глазами (Рис. 1).
Немного поднатужившись, мы сможем породить и 36 Черепашек – что мы глупее
немцев с их питоновыми Черепашками?
Но породить мало – нужно ещё придать Черепашкам правильное направление и
дать им крепкого родительского пинка под зад, чтобы они разбежались во все
стороны:
procedure Running();
begin
var turtles := new MyTurtle[36];
for var i := 0 to 36 - 1 do begin
turtles[i] := new MyTurtle(1.6);
turtles[i].X := CX;
158
turtles[i].Y := CY;
turtles[i].Angle := i * 360 / 36;
turtles[i].Speed := 10;
turtles[i].Show();
end;
foreach var t in turtles do
t.Move(300);
end;
Рис. 1. Двойняшки-Черепашки
Моментальное семейное фото с места событий демонстрирует полную победу
наших Черепашек над иноземными панцерами (Рис. 2).
А сейчас давайте смоделируем ситуацию поиска жизненного пути неориентированными в жизни созданиями.
159
Рис. 2. Фигурно!
В процедуре RandomWalk мы производим на свет 12 юных Черепашек, которые
тут же пускаются в бесконечный путь познания добра и зла:
160
procedure RandomWalk();
begin
var turtles := new MyTurtle[12];
for var i := 0 to 12 - 1 do begin
turtles[i] := new MyTurtle(1.2);
turtles[i].X := Random(CX - 50, CX + 50);
turtles[i].Y := Random(CY - 50, CY + 50);
turtles[i].LineColor := gw.GetRandomColor().AsColor();
turtles[i].Speed := 10;
turtles[i].Show();
end;
while(true) do
for var i := 0 to 12 - 1 do begin
turtles[i].Angle := Random(360);
turtles[i].Move(Random(10, 50));
end;
end;
Поскольку ума у них ещё нет, а рвения хоть отбавляй, то они будут суетиться, ища
место под солнцем, а их жизненный путь предстанет перед нашими глазами в
виде перепутанного клубка нитей или своеобразного броуновского движения, которое англосаксоны называют случайным блужданием (Рис. 3).
А чего больше в этом эксперименте - физики или педагогики, - решайте сами!
Если увеличить толщину линий, то Черепашки натворят настоящую абстрактную
картину (Рис. 4):
turtles[i].LineWidth := 20;
161
Рис. 3. Эх, куда глаза глядят!
162
Рис. 4. Служенье муз не терпит суеты…
163
Черепашка из Small Visual Basic
Вы уже хорошо знакомы с Черепашкой из библиотеки SmallBasic. Эта Черепашка
не остановилась в своём развитии и эволюционировала дальше.
Сам Small Basic превратился в Small Visual Basic (Рис. 1).
Рис. 1. Эволюцию не остановишь
Новая программа добавила слово Visual к названию родительской программы,
поскольку написана на языке Visual Basic. И установщик программы, и исходники
вы легко найдёте в Интернете.
Все программы, использующие библиотеку SmallVisualBasicLibrary.dll должны
начинаться с такого кода:
164
{$reference SmallVisualBasicLibrary.dll}
uses Microsoft.SmallVisualBasic.Library;
type gw = GraphicsWindow;
Саму библиотеку скопируйте в папку со своими программами.
Многоугольники
Вы уже знаете, что любая Черепашка с лёгкостью начертит любой многоугольник.
Умеет это делать и наша новая знакомица. Вот так лихо она начертила правильный треугольник (Рис. 1):
{$reference SmallVisualBasicLibrary.dll}
uses Microsoft.SmallVisualBasic.Library;
type gw = GraphicsWindow;
// размеры окна:
const GW_WIDTH = 640;
const GW_HEIGHT = 640;
const CX = GW_WIDTH div 2;
const CY = GW_HEIGHT div 2;
procedure Prepare();
begin
gw.Hide();
gw.Title := 'Многоугольники';
gw.Width := GW_WIDTH;
gw.Height := GW_HEIGHT;
gw.Show();
gw.Left := (Desktop.Width - gw.Width) / 2;
gw.Top := (Desktop.Height - gw.Height) / 2;
gw.BackgroundColor := 'SeaShell';
// цвет линий:
gw.PenColor := 'Red';
165
// толщина линий:
gw.PenWidth := 3;
// цвет заливки фигур:
gw.BrushColor := 'Yellow';
// скорость Черепашки:
Turtle.Speed := 9;
end;
// треугольник
procedure Triangle;
begin
Turtle.X := 10;
Turtle.Y := GW_HEIGHT-10;
Turtle.Show;
Turtle.Turn(90);
var r := GW_WIDTH - 20;
loop 3 do begin
Turtle.Move(r);
Turtle.Turn(-120);
end;
end;
begin
Prepare;
Triangle;
end.
Треугольник весьма хорош, а из кода можно узнать, что свойство Turtle.Speed задаёт скорость (прыткость) ползания Черепашки. Свойства Turtle.X и Turtle.Y телепортируют Черепашку в точку с заданными координатами. Процедура Turtle.Show
показывает Черепашку на экране, а процедура Turtle.Hide скрывает её. Процедура
Turtle.Turn(angle) поворачивает Черепашку на заданный угол. Если угол положительный, то по часовой стрелке, если отрицательный – в обратном направлении.
Процедура Turtle.Move(disrtance) двигает Черепашку вперёд на заданное расстояние.
166
Рис. 1. Визуальный треугольник
Цвет и толщина пера, как и раньше, определяются свойствами PenColor и
PenWidth:
167
// цвет линий:
gw.PenColor := 'Red';
// толщина линий:
gw.PenWidth := 3;
Мы также выбрали жёлтый цвет для закрашивания фигур:
// цвет заливки фигур:
gw.BrushColor := 'Yellow';
Вы, конечно, заметили, что ни одна наша Черепашка не умеет закрашивать фигуры, а только чертит контуры. Новая Черепашка освоила не только чертёжные,
но и малярные работы!
Пишем новую процедуру:
// ЗАКРАШЕННЫЙ ТРЕУГОЛЬНИК
procedure TriangleFill;
begin
Turtle.X := 10;
Turtle.Y := GW_HEIGHT-10;
Turtle.Show;
Turtle.Turn(90);
var r := GW_WIDTH - 20;
Turtle.CreateFigure;
loop 3 do begin
Turtle.Move(r);
Turtle.Turn(-120);
end;
Turtle.FillFigure();
end;
Процедура Turtle.CreateFigure должна предшествовать рисованию замкнутой фигуры, а процедура Turtle.FillFigure после этого закрашивает фигуру заданным цветом. Рис. 2 наглядно показывает, что закрашенные фигуры гораздо красивее пустых, а соединения линий более качественные.
168
Рис. 2. Закрашенный треугольник
Если Черепашка начертит только 2 стороны треугольника, то фигура всё равно будет правильно закрашена, как будто у неё есть третья, невидимая сторона (Рис.
3).
169
Рис. 3. Закрашенный угол
170
Точно так же Черепашка вычертит и красный квадрат с жёлтой заливкой. Но давайте вспомним замечательный квадрат Малевича, который совсем чёрный и без контура.
Убираем контурные линии и макаем кисточку в
чёрную краску:
// ЗАКРАШЕННЫЙ КВАДРАТ
procedure QuadratFill;
begin
// толщина линий:
gw.PenWidth := 0;
// цвет заливки фигур:
gw.BrushColor := 'Black';
Turtle.X := 10;
Turtle.Y := GW_HEIGHT-10;
Turtle.Show;
Turtle.Turn(90);
var r := GW_WIDTH - 20;
Turtle.CreateFigure;
loop 4 do begin
Turtle.Move(r);
Turtle.Turn(-90);
end;
Turtle.FillFigure();
end;
Запускаем программу – и черепаший шедевр готов (Рис. 4).
Если число сторон многоугольника достаточно велико, то Черепашка начертит
круг (Рис. 5).
171
Рис. 4. Рисуем по-чёрному
172
Рис. 5. Черепаший круговорот
173
Солнечный круг,
Небо вокруг…
Лев Ошанин
Пусть всегда будет солнце
А вот и звезда по имени Солнце (Рис. 6):
// окружность
procedure CircleFill;
begin
gw.BackgroundColor := 'DodgerBlue';
// толщина линий:
gw.PenWidth := 0;
// цвет заливки фигур:
gw.BrushColor := 'Yellow';
Turtle.X := GW_WIDTH - 15;
Turtle.Y := GW_HEIGHT / 2 - 10;
var r := 24;
var n := 80;
Turtle.CreateFigure;
loop n do begin
Turtle.Turn(-360/n);
Turtle.Move(r);
end;
Turtle.FillFigure();
end;
174
Рис. 6. Солнечный круг
175
Полярные кривые
Гораздо удобнее чертить окружность по координатам её центра и по длине радиуса. Координаты точек окружности вычисляем по формулам:
x = R * cos(Z)
(1)
y = R * sin(Z)
(2)
Точки последовательно соединяем отрезками прямых:
// окружность
procedure DrawCircle(xc,yc,r : double; n : integer := 80);
begin
// текущий полярный угол:
var z := 0.0;
// начальная позиция Черепашки:
Turtle.X := xc + r;
Turtle.Y := yc;
Turtle.CreateFigure;
loop n + 1 do begin
// следующие координаты Черепашки:
var x := xc + r * cos(z);
var y := yc + r * sin(z);
// чертим отрезок:
Turtle.MoveTo(x,y);
// следующий угол:
z += 2 * PI / n;
end;
Turtle.FillFigure();
end;
// толщина линий:
gw.PenWidth := 2;
// цвет заливки фигур:
gw.BrushColor := 'Yellow';
176
DrawCircle(CX, CY, 300);
Результат мы получим тот же самый, но более геометрически (Рис. 1).
Рис. 1. Солнечный круг – версия 2
177
У Черепашки в лапках оказался мощный чертёжный инструмент, и теперь она запросто вычертит разноцветные концентрические круги (Рис. 2):
// толщина линий:
gw.PenWidth := 2;
gw.PenColor := 'Black';
// скорость Черепашки:
Turtle.Speed := 100;
for var i := 0 to 15 do begin
// цвет заливки фигур:
gw.BrushColor := gw.GetRandomColor;
DrawCircle(CX, CY, 310 - 20 * i);
end;
Для большего художественного эффекта можно изменять радиусы кругов случайным образом (Рис. 3):
// толщина линий:
gw.PenWidth := 2;
gw.PenColor := 'Black';
// скорость Черепашки:
Turtle.Speed := 100;
var dr := 0.0;
var r := 310.0;
while dr <= r - 20 do begin
// цвет заливки фигур:
gw.BrushColor := gw.GetRandomColor;
DrawCircle(CX, CY, r - dr);
dr += Random(20, 60);
if r - dr < 20 then dr := dr - 20;
end;
178
Рис. 2. Концентрические круги
179
Рис. 3. Дело случая
Замечательно смотрятся эксцентрические круги (Рис. 4):
// толщина линий:
180
gw.PenWidth := 2;
gw.PenColor := 'Black';
// скорость Черепашки:
Turtle.Speed := 100;
var dr := 0.0;
var r := 310.0;
for var i := 0 to 7 do begin
// цвет заливки фигур:
gw.BrushColor := gw.GetRandomColor;
DrawCircle(CX + 20 * i, CY - 20 * i, r - 40 * i);
end;
Любимая всеми Черепашками красно-жёлтая раскраска кругов (Рис. 5):
// толщина линий:
gw.PenWidth := 1.0;
gw.PenColor := 'Black';
// скорость Черепашки:
Turtle.Speed := 100;
var dr := 0.0;
var r := 310.0;
var color1 := 'Red';
var color2 := 'Yellow';
for var i := 0 to 15 do begin
var color := i mod 2 = 0 ? color1 : color2;
// цвет заливки фигур:
gw.BrushColor := color;
DrawCircle(CX + 10 * i, CY - 10 * i, r - 20 * i);
end;
Чертим розетки (Рис. 6-8).
// чертим розетки:
DrawRosette(60, 0);
// чертим розетки:
DrawRosette(40, 40);
181
// чертим розетки:
DrawRosette(50, 80);
Рис. 4. Эксцентрические круги
182
Рис. 5. Становится жарко
183
Рис. 6. Черепашья розетка
184
Рис. 7. Черепаший розеточный бублик
185
Рис. 8. Черепашья розеточная баранка
Совмещаем приятное для глаз с полезным для ума (Рис. 9):
// чертим розетки:
186
DrawRosette(90, 60, true);
Рис. 9. Позолотили
187
После иллюстраций код, производящий эту геометрическую красоту, должен
быть вам абсолютно понятен:
procedure Circle(xc,yc,r : double; n : integer := 80);
begin
// текущий полярный угол:
var z := 0.0;
// приращение угла:
var dz := DegToRad(360/n);
// начальная позиция Черепашки:
Turtle.X := xc + r;
Turtle.Y := yc;
loop n + 1 do begin
// следующие координаты Черепашки:
var x := xc + r * cos(z);
var y := yc + r * sin(z);
// чертим отрезок:
Turtle.MoveTo(x,y);
// следующий угол:
z += dz;
end;
end;
// ЧЕРТИМ РОЗЕТКУ
procedure DrawRosette(n, offset: integer; fill : boolean := false);
begin
// скорость Черепашки:
Turtle.Speed := 100;
// радиус:
var r := GW_WIDTH / 4 - 5 - offset / 2;
if fill then begin
// толщина линий:
gw.PenWidth := 2;
gw.BrushColor := 'Yellow';
gw.PenColor := 'Red';
DrawCircle(CX, CY, GW_WIDTH / 2 - 5);
end;
188
// толщина линий:
gw.PenWidth := 1.8;//1.5;
gw.PenColor := 'DarkGreen';
var angle := 0.0;
var dAngle := DegToRad(360/n);
loop n do begin
var xc := CX + Cos(angle) * r + offset * Cos(angle);
var yc := CY + Sin(angle) * r + offset * Sin(angle);
Circle(xc,yc,r);
angle += dAngle;
end;
end;
Улитка Паскаля
Надоело мне уже
Лопать ваше бланманже
Приготовьте для царя
Сто улиток Паскаля!
Новофилатов, Капризный
Царь
По названию кривой
можно было бы предположить, что она названа в
честь знаменитого французского физика Блеза
Паскаля, но это не так. На
самом деле, его опередил отец и математик в одном лице - Этьен Паскаль (Étienne Pascal (1588 - 1651)).
Вот так-то: принято считать, что природа отдыхает на детях, но отец и сын Паскали
опровергли этот постыдный вздор.
189
Улитку Паскаля называют также «по-латински» лимаконом Паскаля (Limacon) - от настоящего латинского слова
limax, которое и значит улитка. Само крещение кривой
произошло в 1650 году французским математиком, астрономом и физиком Жилем Робервалем (Рис. 1) (настоящая
его фамилия - Персонье или Персон, Personier, Personne).
Рис. 1. Gilles Personne de Roberval (1602-1675)
Кроме интересной формы, улитка Паскаля обладает и
«магическим» свойством - с её помощью можно решить
одну из задач древности – о трисекции угла. Правда, реше-
190
ние будет уже не классическим, поскольку в оригинале требуется все построения выполнять только циркулем и линейкой. Однако с этими правилами задача неразрешима.
Улитка Паскаля – частный случай эпитрохоиды.
Вычерчиваем улитку (Рис. 2) и с трудом узнаём в этой петле заявленную выше
«зверушку»:
{$reference SmallVisualBasicLibrary.dll}
uses Microsoft.SmallVisualBasic.Library;
type
gw = GraphicsWindow;
// размеры окна:
const
GW_WIDTH = 640;
const
GW_HEIGHT = 640;
const
CX = GW_WIDTH div 2;
const
CY = GW_HEIGHT div 2;
procedure Prepare();
begin
gw.Hide();
gw.Title := 'Улитка Паскаля';
gw.Width := GW_WIDTH;
gw.Height := GW_HEIGHT;
gw.Show();
gw.Left := (Desktop.Width - gw.Width) / 2;
gw.Top := (Desktop.Height - gw.Height) / 2;
gw.BackgroundColor := 'SeaShell';
end;
191
procedure DrawUlitka();
begin
// скорость Черепашки:
Turtle.Speed := 100;
// толщина линий:
gw.PenWidth := 1.8;
gw.PenColor := 'DarkGreen';
// Улитка Паскаля:
var k := 1.0;
var l := 2.0;
var r := 90;
var n := 120;
var angle := 0.0;
var dAngle := DegToRad(360 / n);
var offset := 0.0;
var x0: double := 70;
var y0: double := CY;
for var i := 0 to n + 1 do
begin
// радиус:
var rz := (1 + l * Cos(k * angle)) * r;
var x := x0 + l * rz * Cos(angle) + k * Cos(l * angle);
var y := y0 + l * rz * Sin(angle) + k * Sin(l * angle);
// первая точка:
if i = 0 then begin
Turtle.X := x;
Turtle.Y := y;
continue;
end;
// чертим отрезок:
Turtle.MoveTo(x, y);
angle += dAngle;
end;
end;
begin
Prepare;
192
DrawUlitka();
end.
Рис. 2. Улитка Паскаля
193
При других параметрах кривой мы вместо улитки Паскаля получим петельное
сцепление (Рис. 3) и кардиоиду (название произошло от греческого слова καρδιά
(кардиа) - сердце), которая, скорее, напоминает помидор сорта бычье сердце
(Рис. 4), чем настоящий пламенный мотор (Рис. 5).
Заливку сложных фигур Черепашка делает отвратительно, поэтому мы пойдём на
хитрость: сначала начертим заливную фигуру без контура, а затем – контур без
заливки:
procedure DrawPetlya(fill: boolean := false);
begin
// скорость Черепашки:
Turtle.Speed := 100;
// толщина линий:
if fill then gw.PenWidth := 0
else gw.PenWidth := 1.8;
gw.PenColor := 'DarkGreen';
// цвет заливки фигур:
gw.BrushColor := 'Yellow';
// петельное сцепление:
var k := 2.0;
var l := 2.0;
var
var
var
var
var
var
var
r := 52;
n := 150;
angle := 0.0;
dAngle := DegToRad(360 / n);
offset := 0.0;
x0: double := 315;
y0: double := CY;
Turtle.CreateFigure;
for var i := 0 to n + 2 do
begin
// радиус:
var rz := (1 + l * Cos(k * angle)) * r;
var x := x0 + l * rz * Cos(angle) + k * Cos(l * angle);
var y := y0 + l * rz * Sin(angle) + k * Sin(l * angle);
// первая точка:
194
if i = 0 then begin
Turtle.X := x;
Turtle.Y := y;
continue;
end;
// чертим отрезок:
Turtle.MoveTo(x, y);
angle += dAngle;
end;
if fill then Turtle.FillFigure();
end;
begin
Prepare;
//DrawUlitka();
DrawPetlya(true);
DrawPetlya();
end.
Петля получилась великолепно!
По такому же сценарию строим кардиоиду:
procedure DrawCardioid(fill: boolean := false);
begin
// скорость Черепашки:
Turtle.Speed := 100;
// толщина линий:
if fill then gw.PenWidth := 0
else gw.PenWidth := 2.8;
gw.PenColor := 'DarkGreen';
// цвет заливки фигур:
gw.BrushColor := 'Red';
// кардиоида:
var k := 1.0;
var l := 1.0;
195
var
var
var
var
var
r := 240;
n := 150;
angle := 0.0;
dAngle := DegToRad(360 / n);
offset := 0.0;
var x0: double := 115;
var y0: double := CY;
Turtle.CreateFigure;
for var i := 0 to n + 2 do
begin
// радиус:
var rz := (1 + l * Cos(k * angle)) * r;
var x := x0 + l * rz * Cos(angle) + k * Cos(l * angle);
var y := y0 + l * rz * Sin(angle) + k * Sin(l * angle);
// первая точка:
if i = 0 then begin
Turtle.X := x;
Turtle.Y := y;
continue;
end;
// чертим отрезок:
Turtle.MoveTo(x, y);
angle += dAngle;
end;
if fill then Turtle.FillFigure();
end;
begin
Prepare;
//DrawUlitka();
//
DrawPetlya(true);
//
DrawPetlya();
DrawCardioid(true);
DrawCardioid();
end.
196
Рис. 3. Петельное сцепление
197
Рис. 4. Кардиоида
198
Рис. 5. Помидоры бычачьих форм
Спирали
В нашем деле главное – этот самый, реализьм!
Кредо Шефа в интерпретации Лёлика,
Кинокомедия Бриллиантовая рука
У нас дела пойдут куда лучше, если мы представим себе улитку в виде спирали, на которую раковины многих одностворчатых моллюсков
очень похожи.
Мы не будь дураками окучим сразу две спирали
– архимедову и логарифмическую.
199
Свойства первой спирали досконально изучил древнегреческий математик, физик, механик и инженер из Сиракуз гражданин Архимед (Рис. 1). Чем и обессмертил эту загогулину.
Дословный перевод его имени – Супер-ум, так что скромностью его явно обнесли.
Наверное, Архимед больше знаком вам по его знаменитому
кличу Эврика! (по-гречески εὕρηκα значит нашёл), которым
он выразил свою радость по случаю открытия гидростатического закона (он же школьный закон Архимеда).
Рис. 1. Ἀρχιμήδης; 287 до н. э. — 212 до н. э.
Формула архимедовой спирали очень проста, поэтому мы закодируем её в один
приём (Рис. 2):
200
{$reference SmallVisualBasicLibrary.dll}
uses Microsoft.SmallVisualBasic.Library;
type
gw = GraphicsWindow;
// размеры окна:
const
GW_WIDTH = 640;
GW_HEIGHT = 640;
CX = GW_WIDTH div 2;
CY = GW_HEIGHT div 2;
procedure Prepare();
begin
gw.Hide();
gw.Title := 'Спирали';
gw.Width := GW_WIDTH;
gw.Height := GW_HEIGHT;
gw.Show();
gw.Left := (Desktop.Width - gw.Width) / 2;
gw.Top := (Desktop.Height - gw.Height) / 2;
gw.BackgroundColor := 'SeaShell';
end;
procedure DrawSpiral();
begin
// скорость Черепашки:
Turtle.Speed := 100;
// толщина линий:
gw.PenWidth := 2.0;
gw.PenColor := 'DarkGreen';
var k := 5.0;
var
var
var
var
var
var
r := 90;
n := 240;
angle := 0.0;
dAngle := DegToRad(1080 / n);
x0: double := 290;
y0: double := CY;
201
for var i := 0 to n + 1 do
begin
// радиус:
var rz := angle / k * r;
var x := x0 + Cos(angle) * rz;
var y := y0 + Sin(angle) * rz;
// первая точка:
if i = 0 then begin
Turtle.X := x;
Turtle.Y := y;
continue;
end;
// чертим отрезок:
Turtle.MoveTo(x, y);
angle += dAngle;
end;
end;
begin
Prepare;
DrawSpiral;
end.
Но - одинокая спираль наводит тоску на жаждущую веселья душу, поэтому мы
лучше одновременно закрутим несколько спиралей (Рис. 3-5 ).
Зададимся риторическим вопросом: а похожа ли архимедова спираль на улитку,
как мы изначально надеялись? – И честно ответим себе: ни фига она не похожа!
Забавные спиральки получаются при больших значениях толщины линий (Рис. 6):
procedure DrawSpiral2(nv : integer);
begin
// скорость Черепашки:
Turtle.Speed := 100;
// толщина линий:
gw.PenWidth := 24.0;
gw.PenColor := 'DarkGreen';
202
var k := 5.0;
// поворот спирали:
var da := 0.0;
loop nv do begin
var r := 82;
var n := 1600;
. . .
И даже одноцветные спиральки по-своему интересны (Рис. 7).
Конечно, Пабло Пикассо и другим абстракционистам именно так улитка и видится
в их высокохудожественных грёзах (Рис. 8), но, поверьте, она совсем не такая!
203
Рис. 2. Эврика: Архимедова спираль наверчена!
204
Рис. 3. 3 Архимедовых спирали
205
Рис. 4. 4 Архимедовых спирали
206
Рис. 5. Клубок из шести спиралей
207
Рис. 6. Толстые спирали
208
Рис. 7. Вегетарианские спирали
209
Рис. 8. Почти абстрактная улитка
Архимедову спираль активно используют питоны и другие
удавы в своих акробатических упражнениях (Рис. 9).
Рис. 9. Сетчатый питон
210
Примером почти идеальной архимедовой спирали могут
служить аккуратно свернутые шланги и пожарные рукава
(Рис. 10).
Рис. 10. Архимедовы шланги
Русское слово шланг произошло от немецкого Schlange –
змея. А вот собственно шланг по-немецки называется
Schlauch. То есть мы внешнее сходство между шлангами и
змеями видим, а немцы нет. Зато немцы в очереди (тоже
Schlange) видят именно змею.
Итак, с нами приключилась полуэврика: мы нарисовали не совсем того представителя животного мира, которого хотели. Поэтому давайте вооружимся острым
взором и пристально, взглядом оголодавшего француза, присмотримся к настоящим улиткам (Рис. 11). Присмотрелись? – Вот они такие и есть.
А наш досадный геометрический промах заключается в том, что витки архимедовой спирали раскручиваются равномерно, а улиточные – «с ускорением». Поэтому
для наших бионических экзерсисов больше подойдёт спираль логарифмическая,
которую можно описать двухэтажным уравнением:
211
r = Rekz
Рис. 11. Натуральные улитки
Без особых хлопот мы переведём эту формулу на язык паскаля, который тоже был
француз:
// ЛОГАРИФМИЧЕСКАЯ СПИРАЛЬ
procedure DrawLogSpiral();
begin
// скорость Черепашки:
Turtle.Speed := 100;
// толщина линий:
gw.PenWidth := 1.8;
gw.PenColor := 'DarkGreen';
var
var
var
var
var
var
var
k := 0.14;
r := 10;
n := 240;
angle := 0.0;
dAngle := DegToRad(1080 / n);
x0: double := 290;
y0: double := CY;
while angle < 8.3 * PI do begin
// радиус:
212
var rz := Exp(k * angle) * r;
var x := x0 + Cos(angle) * rz;
var y := y0 + Sin(angle) * rz;
// первая точка:
if angle = 0 then begin
Turtle.X := x;
Turtle.Y := y;
angle += dAngle;
continue;
end;
// чертим отрезок:
Turtle.MoveTo(x, y);
angle += dAngle;
end;
end;
Нажимаем кнопочку заветную – и в лучшем виде получаем знатную логарифмическую спираль, которой позавидует любая, самая съедобная в мире улитка (Рис.
12).
Величину «ускорения» раскрутки витков спирали можно варьировать коэффициентом k. При больших значениях k спирали получаются более улётными и скрываются за горизонтом уже на первом витке.
Спиральная галактика Водоворот
213
Рис. 12. Логарифмическая спираль: k=0.14
214
Поскольку все мы – дети Галактики, то из нескольких логарифмических спиралей
легко создадим вполне реалистическую картину лихо закрученной многозвёздной системы (Рис. 13). Если же мы взглянем на небо до зубов вооружённым глазом, то найдём там немало вот таких звёздных улиток.
procedure DrawLogSpiral(nv : integer);
begin
// скорость Черепашки:
Turtle.Speed := 100;
// толщина линий:
gw.PenWidth := 2.0;
gw.PenColor := 'DarkGreen';
var k := 0.2;
// поворот спирали:
var da := 0.0;
var n := 240;
var r := 9.5;
var dAngle := DegToRad(1080 / n);
var x0: double := 310;
var y0: double := CY;
loop nv do begin
var angle := 0.0;
while angle < 8.0 * PI do begin
// радиус:
var rz := Exp(k * angle) * r;
var x := x0 + Cos(angle + da) * rz;
var y := y0 + Sin(angle + da) * rz;
// первая точка:
if angle = 0 then begin
Turtle.X := x;
Turtle.Y := y;
angle += dAngle;
continue;
end;
// чертим отрезок:
Turtle.MoveTo(x, y);
angle += dAngle;
end;
// случайный цвет спирали:
gw.PenColor := gw.GetRandomColor;
215
da += DegToRad(360 / nv);
end;
end;
Рис. 13. Логарифмическая галактика
216
В одной из спиральных галактик находится и наше Солнце,
и Земля, и мы с вами (Рис. 14). Не правда ли, она очень
похожа на наши логарифмические спирали?
Рис. 14. Галактика Млечный путь в профиль
Со
спиралями
связан
и
трагикомический
случай.
Знаменитый швейцарский математик Якоб Бернулли (Рис.
15)
пожелал,
чтобы
на
его
могиле
была
изображена
логарифмическая спираль.
217
Рис. 15. Jakob Bernoulli (1654 -1705)
Однако за дело взялся второгодник и вместо логарифмической
высек архимедову спираль, которая и по сию пору украшает
нижнюю часть надгробия (Рис. 16). Вокруг спирали можно
прочитать латинскую надпись EADEM MUTATA RESURGO
(изменённая, я вновь воскресаю), которая отражает свойства
именно логарифмической спирали. Вот так-то: математику
учить нужно даже тем, кто хочет зарыть и свой, и чужой
талант в землю!
Рис. 16. Надгробие на могиле Якоба Бернулли
218
Спираль Ферма – это разновидность спирали Архимеда. Иногда её называют
также параболической спиралью.
Эта спираль названа в честь французского математика Пьера де Ферма (Рис. 17).
Рис. 17. Pierre de Fermat, 1607-1665
Координаты точек этой спирали задаются параметрическими уравнениями:
var x := x0 + sign * r * Sqrt(angle) * Cos(angle);
var y := y0 + sign * r * Sqrt(angle) * Sin(angle);
Полная спираль состотит из двух ветвей, котрые начинаются в одной точке, а их
координаты отличаются только знаком sign.
Сначала Черепашка вычерчивает зелёную ветвь, а затем – красную:
DrawFermatSpiral();
DrawFermatSpiral(-1);
219
// СПИРАЛЬ ФЕРМА
procedure DrawFermatSpiral(sign : integer := 1);
begin
// скорость Черепашки:
Turtle.Speed := 100;
// толщина линий:
gw.PenWidth := 1.8;
gw.PenColor := 'DarkGreen';
if sign = -1 then gw.PenColor := 'Red';
var r := 50;
var n := 240;
var dAngle := DegToRad(1080 / n);
var angle := 0.0;
var x0: double := 320;
var y0: double := CY;
while angle < 12 * PI do begin
var x := x0 + sign * r * Sqrt(angle) * Cos(angle);
var y := y0 + sign * r * Sqrt(angle) * Sin(angle);
// первая точка:
if angle = 0 then begin
Turtle.X := x;
Turtle.Y := y;
angle += dAngle;
continue;
end;
// чертим отрезок:
Turtle.MoveTo(x, y);
angle += dAngle;
end;
end;
Получилась весьма изрядная спиральная загогулина (Рис. 18).
220
Рис. 18. Спираль Ферма
221
Лютики-цветочки: клевер, розочки, ромашки
Но не выросла ещё та ромашка,
На которой я себе погадаю.
Песня из к/ф Ещё раз про любовь
Пора нам, однако, от представителей
фауны перейти к более безопасным
представителям флоры, то есть к цветочкам.
Когда-то очень давно итальянский математик и монах Бенедиктинского ордена Луиджи-Гидо Гранди (Рис. 1) написал
вот такое бесхитростное уравнение в полярных координатах:
r = R * sin(kZ)
которое при разных значениях коэффициента k давало кривые, названные в его
честь розами Гвидо Гранди. Нам не составит большого труда нарисовать эти розочки, которые почему-то больше похожи на совсем другие цветочки.
Рис. 1. Luigi Guido Grandi (1671 – 1742)
222
Например, по формуле R= sin(2Z) (Рис. 2) будет вычерчен лист клевера, а по формуле R= sin(12Z) (Рис. 3) – ромашка. Легко понять, что параметр k определяет
число лепестков (их будет в точности 2k).
begin
Prepare;
DrawClever(true);
DrawClever();
end.
// ЧЕРТИМ КЛЕВЕР
procedure DrawClever(fill: boolean := false);
begin
// скорость Черепашки:
Turtle.Speed := 100;
// толщина линий:
if fill then gw.PenWidth := 0
else gw.PenWidth := 1.8;
gw.PenColor := 'DarkGreen';
// цвет заливки фигур:
gw.BrushColor := 'LightGreen';
var r := 400;
var n := 1500;
var dAngle := DegToRad(1080 / n);
var angle := 0.0;
var x0: double := 320;
var y0: double := CY;
// клевер:
var k := 2;
Turtle.CreateFigure;
while angle <= 2 * PI + 0.02 do begin
var rz := Sin(k * angle) * r;
var x := x0 + rz * Cos(angle);
var y := y0 + rz * Sin(angle);
// первая точка:
if angle = 0 then begin
Turtle.X := x;
223
Turtle.Y := y;
angle += dAngle;
continue;
end;
// чертим отрезок:
Turtle.MoveTo(x, y);
angle += dAngle;
end;
if fill then Turtle.FillFigure();
end;
begin
Prepare;
//
DrawClever(true);
//
DrawClever();
DrawRomashka(true);
DrawRomashka;
end.
// ЧЕРТИМ РОМАШКИ
procedure DrawRomashka(fill: boolean := false);
begin
// скорость Черепашки:
Turtle.Speed := 100;
// толщина линий:
if fill then gw.PenWidth := 0
else gw.PenWidth := 1.8;
gw.PenColor := 'DarkGreen';
// цвет заливки фигур:
gw.BrushColor := 'White';
var r := 320;
var n := 1500;
var dAngle := DegToRad(1080 / n);
var angle := 0.0;
var x0: double := 320;
var y0: double := CY;
// ромашка:
224
var k := 12;
Turtle.CreateFigure;
while angle <= 2 * PI + 0.02 do begin
var rz := Sin(k * angle) * r;
var x := x0 + rz * Cos(angle);
var y := y0 + rz * Sin(angle);
// первая точка:
if angle = 0 then begin
Turtle.X := x;
Turtle.Y := y;
angle += dAngle;
continue;
end;
// чертим отрезок:
Turtle.MoveTo(x, y);
angle += dAngle;
end;
if fill then Turtle.FillFigure();
end;
Мы сумеем вычертить и более экзотические цветки, если возьмём более увесистое уравнение:
begin
Prepare;
//
DrawClever(true);
//
DrawClever();
//
//
DrawRomashka(true);
DrawRomashka;
DrawStar(true);
DrawStar();
end.
// ЧЕРТИМ ЗВЁЗДОЧКУ
procedure DrawStar(fill: boolean := false);
begin
// скорость Черепашки:
225
Turtle.Speed := 100;
// толщина линий:
if fill then gw.PenWidth := 0
else gw.PenWidth := 2.8;
gw.PenColor := 'Yellow';
// цвет заливки фигур:
gw.BrushColor := 'Red';
var r := 100;
var n := 1500;
var dAngle := DegToRad(1080 / n);
var angle := 0.0;
var x0: double := 320;
var y0: double := CY;
var k := 5;
//var k := 12;
Turtle.CreateFigure;
while angle <= 2 * PI + 0.02 do begin
var rz := r/(2 + Cos(angle * k + ((Floor(0.1 * angle)/5 Floor(Floor(0.1 * angle)/5))))) * 3;
var x := x0 + rz * Cos(angle);
var y := y0 + rz * Sin(angle);
// первая точка:
if angle = 0 then begin
Turtle.X := x;
Turtle.Y := y;
angle += dAngle;
continue;
end;
// чертим отрезок:
Turtle.MoveTo(x, y);
angle += dAngle;
end;
if fill then Turtle.FillFigure();
end;
226
Рис. 2. Роза Гвидо Гранди при k = 2 (клевер)
227
Рис. 2. Роза Гвидо Гранди при k = 12 (ромашка)
И в этом случае параметр k задает число лепестков (или лучей) цветка, но уже
один к одному (Рис. 3-4).
228
Рис. 3. Одинокая звёздочка при k = 5
229
Рис. 4. Одинокая звёздочка при k = 12
Цветочки станут наряднее, если мы повторим звёздочки несколько раз (Рис. 5-7):
begin
230
Prepare;
//
DrawClever(true);
//
DrawClever();
//
//
DrawRomashka(true);
DrawRomashka;
//
//
DrawStar(true);
DrawStar();
DrawStar(6, 10);
//DrawStar(12, 5);
//DrawStar(12,3);
end.
// ЧЕРТИМ ЗВЁЗДОЧКУ
procedure DrawStar(k, no : integer);
begin
// скорость Черепашки:
Turtle.Speed := 100;
// толщина линий:
gw.PenWidth := 1.8;
gw.PenColor := 'DarkGreen';
var
var
var
var
var
r := 100;
n := 1500;
dAngle := DegToRad(1080 / n);
x0: double := 320;
y0: double := CY;
var da := 0.0;
var dda := DegToRad(no);
loop integer(360 / k / no) do begin
var angle := 0.0;
while angle <= 2 * PI + 0.02 do begin
var rz := r/(2 + Cos(angle * k + ((Floor(0.1 * angle)/5 Floor(Floor(0.1 * angle)/5))))) * 3;
var x := x0 + rz * Cos(angle + da);
var y := y0 + rz * Sin(angle + da);
// первая точка:
231
if angle = 0 then begin
Turtle.X := x;
Turtle.Y := y;
angle += dAngle;
continue;
end;
// чертим отрезок:
Turtle.MoveTo(x, y);
angle += dAngle;
end;
da += dda;
end;
end;
А если мы к поворотам добавим смещения, то доведём наши звёзды до смущения
(Рис. 8).
DrawStar(6, 2, 50);
// ЧЕРТИМ ЗВЁЗДОЧКИ СО СМЕЩЕНИЕМ
procedure DrawStar(k, no, offset: integer);
begin
// скорость Черепашки:
Turtle.Speed := 1000;
// толщина линий:
gw.PenWidth := 1.8;
gw.PenColor := 'DarkGreen';
var
var
var
var
var
r := 85;
n := 1500;
dAngle := DegToRad(1080 / n);
x0: double := 320;
y0: double := CY;
var da := 0.0;
var dda := DegToRad(no);
loop integer(360 / k / no) do begin
var angle := 0.0;
232
while angle <= 2 * PI + 0.02 do begin
var rz := r/(2 + Cos(angle * k + ((Floor(0.1 * angle)/5 Floor(Floor(0.1 * angle)/5))))) * 3;
var x := x0 + rz * Cos(angle + da) + offset * Cos(angle);
var y := y0 + rz * Sin(angle + da) + offset * Sin(angle);
// первая точка:
if angle = 0 then begin
Turtle.X := x;
Turtle.Y := y;
angle += dAngle;
continue;
end;
// чертим отрезок:
Turtle.MoveTo(x, y);
angle += dAngle;
end;
da += dda;
end;
end;
Если чертить звёздочки очень грубо, то они превращаются в правильные треугольники:
// ЧЕРТИМ ЗВЁЗДОЧКИ СО СМЕЩЕНИЕМ
procedure DrawStar2(k, no: integer);
begin
// скорость Черепашки:
Turtle.Speed := 1000;
// толщина линий:
gw.PenWidth := 1.5;
gw.PenColor := 'DarkGreen';
var
var
var
var
var
r := 300;
n := 9;
dAngle := DegToRad(1080 / n);
x0: double := CX;
y0: double := CY;
var da := 0.0;
var dda := DegToRad(no);
233
loop 60 do begin
var angle := 0.0;
while angle <= 2 * PI + 0.02 do begin
var rz := r/(2 + Cos(angle * k + ((Floor(0.1 * angle)/5 Floor(Floor(0.1 * angle)/5))))) * 3;
var x := x0 + rz * Cos(angle + da);
var y := y0 + rz * Sin(angle + da);
// первая точка:
if angle = 0 then begin
Turtle.X := x;
Turtle.Y := y;
angle += dAngle;
continue;
end;
// чертим отрезок:
Turtle.MoveTo(x, y);
angle += dAngle;
end;
da += dda;
end;
end;
Треугольники вращаются вокруг центра и создают забавную картинку (Рис. 9).
234
Рис. 5. Клонированные звёздочки при k = 6
235
Рис. 6. Клонированные звёздочки при k = 12
236
Рис. 7. Суперклонированные звёздочки при k = 12
237
Рис. 8. Клонированные звёздочки со смещением
238
Рис. 9. Звёзды в шоке!
239
Лемниската Бернулли
Не дарите в женский день
Своим милым дребедень –
Лучше блюдечко с зарплатой
Повяжите лемнискатой!
Нововишневский,
Красная джибурда
Лемнискату Бернулли придумал в 1694 году тот самый Якоб, которому так не повезло с логарифмической спиралью. В полярных координатах её уравнение выглядит не очень приветливо:
r = R√2cos(kZ)
но, слегка поднапрягшись, мы справимся и с этой напастью:
begin
Prepare;
// лемниската:
DrawLem(true);
DrawLem(false);
// клевер:
//
DrawLem(true, Pi/2);
//
DrawLem(false,Pi/2);
end.
// ЧЕРТИМ ЛЕМНИСКАТУ БЕРНУЛЛИ
procedure DrawLem(fill: boolean := false; a : double := 0.0);
begin
// скорость Черепашки:
Turtle.Speed := 100;
// толщина линий:
if fill then gw.PenWidth := 0
240
else gw.PenWidth := 1.8;
gw.PenColor := 'DarkGreen';
// цвет заливки фигур:
gw.BrushColor := 'LightGreen';
var r := 220;
var n := 1500;
var dAngle := DegToRad(1080 / n);
var angle := 0.0;
var x0: double := 320;
var y0: double := CY;
var k := 2;
var offset := 0.0;
Turtle.CreateFigure;
while angle <= 2 * PI + 0.02 do begin
var rz := r * Sqrt(2 * Cos(angle * k));
var x := x0 + Cos(angle + a) * rz + offset * Cos(angle);
var y := y0 + Sin(angle + a) * rz + offset * Sin(angle);
// первая точка:
if angle = 0 then begin
Turtle.X := x;
Turtle.Y := y;
angle += dAngle;
continue;
end;
// чертим отрезок:
try Turtle.MoveTo(x, y)
except Turtle.MoveTo(x0, y0)end;
angle += dAngle;
end;
if fill then Turtle.FillFigure();
end;
Лемниската - по-гречески λημνισχος - значит лента. В
Древней Греции так называли повязку, которой закрепляли
241
венок на голове победителя в спортивных играх. Сам Бернулли окрестил кривую lemniscus.
Лемниската Бернулли является частным случаем овала
Кассини - кривой, которую за 14 лет до Бернулли исследовал итальянский и французский астроном Джованни Доменико Кассини (Рис. 1).
Рис. 1. Giovanni Domenico/Jean-Dominique Cassini (1625-1712)
Лемниската - по-гречески λημνισχος - значит лента. В
Любители астрономии, безусловно, вспомнят кратеры
Кассини на Луне и Марсе, а также знаменитую щель Кассини – «зазор» между кольцами Сатурна шириной около 5
тысяч километров (Рис. 2).
242
Рис. 2. Кольца Сатурна
И здесь от величины параметра k зависит число лепестков у цветка. Обычная лемниската Бернулли очень похожа на опрокинутую восьмёрку или на математический знак бесконечности (Рис. 3). Если же перпендикулярно первой лемнискате
вычертить ещё одну, то опять возникнет листок клевера (Рис. 4).
243
Рис. 3. Лемниската Бернулли при k = 2
244
Рис. 4. Клевер из двух лемнискат при k = 2
245
Обычно лист клевера состоит из трёх равных частей, почему и называется трилистником (Рис. 5). Именно в таком
виде он символизирует Ирландию на гербе Великобритании
(Рис. 6) (роза обозначает Англию, чертополох - Шотландию и лук-порей - Уэльс).
Рис. 5. Клевер-трилистник
246
Рис. 6. Варианты герба Великобритании с листочками
клевера
По преданию, с помощью трилистника Святой Патрик
объяснял людям, что такое Святая Троица.
«Правильный» лист клевера украшает эмблему немецкого
футбольного клуба Greuther Fürth из пригорода Нюрнберга
(Рис. 7).
247
Рис. 7. Клеверная эмблема
Иногда можно найти и четырёхлистник, который, как принято считать во многих странах мира, приносит удачу.
Например, многие немецкие газеты вставили счастливый
листок в новогодние поздравления (Рис. 8).
Рис. 8. Год будет счастливым!
Поскольку сумма первых двух цифр 2011 года равна сумме
двух последних цифр, то его также можно считать удачным!
Задав параметру k значение 3, мы как раз и получим трилистник (Рис. 9) (или пропеллер, если вы не любите ботанику).
А чтобы изобразить розочку, достаточно несколько раз прокрутить пропеллер вокруг полюса (Рис. 10):
DrawLems(3, 6);
// ЧЕРТИМ ЛЕМНИСКАТЫ
procedure DrawLems(k, no : integer);
begin
248
// скорость Черепашки:
Turtle.Speed := 100;
// толщина линий:
gw.PenWidth := 1.5;
gw.PenColor := 'DarkGreen';
var r := 220;
var n := 1500;
var dAngle := DegToRad(1080 / n);
var x0: double := CX;
var y0: double := CY;
var da := 0.0;
var dda := DegToRad(no);
loop integer(360 / k / no) do begin
var angle := 0.0;
while angle <= 2 * PI + 0.02 do begin
var rz := r * Sqrt(2 * Cos(angle * k));
var x := x0 + Cos(angle + da) * rz;
var y := y0 + Sin(angle + da) * rz;
// первая точка:
if angle = 0 then begin
Turtle.X := x;
Turtle.Y := y;
angle += dAngle;
continue;
end;
try Turtle.MoveTo(x, y)
except Turtle.MoveTo(x0, y0)end;
angle += dAngle;
end;
da += dda;
end;
end;
249
Рис. 9. Лемниската-пропеллер при k = 3
250
Рис. 10. Лемниската-розочка при k = 3
251
Лемниската Бернулли ведёт себя очень скрытно возле полюса,
поэтому
перемещаем
Черепашку
внутри
блока
try..except:
try Turtle.MoveTo(x, y)
except Turtle.MoveTo(x0, y0)end;
Когда возникнет ошибка, мы просто соединим текущую
точку с начальной.
Фигуры Лиссажу
У девчонок на пляжу
Сплошь фигуры Лиссажу!
Нововишневский,
Сборник Всякая фигурень
Фигуры Лиссажу очень похожи на клубок синусоид, вписанных в прямоугольник.
Вид синусоид определяется параметрами kx и ky (Рис. 1-4). Например, при kx=1,
ky=2 получится удивлённая «лемниската Бернулли».
{$reference SmallVisualBasicLibrary.dll}
uses Microsoft.SmallVisualBasic.Library;
type
gw = GraphicsWindow;
// размеры окна:
const
GW_WIDTH = 640;
GW_HEIGHT = 640;
CX = GW_WIDTH div 2;
CY = GW_HEIGHT div 2;
252
procedure Prepare();
begin
gw.Hide();
gw.Title := 'Фигуры Лиссажу';
gw.Width := GW_WIDTH;
gw.Height := GW_HEIGHT;
gw.Show();
gw.Left := (Desktop.Width - gw.Width) / 2;
gw.Top := (Desktop.Height - gw.Height) / 2;
gw.BackgroundColor := 'SeaShell';
end;
// ЧЕРТИМ ФИГУРЫ ЛИССАЖУ
procedure DrawLissajous(fill: boolean := false);
begin
// скорость Черепашки:
Turtle.Speed := 100;
// толщина линий:
if fill then gw.PenWidth := 0
else gw.PenWidth := 1.8;
gw.PenColor := 'DarkGreen';
// цвет заливки фигур:
gw.BrushColor := 'LightGreen';
//
//
//
//
//
//
var kx := 1;
var ky := 2;
var kx := 3;
var ky := 2;
var kx := 7;
var ky := 11;
var kx := 17;
var ky := 11;
var r := 100;
var n := 3000;
var dAngle := DegToRad(1080 / n);
var angle := 0.0;
var x0: double := 320;
var y0: double := CY;
253
Turtle.CreateFigure;
while angle <= 2 * PI + 0.01 do begin
var rz := 3 * r;
var x := x0 + Cos(angle * kx) * rz;
var y := y0 + Sin(angle * ky) * rz;
// первая точка:
if angle = 0 then begin
Turtle.X := x;
Turtle.Y := y;
angle += dAngle;
continue;
end;
// чертим отрезок:
Turtle.MoveTo(x, y);
angle += dAngle;
end;
if fill then Turtle.FillFigure();
end;
begin
Prepare;
DrawLissajous(true);
//
DrawLissajous();
end.
254
Рис. 1. Фигура Лиссажу при kx=1, ky=2
255
Рис. 2. Фигура Лиссажу при kx=3, ky=2
256
Рис. 3. Фигура Лиссажу при kx=7, ky=11
257
Рис. 4. Фигура Лиссажу при kx=17, ky=11
Как следует из названия, эти кривые были найдены французским математиком Жюлем Антуаном Лиссажу (Рис. 5).
258
Рис. 5. Jules Antoine Lissajous (1822 - 1880)
По(пу)лярная анимация
Эй, товарищ! Больше жизни!
Спортивный марш
Музыка И. Дунаевского,
стихи В. Лебедева- Кумача
А теперь давайте простыми (с)подручными средствами анимируем наши унылостатичные картинки. Для этого мы отправим Черепашку двигаться вдоль да по
кривой и ставить на ней красные точки:
procedure Animation();
begin
DrawLissajous(true);
DrawLissajous();
// скорость Черепашки:
Turtle.Speed := 10;
gw.PenColor := 'Red';
gw.PenWidth := 4.8;
259
var kx := 1;
var ky := 2;
var r := 100;
var n := 1500;
var dAngle := DegToRad(1080 / n);
var angle := 0.0;
var x0: double := 320;
var y0: double := CY;
var np := 0;
while angle <=
var rz := 3
var x := x0
var y := y0
2
*
+
+
* PI + 0.01 do begin
r;
Cos(angle * kx) * rz;
Sin(angle * ky) * rz;
// первая точка:
if angle = 0 then begin
Turtle.X := x;
Turtle.Y := y;
angle += dAngle;
continue;
end;
if np mod 3 = 0 then Turtle.PenDown
else Turtle.PenUp;
np += 1;
// чертим отрезок:
Turtle.MoveTo(x, y);
angle += dAngle;
end;
end;
Процесс фигурного движения точки вдоль кривой выглядит живо и забавно (Рис.
1).
Легко заметить, что красные точки расставлены неравномерно. Дело в том, что
наш метод вычисления координат точек кривой приводит к тому, что где-то их
260
окажется густо, а где-то – пусто. На Рис. 2 хорошо видно, что, чем больше кривизна
кривой, тем меньше скорость Черепашки, и наоборот.
Рис. 1. Черепашьи мульти-пульти
261
Рис. 2. Осторожнее на поворотах!
262
Трохоиды
В русской народной сказке Колесо описана трогательная, но вполне
реалистичная история о том, как незадачливый переросток никак
не мог жениться по причине своего злостного косноязычия. Тогда
его мамаша посоветовала ему говорить с девушками «покруглее».
На следующем сватании этот маменькин сынок сидит-сидит да и
встрянет в разговор: «Колесо!». И эта декламация продолжалась
до тех пор, пока жениха вместе с его мамашей не выгнали в очередной раз с позором.
И тут мамаша спрашивает своего отпрыска:
- Ну что ты всё заладил: «колесо да колесо?»
На что последний резонно отвечает ей:
- Так вы же сами, мамаша, велели мне говорить покруглее!
Как отмечал наш классик, Александр Сергеевич Пушкин, сказка ложь, да в ней
намёк!, посему поговорим о кривых покруглее. А чтобы было совсем кругло, возьмем пару окружностей и покатим одну по другой.
Катить окружность нужно без проскальзывания!
Первую окружность назовём образующей, вторую - опорной.
В любом месте образующей окружности, внутри неё или даже за её пределами
закрепим карандаш (Рис. 1). Главное, чтобы карандаш был жёстко с ней связан,
тогда при качении он оставит на бумаге след – кривую, которую математики называют трохоидой.
По-гречески слово trochoeidе́s значит похожий на колесо,
круглый.
Отношение радиусов образующей и опорной окружностей
обозначают буквой m = r / R.
263
Расстояние от карандаша до центра образующей окружности принято обозначать буквой h.
Если катить образующую окружность по «ободу» снаружи, то мы получим эпитрохоиду, если внутри «обода» – гипотрохоиду.
По-гречески epi – над, hypo – под.
Рис. 1. Процесс (по)рождения эпи- и гипотрохоид
Если h = r, то карандаш вычертит эпициклоиду (Рис. 2, слева).
264
Рис. 2. Различные трохоиды
При h < r -укороченную (сжатую) эпитрохоиду (Рис. 2, в центре), а при h > r -удлинённую (растянутую) эпитрохоиду (Рис. 2, справа).
Но кроме столь скупых трохоид, мы сможем, поигрывая параметрами, вычерчивать самые настоящие узоры!
Кроме собственно трохоиды, мы серым цветом намечаем опорную окружность,
чтобы облегчить себе понимание происходящих верчений окружностей (Рис. 3).
В процедуре DrawE мы выбираем требуемое число оборотов производящей
окружности. И задать его нужно так, чтобы узор не прервался на самом интересном месте! Для этого подбираем число nep так, чтобы производящая окружность
объехала опорную целое число раз.
265
Рис. 3. (Не)наглядный пример
// ЧЕРТИМ ЭПИТРОХОИДЫ
procedure DrawE(fill: boolean := false);
begin
266
// скорость Черепашки:
Turtle.Speed := 100;
// толщина линий:
if fill then gw.PenWidth := 0
else gw.PenWidth := 1.5;
gw.PenColor := 'DarkGreen';
// цвет заливки фигур:
gw.BrushColor := 'LightGreen';
var n := 1000;
var dAngle := DegToRad(1080 / n);
var angle := 0.0;
var x0: double := 320;
var y0: double := CY;
// радиус опорной окружности:
var rop := 200;
// радиус производящей окружности:
var rpr := 20;
// смещение от центра:
var h := 90;
Вычисляем число оборотов и «по-научному»:
var nep := integer(Rop/NOD(Rop, rpr));
Обратите внимание, что здесь мы пользуемся функцией NOD:
// БЫСТРЫЙ АЛГОРИТМ ЕВКЛИДА
function NOD(n1, n2: integer): integer;
begin
while (n2 > 0) do begin
var n := n1 mod n2;
(n1, n2) := (n2, n);
end;
Result := n1;
end;
267
var m := rpr / Rop;
Turtle.CreateFigure;
while angle <= 2 * nep * PI + 0.025 do begin
var x := x0 + (Rop + rpr) * Cos(m * angle) –
h * Cos(angle + m * angle);
var y := y0 + (Rop + rpr) * Sin(m * angle) - h * Sin(angle +
m * angle);
// первая точка:
if angle = 0 then begin
Turtle.X := x;
Turtle.Y := y;
angle += dAngle;
continue;
end;
// чертим отрезок:
Turtle.MoveTo(x, y);
angle += dAngle;
end;
if fill then Turtle.FillFigure();
// чертим опорную окружность:
gw.PenColor := 'Gray';
gw.DrawEllipse(CX-rop,CY-rop,2*rop, 2*rop);
end;
Эпитрохоида с другими параметрами (Рис. 4):
// радиус опорной окружности:
var rop := 88;
// радиус производящей окружности:
var rpr := 55;
// смещение от центра:
var h := 124;
Ещё одна эпитрохоида (Рис. 5):
// радиус опорной окружности:
268
var rop := 104;
// радиус производящей окружности:
var rpr := 92;
// смещение от центра:
var h := 92;
Нет некрасивых трохоид (Рис. 6):
x0 := CX + 30;
y0 := CY + 30;
// радиус опорной окружности:
var rop := 125;
// радиус производящей окружности:
var rpr := 85;
// смещение от центра:
var h := 125;
Формулы для расчёта гипотрохоиды (Рис. 7) отличаются от эпитрохоидных только
знаками, так что обсуждать их мы не будем.
// ЧЕРТИМ ГИПОТРОХОИДЫ
procedure DrawH(fill: boolean := false);
begin
// скорость Черепашки:
Turtle.Speed := 100;
// толщина линий:
if fill then gw.PenWidth := 0
else gw.PenWidth := 1.5;
gw.PenColor := 'DarkGreen';
// цвет заливки фигур:
gw.BrushColor := 'LightGreen';
var n := 1500;
var dAngle := DegToRad(1080 / n);
var dcx := 0;
var dcy := 0;
269
var angle := 0.0;
var x0: double := 320;
var y0: double := CY;
// радиус опорной окружности:
var rop := 176;
// радиус производящей окружности:
var rpr := 64;
// смещение от центра:
var h := 200;
var nep := integer(Rop/NOD(Rop, rpr));
var m := rpr / Rop;
Turtle.CreateFigure;
while angle <= 2 * nep * PI + 0.02 do begin
var x := x0 + (Rop - rpr) * Cos(m * angle) +
h * Cos(angle - m * angle);
var y := y0 + (Rop - rpr) * Sin(m * angle) –
h * Sin(angle - m * angle);
// первая точка:
if angle = 0 then begin
Turtle.X := x;
Turtle.Y := y;
angle += dAngle;
continue;
end;
// чертим отрезок:
Turtle.MoveTo(x, y);
angle += dAngle;
end;
if fill then Turtle.FillFigure();
// чертим опорную окружность:
gw.PenColor := 'Gray';
gw.DrawEllipse(CX-rop + dcx,CY-rop + dcy,2*rop, 2*rop);
end;
270
Рис. 4. Изящная эпитрохоида
271
Рис. 5. Эпитрохоида в шашечку
272
Рис. 6. Красота – страшная сила!
273
Рис. 7. Гипотрохоиды тоже красивы
Эта трохоида просто гипер-гипотрохоидна (Рис. 8):
274
dcx := 20;
dcy := 20;
x0 := CX + dcx;
y0 := CY + dcy;
// радиус опорной окружности:
var rop := 125*2;
// радиус производящей окружности:
var rpr := 85*2;
// смещение от центра:
var h := 125*2;
275
Рис. 8. Королева красоты
276
Спирограф
Dum spiro, spero.
Пока дышу, надеюсь.
Латинская пословица
Задолго до того как компьютеры осилили графику, люди уже научились выделывать на бумаге изрядные кренделя. Причём с помощью детской игрушки (Рис. 1),
которую её изобретатель британский инженер Дэнис Фишер (Denys Fisher, 1918–
2002) назвал спирографом и в 1965 году показал на Нюрнбергской международной выставке игрушек.
Рис. 1. Первый спирограф
Не удивляйтесь, если в больнице вас заставят в спирограф
дуть. Дело в том, что в мире есть и совсем не игрушечные
спирографы. Они нужны для того, чтобы измерять объём
легких и частоту дыхания (Рис. 2, слева).
Прибор, который только измеряет, но не записывает показания, называется спирометром (Рис. 2, справа).
О значении слова спиро в названии дыхательного аппарата
легко догадаться по эпиграфу, а вот что оно значит в игрушечном спирографе, достоверно не известно. Скорее
всего, спираль, хотя спирограф вычерчивает не спирали, а
гипотрохоиды.
277
Рис. 2. Спирограф и спирометр
Ещё один Спирограф можно обнаружить на небе – это туманность в созвездии Зайца (Рис. 3).
Рис. 3. Планетарная туманность Спирограф
278
Как видите, в набор входила пластина с отверстиями разного диаметра и несколько кругов, также отличающихся по диаметру. Внутри кругов проделаны дырочки для карандаша. Легко понять, что при обкатывании маленького круга
внутри большого карандаш будет вычерчивать гипотрохоиды.
Круги, чтобы избежать проскальзывания при качении, снабжены небольшими зубчиками, которые на рисунке видны
плохо.
Игрушка пользовалась большим успехом и четыре года подряд признавалась лучшей обучающей игрушкой мира, поэтому неудивительно, что спирографы выпускаются и до сих пор, и некоторые из них стали значительно изощрённее предка
(Рис. 4).
Рис. 4. Современные спирографы
Побочные эпитрохоиды
Игры с параметрами трохоид приведут нас не только к красивым узорам, но и к
неожиданным «открытиям». Оказывается, улитка Паскаля – это всего лишь
весьма примитивная эпитрохоида, которую легко получить, если задать радиусы
обеих окружностей одинаковыми: rop = rpr (Рис. 1-2):
n := 1500;
279
dAngle := DegToRad(1080 / n);
dcx := 80;
dcy := 0;
x0 := CX + dcx;
y0 := CY + dcy;
// радиус опорной окружности:
var rop := 95;
// радиус производящей окружности:
var rpr := 95;
// смещение от центра:
var h := 165;
dcx := 40;
dcy := 0;
x0 := CX + dcx;
y0 := CY + dcy;
// радиус опорной окружности:
rop := 40;
// радиус производящей окружности:
rpr := 40;
// смещение от центра:
h := 250;
А портрет «донны Розы Дальвадорец» а ля Пикассо столь же просто нарисовать
при h = rop + rpr (Рис. 3). При этом число лепестков определяется отношением
радиусов окружностей rop/rpr.
n := 1000;
dAngle := DegToRad(1080 / n);
dcx := 5;
dcy := 5;
x0 := CX + dcx;
y0 := CY + dcy;
// радиус опорной окружности:
var rop := 150;
// радиус производящей окружности:
var rpr := 10;
// смещение от центра:
var h := 160;
280
Рис. 1. Эпитрохоидная улитка Паскаля
281
Рис. 2. Ещё одна улитка Паскаля
282
Рис. 3. Эпитрохоидная розочка
283
С помощью нашей программы вы вряд ли найдёте особенную кривую, которую
назовут вашим именем, но вот узоры вы можете нарисовать презанятные! По
крайней мере, классическому спирографу очень далеко до нашей программы!
Немыслимые мысленные эксперименты
Очевидное? – Невероятное!
Профессор Капица
Умная мысля приходит опосля.
Народная мудрость
Возьмите две одинаковые монеты (если у вас таковых не оказалось, то представьте их мысленно) и поместите их одну над другой на некотором расстоянии,
как показано на Рис. 1. А теперь уж точно мысленно соедините центры монет отрезком, который мы назовём водилом. Верхнюю монету жёстко прикрепим к водилу, а вокруг нижней монеты водило может свободно вращаться. Повернём водило на 180 градусов (то есть на половину оборота). Вопрос: какое положение
займёт нижняя монета, оказавшись внизу?
Рис. 1. Начинаем мысленный эксперимент
284
Поскольку эксперимент мысленный, то сначала завершите его, и только потом загляните в ответ (Рис. 2).
Рис. 2. Готово!
Я думаю, вы не ошиблись в своих предположениях. Результат вполне ожидаемый:
верхняя монета повернулась на пол-оборота и остановилась вверх ногами.
Усложним эксперимент. Прижмите монеты друг к другу так, чтобы они касались
друг друга (Рис. 3).
Позволим верхней монете вращаться вокруг собственной оси. Теперь катите верхнюю монету по нижней без проскальзывания, опять же на пол-оборота. А вопрос
тот же: какое положение займёт верхняя монета?
285
Рис. 3. Эксперимент номер два
Ответ уже не столь очевиден, как в первом случае, ведь верхняя монета будет
дополнительно вращаться вокруг своего собственного центра. На время прервём
мысленный эксперимент и воспользуемся нашей программой для рисования трохоид. При rop = rpr мы получим вот такую эпитрохоиду (Рис. 4).
Рис. 4. Вспомогательная эпитрохоида
286
Кривая повёрнута так, чтобы соответствовать нашим
предыдущим рисункам.
Легко видеть, что нижняя точка верхней монеты снова окажется внизу, когда верхняя монета совершит пол-оборота (Рис. 5).
Рис. 5. Неожиданный результат?
Таким образом, верхняя монета повернётся на полный оборот! Довольно
странно, если учесть, что точка касания монет прошла только половину окружности. Для прояснения ситуации уменьшим радиус верхней монеты вдвое, тогда она
обернётся при повороте на 180 градусов на полтора оборота (Рис. 6). При этом
287
вокруг собственного центра она повернётся на полный оборот. Становится понятно, что пол-оборота добавляет вращение самого центра верхней монеты относительно центра монеты нижней.
Рис. 6. Эпитрохоида при rop = 2rpr и полукругосветное путешествие монеты
Если мы изловчимся и заменим монеты шестернями, то изобретём планетарную
передачу (Рис. 7).
Рис. 7. Планетарная передача
288
Здесь в центре «композиции» находится солнечная шестерня, а вокруг неё вращаются планетарные шестерни, числом от двух до четырёх. Планетарные шестерни
закреплены на водиле. Нередко планетарные шестерни обкатываются и по внешней кольцевой (коронной) шестерне, которую также называют эпициклом.
Ну а если размахнуться до вселенских масштабов, то недолго создать и Солнечную систему (Рис. 8).
Рис. 8. Гелиоцентрическая система мира
Как вы помните, впервые заставил Землю и другие планеты вращаться вокруг
Солнца польский астроном Николай Коперник (Рис. 9).
289
Рис. 9. Mikolaj Kopernik (Nicolaus Copernicus) (1473-1543)
Звёздный конструктор
Ведь, если звёзды зажигают значит - это кому-нибудь нужно?
Значит - кто-то хочет, чтобы они были?
Владимир Маяковский. Послушайте!
Давайте напишем программу, которое начертит нам
красивые звёзды.
Число лучей у звезды определяется значением параметра numPoint. Значения параметров innerRadius и
outerRadius задают размер лучей – внутренний и внешний. Каждый луч состоит из двух отрезков. Чтобы провести отрезок, Черепашка должна знать координаты
290
двух точек – внутренней и внешней. Поэтому в каждом цикле рисования мы вычисляем координаты этих двух точек. Формулы для вычисления координат очень
простые:
procedure Star(numPoint: integer; innerRadius, outerRadius: double;
fill: boolean := false);
begin
// скорость Черепашки:
Turtle.Speed := 100;
// толщина линий:
if fill then gw.PenWidth := 0
else gw.PenWidth := 1.8;
gw.PenColor := 'DarkRed';
// цвет заливки фигур:
gw.BrushColor := 'Red';
dcx := 0.0;
dcy := 30.0;
x0 := CX + dcx;
y0 := CY + dcy;
Turtle.CreateFigure;
for var i := 0 to numPoint do begin
var angle := i * 2.0 * PI / numPoint;
var x := x0 + outerRadius * Sin(angle);
var y := y0 - outerRadius * Cos(angle);
if i = 0 then begin
Turtle.X := x;
Turtle.Y := y;
end
else
// чертим отрезок:
Turtle.MoveTo(x, y);
angle := (i + 0.5) * 2.0 * PI / numPoint;
x := x0 + innerRadius * Sin(angle);
y := y0 - innerRadius * Cos(angle);
// чертим отрезок:
Turtle.MoveTo(x, y);
end;
if fill then Turtle.FillFigure();
end;
291
Если значение параметра innerRadius положительное, то получаются привычные
звёзды (Рис. 1-10).
Рис. 1. Красная звезда
292
begin
Prepare;
Star(5, 120, 320, true);
Star(5, 120, 320);
end.
gw.PenColor := 'SteelBlue';
// цвет заливки фигур:
gw.BrushColor := 'SkyBlue';
dcx := 0.0;
dcy := 5.0;
x0 := CX + dcx;
y0 := CY + dcy;
Star(17, 120, 320, true);
Star(17, 120, 320);
Интересности начинаются, когда параметр innerRadius получает отрицательные
значения (Рис. 3):
gw.PenColor := 'DarkGreen';
// цвет заливки фигур:
gw.BrushColor := 'LightGreen';
var
var
var
var
dcx := 0.0;
dcy := 35.0;
x0 := CX + dcx;
y0 := CY + dcy;
Star(5, -120, 320, true);
Star(5, -120, 320);
Изменяя число лучей, внутренний радиус и цвета контура и заливки, вы сможете
получить очень красивые звёзды (Рис. 4-6):
gw.PenColor := 'Blue';
// цвет заливки фигур:
gw.BrushColor := 'DodgerBlue';
293
dcx := 0.0;
dcy := 20.0;
x0 := CX + dcx;
y0 := CY + dcy;
Star(7, -120, 320, true);
Star(7, -120, 320);
gw.PenColor := 'DarkOrange';
// цвет заливки фигур:
gw.BrushColor := 'Khaki';
dcx := 0.0;
dcy := 5.0;
x0 := CX + dcx;
y0 := CY + dcy;
Star(15, -80, 320, true);
Star(15, -80, 320);
gw.PenColor := 'Indigo';
// цвет заливки фигур:
gw.BrushColor := 'Violet';
dcx := 0.0;
dcy := 5.0;
x0 := CX + dcx;
y0 := CY + dcy;
Star(12, -80, 320, true);
Star(12, -80, 320);
gw.PenColor := 'Green';
// цвет заливки фигур:
gw.BrushColor := 'Lime';
dcx := 0.0;
dcy := 5.0;
x0 := CX + dcx;
y0 := CY + dcy;
294
Star(16, -250, 320, true);
Star(16, -250, 320);
gw.PenColor := 'Teal';
// цвет заливки фигур:
gw.BrushColor := 'LightSeaGreen';
dcx := 0.0;
dcy := 0.0;
x0 := CX + dcx;
y0 := CY + dcy;
Star(6, -320, 320, true);
Star(6, -320, 320);
gw.PenColor := 'DarkRed';
// цвет заливки фигур:
gw.BrushColor := 'LightSalmon';
dcx := 0.0;
dcy := 0.0;
x0 := CX + dcx;
y0 := CY + dcy;
Star(12, -320, 320, true);
Star(12, -320, 320);
gw.PenColor := 'Sienna';
// цвет заливки фигур:
gw.BrushColor := 'Wheat';
dcx := 0.0;
dcy := 0.0;
x0 := CX + dcx;
y0 := CY + dcy;
Star(36, -320, 320, true);
Star(36, -320, 320);
295
Рис. 2. Многолучевая звезда
296
Рис. 3. Отрицательная звезда
297
Рис. 4. Синяя звезда
298
Рис. 5. Жёлтая звезда
299
Рис. 6. Фиолетовая звезда
300
Рис. 7. Зелёная звезда
301
Рис. 8. Сине-зелёная звезда
302
Рис. 9. Оранжево-розовая звезда
303
Рис. 10. Желтоватая звезда
304
Разноцветные окружности
Мы уже обучили Черепашку рисовать окружности. Но одноцветные окружности не очень интересны, поэтому теперь Черепашка будет вычерчивать разноцветные окружности.
Способ рисования очень простой: мы вычерчиваем каждую сторону многоугольника толстым
цветным карандашом случайного цвета:
{$reference SmallVisualBasicLibrary.dll}
uses Microsoft.SmallVisualBasic.Library;
type
gw = GraphicsWindow;
// размеры окна:
const
GW_WIDTH = 640;
GW_HEIGHT = 640;
CX = GW_WIDTH div 2;
CY = GW_HEIGHT div 2;
procedure Prepare();
begin
gw.Hide();
gw.Title := 'Разноцветные окружности';
gw.Width := GW_WIDTH;
gw.Height := GW_HEIGHT;
gw.Show();
gw.Left := (Desktop.Width - gw.Width) / 2;
gw.Top := (Desktop.Height - gw.Height) / 2;
gw.BackgroundColor := 'SeaShell';
end;
// ЧЕРТИМ ОКРУЖНОСТЬ
procedure DrawCircle();
305
begin
var
var
var
var
dcx := 0.0;
dcy := 0.0;
xc := CX + dcx;
yc := CY + dcy;
// число сторон многоугольника:
var n := 120;
// радиус описанной окружности:
var r := CX - 20;
for var j := 0 to 19-1 do begin
// текущий угол:
var alpha := 0.0;
// приращение угла:
var da := 360.0 / n;
// координаты начальной и конечной точек сторон:
var startX := 0.0;
var startY := 0.0;
var x := 0.0;
var y := 0.0;
// толщина пера:
gw.PenWidth := 16.0 * r / (GW_WIDTH / 4); // 8
// чертим окружность:
for var i := 0 to n+1 do begin
x := xc + r * Cos(DegToRad(alpha));
y := yc - r * Sin(DegToRad(alpha));
gw.PenColor := gw.GetRandomColor();
if i = 0 then begin
Turtle.X := x;
Turtle.Y := y;
end
else
// чертим отрезок:
Turtle.MoveTo(x, y);
alpha += da;
startX := x;
startY := y;
end;
// уменьшаем радиус описанной окружности:
var dr := integer(gw.PenWidth) + 2;
306
r -= dr;
end;
end;
begin
Prepare;
DrawCircle();
end.
В итоге наших усилий Черепашка нарисовала замечательные концентрические и
в то же время эксцентрически пёстрые окружности (Рис. 1-2)!
Толстый карандаш (или перо) несколько грубоват для тонкой, изящной работы,
поэтому наши окружности напоминают плохо сшитые лоскутки в стиле пэчворк.
Чтобы избавиться от излишней пестроты, мы научим Черепашку выбирать градиентные цвета с помощью функции GetGradientColor:
// ВЫЧИСЛЯЕМ ЦВЕТ ПО ЕГО НОМЕРУ В ГРАДИЕНТЕ
function GetGradientColor(id: double) : string;
begin
var r, g, b : integer;
var n1 := integer(id / 256) mod 3;
var n2 := integer(id) mod 256;
if (n1 = 0) then begin
b := 255;
r := n2;
g := integer(255 - n2);
end
else if (n1 = 1) then begin
r := 255;
g := n2;
b := integer(255 - n2);
end
else begin
g := 255;
b := n2;
r := integer(255 - n2);
307
end;
Result := gw.GetColorFromRGB(r, g, b);
end;
procedure DrawCircle2();
begin
var dcx := 0.0;
var dcy := 0.0;
var xc := CX + dcx;
var yc := CY + dcy;
// число сторон многоугольника:
var n := 360;
// радиус описанной окружности:
var r := CX - 20;
for var j := 0 to 11 do begin
var rc := 18.0;// - j / 1.8;
var id := 0.0;
// текущий угол:
var alpha := 0.0;
// приращение угла:
var da := 360.0 / n;
// координаты начальной и конечной точек сторон:
var startX := 0.0;
var startY := 0.0;
var x := 0.0;
var y := 0.0;
// толщина пера:
gw.PenWidth := 8.0 * r / (GW_WIDTH / 4);
// чертим окружность:
for var i := 0 to n+1 do begin
x := xc + r * Cos(DegToRad(alpha));
y := yc - r * Sin(DegToRad(alpha));
gw.PenColor := GetGradientColor(id);
if i = 0 then begin
Turtle.X := x;
Turtle.Y := y;
end
else
308
// чертим отрезок:
Turtle.MoveTo(x, y);
// новый цвет:
//id += 768.0/rc; //психоделические цвета
id += 768.0/n;
alpha += da;
startX := x;
startY := y;
end;
// уменьшаем радиус описанной окружности:
var dr := integer(gw.PenWidth) + -2;
r -= dr;
end;
end;
Теперь Черепашка выдаёт нам плавно окрашенные градиентные окружности (Рис.
3).
Если ускорить изменение цветов вдоль окружности
id += 768 * 2.0 / n;
то картинка станет и того краше (Рис. 4).
Если вас всё ещё тянет к прекрасному, попробуйте поэкспериментировать с приращением цвета. Например, при значении
// новый цвет:
id += 768 / rc; //психоделические цвета
окружности вызывают рябь и дрожь в глазах (Рис. 5). Такие картины были бы хороши и полезны в общественных местах, например, в барах и ресторанах.
309
Рис. 1. Пёстро
310
Рис. 2. Ещё пестрее
311
Рис. 3. Градиентные окружности
312
Рис. 4. Цветная акселерация
313
Рис. 5. Цветная круговерть
314
Рис. 6. Расцветка Вырви глаз
Не ленитесь экспериментировать – и вас получатся новые замечательные картинки (Рис. 7)!
315
id += 768 / n + 384;
Рис. 7. Лучезарные окружности
316
Параметрические кривые
Кривая линия может быть задана параметрическими уравнениями. Для плоской
кривой понадобятся 2 уравнения – для каждой из координат. Координаты – это
функции от некоторой переменной t.
В этом проекте мы рассмотрим параметрические кривые, которые задаются такими уравнениями:
var x = Cos(A * t) + Cos(B * t) / 2 + Sin(C * t) / 3;
var y = Sin(A * t) + Sin(B * t) / 2 + Cos(C * t) / 3;
Параметр t изменяется в диапазоне 0..2π, а коэффициенты А, В и С – это произвольные целые числа.
Эти уравнения вы можете найти в книге Фрэнка Фарриса (Frank A. Farris) Creating
Symmetry: The Artful Mathematics of Wallpaper Patterns (Рис. 1).
Рис. 1. Книга о симметрии
317
В ней параметры равны:
А = 1 В = 6 С = 14
Им соответствует кривая на Рис. 2.
Рис. 2. Образец для подражания
318
Но нам, конечно, хотелось бы посмотреть и на другие кривые – может быть, среди
них найдутся ещё более интересные!
В этом проекте мы напишем 2 процедуры:
begin
Prepare;
DrawCurve();
//DrawColorCurve();
end.
Процедура DrawCurve чертит кривые с цветной заливкой, но с одноцветным контуром. Процедура DrawColorCurve чертит кривые без заливки, но с разноцветным
контуром. К сожалению, совместить разноцветный контур с цветной заливкой невозможно. Однако вы можете сначала сделать заливку, а затем наложить на неё
цветной контур, как мы это делали в предыдущих проектах.
В этом проекте мы применим универсальный метод построения кривых по точкам
из предварительно составленного списка. Он позволит нам вычертить линию и
сразу же закрасить её. Поскольку Черепашка двигается медленно, то этот метод
сократит время построения кривой в 2 раза.
В начале программы я собрал коэффициенты для построения кривых, которые вы
увидите дальше:
{$reference SmallVisualBasicLibrary.dll}
uses Microsoft.SmallVisualBasic.Library;
type
gw = GraphicsWindow;
// размеры окна:
const
GW_WIDTH = 800;
GW_HEIGHT = 800;
319
CX = GW_WIDTH div 2;
CY = GW_HEIGHT div 2;
// коэффициенты:
var
A := 1; B := 6; C := 14;
//A := 10; B := 21; C := 11;
//A := 10; B := 19; C := 11;
//A := 10; B := 39; C := 20;
//A := -1; B := 39; C := 40;
//A := -1; B := 38; C := 40;
//A := 9; B := 38; C := 40;
//A := -29; B := 38; C := 40;
//A := 19; B := 43; C := 19;
//A
//A
//A
//A
//A
:=
:=
:=
:=
:=
1; B := 15; C := 14;
15; B := 29; C := 14;
1; B := 6; C := 308;
1; B := -110; C := -113;
1; B := -53; C := -112;
Вы просто раскомментируйте нужную строку и вызовите нужную процедуру. Координаты точек удобно хранить в экземплярах примитивного класса Point:
// КЛАСС КООРДИНАТ ТОЧЕК
type Point = auto class
x, y : double;
end;
А все точки мы поместим в список lstPoints, откуда их легко извлекать:
// список точек кривой:
var lstPoints := new List<Point>();
Для этого проекта я выбрал тёмно-зелёный цвет фона:
procedure Prepare();
begin
320
gw.Hide();
gw.Title := 'Параметрические кривые';
gw.Width := GW_WIDTH;
gw.Height := GW_HEIGHT;
gw.Show();
gw.Left := (Desktop.Width - gw.Width) / 2;
gw.Top := (Desktop.Height - gw.Height) / 2;
gw.BackgroundColor := 'DarkGreen';
end;
В функции CreateCurve мы вычисляем координаты всех точек кривой и отправляем их в список lstPoints на хранение:
// СОЗДАЁМ КРИВУЮ
function CreateCurve() : double;
begin
lstPoints.Clear;
// коэффициент увеличения:
var k := 219;
// шаг:
var maxk := Max(Max(A, B), C);
Чем длиннее кривая, тем больше нужно точек, чтобы она получилась гладкой.
Число точек зависит от размера шага, поэтому подбирайте его соответственно вычерчиваемой кривой:
//var step := Min(1.0 / maxk / 8.0, 0.005);
var step := Min(1.0 / maxk / 8.0, 0.0025);
var dcx := 0.0;
var dcy := 10.0;
var x0 := CX + dcx;
var y0 := CY + dcy;
var t := 0.0;
while t <= PI * 2 do begin
var x := Cos(A * t) + Cos(B * t) / 2 + Sin(C * t) / 3;
var y := Sin(A * t) + Sin(B * t) / 2 + Cos(C * t) / 3;
x := x0 + x * k;
y := y0 + y * k;
321
lstPoints.Add(new Point(x, y));
t += step;
end;
Координаты точек, вычисляемые по формулам, настолько малы, что их необходимо домножить на некий коэффициент k, чтобы кривая заняла достойное место
на экране. Коэффициент maxk используется и в следующих процедурах. Чтобы не
вычислять его дважды, функция CreateCurve возвращает его в готовом виде:
Result := maxk;
end;
Процедура DrawCurve, кроме коэффициента maxk, получает и заполненный координатами точек кривой список lstPoints. Перед вычерчиванием кривой процедура
задаёт цвет контура и заливки, а также толщину линий:
// ЧЕРТИМ ОДНОЦВЕТНУЮ КРИВУю С ЗАЛИВКОЙ
procedure DrawCurve(fill: boolean:= true);
begin
var maxk := CreateCurve();
// цвет линий:
gw.PenColor := 'Yellow';
// цвет заливки фигур:
gw.BrushColor := 'LimeGreen';
// толщина пера:
gw.PenWidth := 2;
// толстая линия:
if (maxk > 40) then
gw.PenWidth := 2.5;
Перед вызовом процедуры Turtle.CreateFigure нужно сразу установить Черепашку
в последнюю точку кривой, иначе она начертит лишнюю прямую:
// последняя точка кривой:
Turtle.X := lstPoints[lstPoints.Count-1].x;
Turtle.Y := lstPoints[lstPoints.Count-1].y;
322
Turtle.CreateFigure;
А дальше в цикле foreach мы последовательно извлекаем из списка lstPoints координаты точек кривой и отсылаем туда Черепашку с пёрышком, и она чертит отрезок кривой линии:
foreach var pt in lstPoints do begin
var x := pt.x;
var y := pt.y;
// чертим отрезок:
Turtle.MoveTo(x, y);
end;
По умолчанию все фигуры закрашиваются:
if fill then Turtle.FillFigure();
end;
Но вы можете оставить только контур, если зададите параметру fill значение false.
Чертим первую кривую по коэффициентам из книги:
A := 1; B := 6; C := 14;
Черепашка отлично справилась с первым заданием (Рис. 3).
Дальше книга нам не помощник, поэтому методом тыка подбираем значения коэффициентов так, чтобы Черепашке не было стыдно за нас и за свою работу (Рис.
4).
Дело это кропотливое и хлопотное, но оно того стоит (Рис. 5).
Форма кривой зависит от коэффициентов, но сразу трудно представить себе, куда
они загнут кривую (Рис. 6).
323
Рис. 3. Следуем положительным примерам
324
Рис. 4. A = 10;
B = 21;
C = 11;
325
Рис. 5. A = 10; B = 19; C = 11;
326
Рис. 6. A = 10; B = 39; C = 20;
Иногда получаются красивые симметричные кривые (Рис. 7).
327
Рис. 7. A = -1; B = 39; C = 40;
Некоторые кривые могут даже удивить своей неожиданной формой (Рис. 8).
328
Рис. 8. A = -1; B = 38; C = 40;
329
В центре фигур могут самопроизвольно возникать дополнительные кривые (Рис. 9).
Рис. 9. A = 9; B = 38; C = 40;
330
Лишняя суета Черепашки может и навредить искусству (Рис. 10).
Рис. 10. A = -29; B = 38; C = 40;
331
Завершает нашу черепашью выставку параметрических кривых изрядный огурец
(Рис. 11). А Черепашка – молодец!
Рис. 11. A := 19; B := 43; C := 19;
332
Мы умеем вычислять цвет отрезков по их индексам. Для этого у нас есть готовая
функция GetGradientColor:
// ВЫЧИСЛЯЕМ ЦВЕТ ПО ЕГО НОМЕРУ В ГРАДИЕНТЕ
function GetGradientColor(id: double) : string;
begin
var r, g, b : integer;
var n1 := integer(id / 256) mod 3;
var n2 := integer(id) mod 256;
if (n1 = 0) then begin
b := 255;
r := n2;
g := integer(255 - n2);
end
else if (n1 = 1) then begin
r := 255;
g := n2;
b := integer(255 - n2);
end
else begin
g := 255;
b := n2;
r := integer(255 - n2);
end;
Result := gw.GetColorFromRGB(r, g, b);
end;
Она несовершенная, но простая и быстрая.
Процедура DrawColorCurve чертит кривую по точкам из списка lstPoints, но для
каждого отрезка кривой получает цвет от функции GetGradientColor по индексу
этого отрезка:
// ЧЕРТИМ РАЗНОЦВЕТНУЮ КРИВУЮ
procedure DrawColorCurve;
begin
gw.BackgroundColor := 'Black';
CreateCurve();
333
// толщина пера:
gw.PenWidth := 2.5;
var id := 0.0;
var n := lstPoints.Count;//360;
Turtle.X := lstPoints[lstPoints.Count-1].x;
Turtle.Y := lstPoints[lstPoints.Count-1].y;
foreach var pt in lstPoints do begin
var x := pt.x;
var y := pt.y;
gw.PenColor := GetGradientColor(id);
// чертим отрезок:
Turtle.MoveTo(x, y);
id += 768.0/n;
end;
end;
Как вы знаете, индекс можно вычислять по-разному, так что дерзайте!
Запускаем программу, и «книжная» кривая стала разноцветной (Рис. 12).
Цветные линии лучше смотрят на чёрном фоне (Рис. 13).
Отсутствие заливки позволяет лучше разглядеть причудливые изгибы параметрических кривых (Рис. 14).
На построение этой крутозаверченной кривой потребуется немало времени, так
что запаситесь напитками, вкусняшками и терпением (Рис. 15).
А эта кривая слегка пострадала в верхних крутых изгибах, но мне очень хотелось
быстрее увидеть её во всей красе (Рис. 16).
Даже простые математические формулы могут порождать художественные творения (Рис. 17), недоступные многим современным аляповатым мастерам.
334
Рис. 12. Цветная книжная кривая
335
Рис. 13. A := 1; B := 15; C := 14;
336
Рис. 14. A := 15; B := 29; C := 14;
337
Рис. 15. A := 1; B := 6; C := 308;
338
Рис. 16. A := 1; B := -110; C := -113;
339
Рис. 17. A := 1; B := -53; C := -112;
340
Разноцветные диагонали
Представьте, что у вас есть правильный многоугольник, в котором вы должны провести все возможные диагонали.
Вы легко справитесь с задачей для многоугольников с небольшим числом вершин
(Рис. 1).
Рис. 1. Правильные многоугольники с диагоналями
У треугольника вообще нет диагоналей.
У квадрата – 2 диагонали.
У пятиугольника – 5 диагоналей.
У шестиугольника – 9 диагоналей.
Дальше считать диагонали и чертить их вручную уже затруднительно, поэтому мы поручим это дело компьютеру, но нам нужно
сначала объяснить ему, как это делается, то есть написать программу.
Как мы только что установили, начинать нужно с квадратов, так как в треугольнике
диагоналей мы провести не сможем. А закончим мы 30-угольником, иначе все
линии просто сольются на экране всмятку и огорчат нас до невозможности, как
сказал Глеб Жеглов несознательному гражданину Ручечнику.
Удобнее всего строить правильный многоугольник по координатам его центра
(xc, yc) и радиусу описанной окружности r (Рис. 2).
341
Рис. 2. Геометрия – это наука!
Отсчёт угла alpha начинается от положительного направление оси X против часовой стрелки. Я недаром повернул все многоугольники на Рис. 1⬆ так, чтобы самая
первая вершина лежала на оси Х, - так нам удобнее вычислять координаты вершин. Если же вы захотите повернуть многоугольник, то всякий раз добавляйте
нужное значение к текущему углу alpha.
Приращение угла при переходе к следующей вершине легко вычислить, если
учесть, что окружность нужно разбить на n частей n точками. В полной окружности
360 градусов, значит, на каждую дугу окружности придётся 360/n градусов:
{$reference SmallVisualBasicLibrary.dll}
uses Microsoft.SmallVisualBasic.Library;
342
type
gw = GraphicsWindow;
// размеры окна:
const
GW_WIDTH = 720;
GW_HEIGHT = 720;
CX = GW_WIDTH div 2;
CY = GW_HEIGHT div 2;
procedure Prepare();
begin
gw.Hide();
gw.Title := 'Разноцветные диагонали';
gw.Width := GW_WIDTH;
gw.Height := GW_HEIGHT;
gw.Show();
gw.Left := (Desktop.Width - gw.Width) / 2;
gw.Top := (Desktop.Height - gw.Height) / 2;
gw.BackgroundColor := 'SeaShell';
end;
// ЧЕРТИМ МНОГОУГОЛЬНИК
procedure DrawPoly(n: integer);
begin
// текущий угол:
var alpha := 0.0;
// приращение угла:
var da := 360.0 / n;
// радиус описанной окружности:
var r := GW_WIDTH / 2 - 10;
// координаты точек:
var x := 0.0;
var y := 0.0;
// толщина пера:
gw.PenWidth := 1.8;
gw.PenColor := 'Black';
343
var
var
var
var
dcx := 0.0;
dcy := 0.0;
xc := CX + dcx;
yc := CY + dcy;
Координаты вершин многоугольника легко вычислить по тригонометрическим
формулам, но значение угла в градусах следует пересчитать в радианы, как того
требуют функции Sin и Cos:
// чертим многоугольник:
for var i := 0 to n do begin
x := xc + r * Cos(DegToRad(alpha));
y := yc + r * Sin(DegToRad(alpha));
if i = 0 then begin
Turtle.X := x;
Turtle.Y := y;
end
else
// чертим отрезок:
Turtle.MoveTo(x, y);
При переходе к следующей вершине увеличиваем угол alpha:
alpha += da;
end;
end;
Запускаем программу – и правильный многоугольник вычерчен на славу (Рис. 3):
begin
Prepare;
DrawPoly(12);
end.
344
Рис. 3. Чертим правильно правильные многоугольники
Но – диагоналей ему не хватает, поэтому берёмся за разработку следующего метода – Draw.
345
Пронумеруем вершины многоугольника от 0 до (n-1) (Рис. 4) и соединим нулевую
вершину со всеми остальными.
Рис. 4. Все диагонали нулевой вершины
Так мы получим и две стороны многоугольника, и его диагонали. Легко подсчитать, что для n-угольника потребуется провести (n-1) линию. Для нашего 8-угольника – 7 линий.
Переходим к точке 1. Здесь всё то же самое, но линия к предыдущей вершине уже
проведена, поэтому достаточно соединять текущую вершину со всеми последующими, но не с предыдущими (Рис. 5).
Из точки 1 мы проведём 6 линий.
Действуя аналогично, получим (Рис. 6):
Для точки 2 – 5 линий
Для точки 3 – 4 линии
Для точки 4 – 3 линии
Для точки 5 – 2 линии
346
Для точки 6 – 1 линию
Для точки 7 – 0 линий
Рис. 5. Диагонали первой вершины
Итак, чтобы вычертить все стороны и углы, нужно соединить каждую вершину
0..n-2 со всеми последующими. Чтобы не вычислять всякий раз координаты вершин, лучше завести два вещественных массива coordsX и coordsY для их координат. Как их вычислить, вы уже знаете:
// ЧЕРТИМ ДИАГОНАЛИ
// со сторонами
procedure Draw(n: integer);
begin
var alpha := 0.0;
var da := 360.0/n;
var r := GW_WIDTH/2 - 10;
var dcx := 0.0;
var dcy := 0.0;
var xc := CX + dcx;
347
var yc := CY + dcy;
// координаты вершин:
var coordsX := new double[n];
var coordsY := new double[n];
// вычисляем координаты вершин многоугольника:
for var i := 0 to n-1 do begin
coordsX[i] := xc + r * Cos(DegToRad(alpha));
coordsY[i] := yc + r * Sin(DegToRad(alpha));
alpha += da;
end;
Рис. 6. Все стороны и диагонали 8-угольника
348
Теперь провести все необходимые линии нам не составит труда:
// чертим диагонали:
if (n < 16) then
gw.PenWidth := 2
else
gw.PenWidth := 1.5;
Turtle.Speed := 20;
for var j := 0 to n-1-1 do begin
var x := coordsX[j];
var y := coordsY[j];
for var i := j + 1 to n-1 do begin
gw.PenColor := gw.GetRandomColor();
Turtle.X := x;
Turtle.Y := y;
Turtle.MoveTo(coordsX[i], coordsY[i]);
end;
end;
end;
Чтобы линии, проведённые из разных вершин, отличались друг от друга, мы окрасим их в разные цвета случайным образом (Рис. 7).
Более насыщенные узоры мы получим для 20- (Рис. 8) и 30-угольников (Рис. 9).
Давайте для интереса подсчитаем число линий в наших многоугольниках.
Если у многоугольника n вершин, то число линий равно сумме членов арифметической прогрессии:
(n-1), (n-2), . . ., (3), (2), (1) = (n-1) + 1
--------- * (n-1)
2
Или:
349
n * (n - 1)
----------2
Однако вернёмся в начало проекта, где мы собирались провести только диагонали. Таким образом, мы должны исключить из списка линий все стороны многоугольника.
Число диагоналей легко подсчитать, если из общего числа линий вычесть стороны
многоугольника, которых n штук. Тогда на долю диагоналей придётся:
n * (n - 3)
----------2
Снова начертим все диагонали из вершины 0, но на этот раз стороны не проводим
(Рис. 10).
Вы видите, что теперь соседняя по ходу вершина имеет номер не на единицу, а
на двойку больше текущей, а заканчивать нужно также не на предпоследней вершине, а на предпредпоследней, так как к этому времени все диагонали уже будут
проведены (Рис. 11).
350
Рис. 7. Правильный 12-угольник со всеми диагоналями
351
Рис. 8. Правильный 20-угольник со всеми диагоналями
352
Рис. 9. Работаем на максимуме
353
Рис. 10. Только диагонали!
Рис. 11. Все диагонали 8-угольника
// без сторон
procedure Draw2(n: integer);
begin
var alpha := 0.0;
354
var da := 360.0/n;
var r := GW_WIDTH/2 - 10;
var
var
var
var
dcx := 0.0;
dcy := 0.0;
xc := CX + dcx;
yc := CY + dcy;
// координаты вершин:
var coordsX := new double[n];
var coordsY := new double[n];
// вычисляем координаты вершин многоугольника:
for var i := 0 to n-1 do begin
coordsX[i] := xc + r * Cos(DegToRad(alpha));
coordsY[i] := yc + r * Sin(DegToRad(alpha));
alpha += da;
end;
// чертим стороны и диагонали:
if (n < 16) then
gw.PenWidth := 2
else
gw.PenWidth := 1.5;
Turtle.Speed := 20;
for var j := 0 to n-1-1 do begin
var x := coordsX[j];
var y := coordsY[j];
for var i := j + 2 to n-1 do begin
gw.PenColor := gw.GetRandomColor();
И нам нужно выполнить ещё одно условие, чтобы избежать соединения первой и
последней вершин многоугольника:
// не чертим сторону между первой и последней вершинами:
if (i - j = n - 1) then continue;
Turtle.X := x;
Turtle.Y := y;
355
Turtle.MoveTo(coordsX[i], coordsY[i]);
end;
end;
end;
На многоугольниках с большим числом сторон их отсутствие не очень заметно,
поэтому для иллюстрации последнего метода мы ограничимся 8-угольником (Рис.
12):
Draw2(8);
356
Рис. 12. Все диагонали 8-угольника – компьютерный вариант
357
Одноцветные диагонали
В некоторых случаях лучше не гнаться за пестротой линий, а вычертить их одним
цветом. В новом, облегчённом проекте мы этим и займёмся, а заодно сообщим в
заголовке окна число диагоналей в заданном многоугольнике.
Вызываем процедуру Draw, которая на этот раз имеет три параметра:
n – число сторон правильного многоугольника
color – цвет линий
thickness – толщина линий (по умолчанию равна 2)
begin
Prepare;
Draw(25, 'Blue', 1);
end.
В самой процедуре Draw нужно изменить и добавить всего несколько строк:
// ЧЕРТИМ МНОГОУГОЛЬНИК С ДИАГОНАЛЯМИ
procedure Draw(n: integer; color : string; tickness: integer := 2);
begin
var alpha := 0.0;
var da := 360.0/n;
var r := GW_WIDTH/2 - 10;
var
var
var
var
dcx := 0.0;
dcy := 0.0;
xc := CX + dcx;
yc := CY + dcy;
// координаты вершин:
var coordsX := new double[n];
var coordsY := new double[n];
358
// вычисляем координаты вершин многоугольника:
for var i := 0 to n-1 do begin
coordsX[i] := xc + r * Cos(DegToRad(alpha));
coordsY[i] := yc + r * Sin(DegToRad(alpha));
alpha += da;
end;
// цвет линий:
gw.PenColor := color;
// толщина линий:
gw.PenWidth := tickness;
Turtle.Speed := 20;
for var j := 0 to n-2 do begin
var x := coordsX[j];
var y := coordsY[j];
for var i := j + 1 to n-1 do begin
Turtle.X := x;
Turtle.Y := y;
Turtle.MoveTo(coordsX[i], coordsY[i]);
end;
end;
gw.Title := 'Число диагоналей = ' + n * (n - 3) / 2;
end;
По-моему, кружева получились великолепно (Рис. 1)!
359
Рис. 1. Все ниточки посчитаны до ниточки!
360
Диагональные разрезы
Более интересная и сложная задача – посчитать,
на сколько частей диагонали разбивают многоугольник.
В книге Росса Хонсбергера (Ross Honsberger)
[HR74], страницы 99-107 вы найдёте 3 способа
выведения формулы для подсчёта числа областей (регионов, частей), на которые диагонали
разбивают выпуклые многоугольники (не обязательно правильные):
К сожалению, эта формула не годится для правильных многоугольников с чётным
числом вершин (кроме квадрата), потому что формула выведена в предположении, что никакие три диагонали не пересекаются в одной точке. На Рис. 1 легко
видеть, что диагонали правильного 6-угольника нарушают это ограничение.
В результате число областей в таких многоугольниках оказывается меньше, чем
даёт формула. Например, диагонали правильного 6-угольника разбивают его на
24 части (посчитайте!), а согласно формуле их должно быть 25.
Давайте учтём это ограничение в новом проекте, который будет считать не только
диагонали, но и области, на которые они делят правильный многоугольник.
Добавьте к предыдущему проекту функцию GetKoeff2 для подсчёта числа сочетаний:
361
Рис. 1. Многоугольник-нарушитель
function GetKoeff2(n, k : integer) : decimal;
begin
var res : decimal := 1;
362
for var i := k - 1 to 0 step -1 do begin
res := res * (n - i);
res := res div (k - i);
end;
Result := res;
end;
И внесите коррективы в остальной код:
// ЧЕРТИМ МНОГОУГОЛЬНИК С ДИАГОНАЛЯМИ
procedure Draw(n: integer; color : string; tickness: real := 2);
begin
var alpha := 0.0;
var da := 360.0/n;
var r := GW_WIDTH/2 - 10;
var
var
var
var
dcx := 0.0;
dcy := 0.0;
xc := CX + dcx;
yc := CY + dcy;
// координаты вершин:
var coordsX := new double[n];
var coordsY := new double[n];
// вычисляем координаты вершин многоугольника:
for var i := 0 to n-1 do begin
coordsX[i] := xc + r * Cos(DegToRad(alpha));
coordsY[i] := yc + r * Sin(DegToRad(alpha));
alpha += da;
end;
// цвет линий:
gw.PenColor := color;
// толщина линий:
gw.PenWidth := tickness;
Turtle.Speed := 20;
for var j := 0 to n-2 do begin
var x := coordsX[j];
var y := coordsY[j];
for var i := j + 1 to n-1 do begin
363
Turtle.X := x;
Turtle.Y := y;
Turtle.MoveTo(coordsX[i], coordsY[i]);
end;
end;
var s := 'Число диагоналей = ' + n * (n - 3) / 2;
if (n = 4) or (n mod 2 <> 0) then
s += ' Число кусков = ' +
(GetKoeff2(n,4) + GetKoeff2(n-1,2)).ToString;
gw.Title := s;
end;
begin
Prepare;
//Draw(6, 'Blue');
//Draw(4, 'Blue');
//Draw(5, 'Blue');
//Draw(7, 'DarkGreen');
//Draw(11, 'DarkGreen')
Draw(9, 'Red')
end.
На этом наша титаническая работа заканчивается, и мы легко справляемся со статистическими расчётами (Рис. 2).
Для уверенности в себе сравниваем результаты работы нашей
программы с картинкой из книги.
Рис. 3 убеждает нас, что программа считает правильно.
364
Рис. 2. Большому куску и программа радуется!
365
Рис. 3. Контрольный пример
Диагональный круг
Если в воображаемом круге провести много-много диагоналей, то мы получим
почти настоящий круг с интересным муаром (Рис. 1):
{$reference SmallVisualBasicLibrary.dll}
uses Microsoft.SmallVisualBasic.Library;
type
gw = GraphicsWindow;
// размеры окна:
const
GW_WIDTH = 720;
GW_HEIGHT = 720;
366
CX = GW_WIDTH div 2;
CY = GW_HEIGHT div 2;
procedure Prepare();
begin
gw.Hide();
gw.Title := 'Диагональный круг';
gw.Width := GW_WIDTH;
gw.Height := GW_HEIGHT;
gw.Show();
gw.Left := (Desktop.Width - gw.Width) / 2;
gw.Top := (Desktop.Height - gw.Height) / 2;
gw.BackgroundColor := 'DarkGreen';
end;
// ЧЕРТИМ ДИАГОНАЛИ
procedure Draw();
begin
// цвет линий:
gw.PenColor := 'Lime';
// толщина линий:
gw.PenWidth := 1.0;
Turtle.Speed := 20;
// радиус окружности:
var r := GW_WIDTH/2 - 10;
// угол:
var a := 0.0;
// шаг:
var step := 2.0;
var
var
var
var
dcx := 0.0;
dcy := 0.0;
xc := CX + dcx;
yc := CY + dcy;
// проводим линии:
for var i := 0 to integer(180 / step)-1 do begin
var rad := DegToRad(a);
// начало линии:
367
var x1 := xc - r * Cos(rad);
var y1 := yc - r * Sin(rad);
Turtle.X := x1;
Turtle.Y := y1;
// конец линии:
var x2 := xc + r * Cos(rad);
var y2 := yc + r * Sin(rad);
Turtle.MoveTo(x2, y2);
a += step;
end;
end;
begin
Prepare;
Draw();
end.
368
Рис. 1. Муар
369
Разноцветные многоугольники
При известной сноровке и ловкости рук мы сможем вычертить градиентные многоугольники. Для этого достаточно внутри большего многоугольника рисовать
меньший, и тогда они создадут плавный переход цвета от внешнего многоугольника к его центру.
Взяв за основу проект Разноцветные диагонали и добавив всего несколько новых
строк, мы научим нашу программу рисовать градиентные картинки (Рис. 1-4).
{$reference SmallVisualBasicLibrary.dll}
uses Microsoft.SmallVisualBasic.Library;
type
gw = GraphicsWindow;
// размеры окна:
const
GW_WIDTH = 720;
GW_HEIGHT = 720;
CX = GW_WIDTH div 2;
CY = GW_HEIGHT div 2;
procedure Prepare();
begin
gw.Hide();
gw.Title := 'Разноцветные многоугольники';
gw.Width := GW_WIDTH;
gw.Height := GW_HEIGHT;
gw.Show();
gw.Left := (Desktop.Width - gw.Width) / 2;
gw.Top := (Desktop.Height - gw.Height) / 2;
gw.BackgroundColor := 'SeaShell';
end;
// ВЫЧИСЛЯЕМ ЦВЕТ ПО ЕГО НОМЕРУ В ГРАДИЕНТЕ
370
function GetGradientColor(id: double) : string;
begin
var r, g, b : integer;
var n1 := integer(id / 256) mod 3;
var n2 := integer(id) mod 256;
if (n1 = 0) then begin
b := 255;
r := n2;
g := integer(255 - n2);
end
else if (n1 = 1) then begin
r := 255;
g := n2;
b := integer(255 - n2);
end
else begin
g := 255;
b := n2;
r := integer(255 - n2);
end;
Result := gw.GetColorFromRGB(r, g, b);
end;
// ЧЕРТИМ МНОГОУГОЛЬНИКИ
procedure DrawPoly(n: integer);
begin
// текущий угол:
var alpha := 0.0;
// приращение угла:
var da := 360.0 / n;
// радиус описанной окружности:
var r := GW_WIDTH / 2 - 10;
// координаты точек:
var x := 0.0;
var y := 0.0;
// толщина пера:
gw.PenWidth := 3;
Turtle.Speed := 100;
var dcx := -90.0; // 3
371
dcx
var
var
var
:= 0.0; // 4,8,60
dcy := 0.0;
xc := CX + dcx;
yc := CY + dcy;
var id := 256.0;
while (r > 0) do begin
// чертим многоугольник:
for var i := 0 to n do begin
x := xc + r * Cos(DegToRad(alpha));
y := yc + r * Sin(DegToRad(alpha));
if i = 0 then begin
Turtle.X := x;
Turtle.Y := y;
end
else begin
var clr := GetGradientColor(id);
gw.PenColor := clr;
// чертим отрезок:
Turtle.MoveTo(x, y);
end;
alpha += da;
end;
// уменьшаем радиус описанной окружности:
r -= 1;
// и изменяем цвет сторон многоугольника:
id += 3.3;//32;//16;
end;
end;
begin
Prepare;
DrawPoly(3);
//DrawPoly(4);
//DrawPoly(8);
//DrawPoly(60);
//DrawPoly(12);
//DrawPoly(60);
end.
372
Рис. 1. Градиентный треугольник
373
Рис. 2. Градиентный квадрат
374
Рис. 3. Градиентный восьмиугольник
375
Рис. 4. Градиентный круг
376
Полосатость многоугольников можно легко изменить, задавая разные приращения индексу цвета (Рис. 5-6).
Рис. 5. id += 32
377
Рис. 6. id += 16
378
Вращающиеся многоугольники
Если начальное значение угла alpha равно нулю:
// текущий угол:
var alpha := 0.0;
то первая вершина многоугольника лежит на оси Х
(это всегда самая правая вершина) (Рис. 1).
Если мы зададим начальное значение -90 градусов
// текущий угол:
var alpha := -90.0;
то весь многоугольник повернётся против часовой оси на этот угол, и первая вершина переместится на ось Y и займёт самую верхнюю позицию (Рис. 2).
Таким образом, изменяя начальное значение угла alpha, мы можем поворачивать
многоугольник вокруг точки (xc, yc).
В новой программе мы используем проект Разноцветные многоугольники, но
уменьшим максимальное число вершин у многоугольников до 10 и будем поворачивать их заданное число раз.
{$reference SmallVisualBasicLibrary.dll}
uses Microsoft.SmallVisualBasic.Library;
type
gw = GraphicsWindow;
// размеры окна:
const
GW_WIDTH = 720;
379
GW_HEIGHT = 720;
CX = GW_WIDTH div 2;
CY = GW_HEIGHT div 2;
procedure Prepare();
begin
gw.Hide();
gw.Title := 'Вращающиеся многоугольники';
gw.Width := GW_WIDTH;
gw.Height := GW_HEIGHT;
gw.Show();
gw.Left := (Desktop.Width - gw.Width) / 2;
gw.Top := (Desktop.Height - gw.Height) / 2;
gw.BackgroundColor := 'DarkGreen';
end;
Основные изменения коснутся только процедуры рисования DrawPoly. Она получает ещё один параметр – число поворотов заданного многоугольника:
// ЧЕРТИМ МНОГОУГОЛЬНИКИ
procedure DrawPoly(n, nrot: integer);
begin
// текущий угол:
var alpha := -90.0;
// приращение угла:
var da := 360.0 / n;
// радиус описанной окружности:
var r := GW_WIDTH / 2 - 10;
// координаты точек:
var x := 0.0;
var y := 0.0;
Цвет линий не меняем, но вы можете выбрать его по своему вкусу. Точно так же,
как и толщину линий:
// толщина пера:
gw.PenWidth := 2;//3;
gw.PenColor := 'Lime';
380
Turtle.Speed := 100;
var
dcx
var
var
var
dcx := -90.0; // 3
:= 0.0; // 3
dcy := 0.0;
xc := CX + dcx;
yc := CY + dcy;
for var j := 0 to nrot-1 do begin
alpha := j * 360.0 / nrot;
// чертим многоугольник:
for var i := 0 to n do begin
x := xc + r * Cos(DegToRad(alpha));
y := yc + r * Sin(DegToRad(alpha));
if i = 0 then begin
Turtle.X := x;
Turtle.Y := y;
end
else
// чертим отрезок:
Turtle.MoveTo(x, y);
alpha += da;
end;
end;
end;
begin
Prepare;
DrawPoly(3,2);
//DrawPoly(4,3);
//DrawPoly(5,21);
//DrawPoly(3,28);
end.
Задавая различные значения параметрам этой процедуры, вы получите звёздчатые многоугольники (Рис. 3-6).
381
Рис. 1. Первая вершина лежит на оси Х
382
Рис. 2. Повернули треугольник
383
Рис. 3. DrawPoly(3,2)
384
Рис. 4. DrawPoly(4,3)
385
Рис. 5. DrawPoly(5,21)
386
Рис. 6. DrawPoly(3,28)
387
Цветные вращающиеся многоугольники
А теперь давайте добавим колорита нашим одноцветным звёздам!
Работы нам предстоит совсем немного, а результаты получатся любопытные (Рис.
1-5):
{$reference SmallVisualBasicLibrary.dll}
uses Microsoft.SmallVisualBasic.Library;
type
gw = GraphicsWindow;
// размеры окна:
const
GW_WIDTH = 720;
GW_HEIGHT = 720;
CX = GW_WIDTH div 2;
CY = GW_HEIGHT div 2;
procedure Prepare();
begin
gw.Hide();
gw.Title := 'Цветные вращающиеся многоугольники';
gw.Width := GW_WIDTH;
gw.Height := GW_HEIGHT;
gw.Show();
gw.Left := (Desktop.Width - gw.Width) / 2;
gw.Top := (Desktop.Height - gw.Height) / 2;
gw.BackgroundColor := 'Ivory';
end;
// ВЫЧИСЛЯЕМ ЦВЕТ ПО ЕГО НОМЕРУ В ГРАДИЕНТЕ
function GetGradientColor(id: double) : string;
begin
var r, g, b : integer;
var n1 := integer(id / 256) mod 3;
var n2 := integer(id) mod 256;
if (n1 = 0) then begin
b := 255;
r := n2;
388
g := integer(255 - n2);
end
else if (n1 = 1) then begin
r := 255;
g := n2;
b := integer(255 - n2);
end
else begin
g := 255;
b := n2;
r := integer(255 - n2);
end;
Result := gw.GetColorFromRGB(r, g, b);
end;
// ЧЕРТИМ МНОГОУГОЛЬНИКИ
procedure DrawPoly(n, nrot: integer);
begin
// текущий угол:
var alpha := 0.0;
// приращение угла:
var da := 360.0 / n;
// радиус описанной окружности:
var r := GW_WIDTH / 2 - 10;
// координаты точек:
var x := 0.0;
var y := 0.0;
// толщина пера:
gw.PenWidth := 4;
gw.PenColor := 'Lime';
Turtle.Speed := 100;
var dcx := 0.0;
dcx := 0.0;
var dcy := 0.0;
var xc := CX + dcx;
var yc := CY + dcy;
var id := 256.0;
for var j := 0 to nrot-1 do begin
alpha := j * 360.0 / nrot;
// чертим многоугольник:
389
for var i := 0 to n do begin
x := xc + r * Cos(DegToRad(alpha));
y := yc - r * Sin(DegToRad(alpha));
if i = 0 then begin
Turtle.X := x;
Turtle.Y := y;
end
else
// чертим отрезок:
Turtle.MoveTo(x, y);
alpha += da;
end;
var clr := GetGradientColor(id);
// полупрозрачный цвет:
//clr := '#A0' + string(Text.GetSubTextToEnd(clr, 2));
gw.PenColor := clr;
// изменяем цвет сторон многоугольника:
id += nrot;
end;
end;
begin
Prepare;
// непрозрачные цвета:
DrawPoly(3,111);
//DrawPoly(4,99);
//DrawPoly(3,331);
//DrawPoly(4,332);
//DrawPoly(3,720);
// полупрозрачные цвета:
//DrawPoly(3,90);
//DrawPoly(4,99);
//DrawPoly(3,720);
end.
Попробуйте поэкспериментировать с полупрозрачными цветами (Рис. 6-7). Для
этого раскомментируйте строчку:
//clr := '#A0' + string(Text.GetSubTextToEnd(clr, 2));
390
Рис. 1. DrawPoly(3,111)
391
Рис. 2. DrawPoly(4,99)
392
Рис. 3. DrawPoly(3,331)
393
Рис. 4. DrawPoly(4,332)
394
Рис. 5. DrawPoly(3,720)
395
Рис. 6. DrawPoly(3,90)
396
Рис. 7. DrawPoly(4,99)
397
Зубчатые многоугольники
Возьмём правильный многоугольник, сделаем его уменьшенную копию, которую
повернём на половину центрального угла стороны многоугольника (Рис. 1, слева).
Теперь соединим вершины этих многоугольников так, как показано на Рис. 1,
справа. В результате мы получим зубчатый многоугольник.
Рис. 1. Зубчатый многоугольник
От нас потребуется только небольшая сноровка, чтобы написать новую программу:
{$reference SmallVisualBasicLibrary.dll}
uses Microsoft.SmallVisualBasic.Library;
type
gw = GraphicsWindow;
// размеры окна:
const
GW_WIDTH = 720;
GW_HEIGHT = 720;
CX = GW_WIDTH div 2;
398
CY = GW_HEIGHT div 2;
// координаты вершин:
var coordsX, coordsY : array of real;
procedure Prepare();
begin
gw.Hide();
gw.Title := 'Зубчатые многоугольники';
gw.Width := GW_WIDTH;
gw.Height := GW_HEIGHT;
gw.Show();
gw.Left := (Desktop.Width - gw.Width) / 2;
gw.Top := (Desktop.Height - gw.Height) / 2;
gw.BackgroundColor := 'DarkGreen';
end;
procedure Draw(n: integer; color: string);
begin
// создаём массив координат вершин многоугольника:
CreateCoords(n);
// чертим многоугольник:
DrawPoly(n, color);
end;
begin
Prepare;
// положительные:
Draw(6,'Lime');
//Draw(40,'Lime');
//Draw(100,'Lime');
// отрицательные:
//Draw(11,'Lime');
//Draw(32,'Lime');
end.
399
Рисование многоугольника проходит в 2 этапа. Сначала мы создаём массивы координат вершин, а затем соединяем вершины отрезками прямых:
// СОЗДАЁМ МАССИВ КООРДИНАТ ВЕРШИН МНОГОУГОЛЬНИКА
procedure CreateCoords(n: integer);
begin
var alpha := 0.0;
var da := 360.0 / n;
var r := GW_WIDTH / 2 - 10;
//var rsmall := r * 0.5;
var rsmall := -r * 0.5;
var
dcx
var
var
var
dcx := 0.0;
:= 0.0;
dcy := 0.0;
xc := CX + dcx;
yc := CY + dcy;
// координаты вершин:
coordsX := new double[n
coordsY := new double[n
// вычисляем координаты
for var i := 0 to n * 2
coordsX[i] := xc + r
coordsY[i] := yc - r
* 2];
* 2];
вершин многоугольника:
- 1 step 2 do begin
* Cos(DegToRad(alpha));
* Sin(DegToRad(alpha));
coordsX[i+1] := xc + rsmall * Cos(DegToRad(alpha + da / 2));
coordsY[i+1] := yc - rsmall * Sin(DegToRad(alpha + da / 2));
alpha += da;
end
end;
Поскольку нам нужны теперь координаты двух концентрических многоугольников, то число точек возрастает вдвое, а радиус описанной окружности меньшего
многоугольника rsmall мы полагаем равным половине радиуса большего многоугольника r (вы можете выбрать другое значение).
400
Чтобы вычертить зубчатый многоугольник, последовательно соединяйте все вершины. При этом нужно не забудьте провести линию между первой и последней
вершинами, чтобы получилась замкнутая ломаная линия:
// ЧЕРТИМ МНОГОУГОЛЬНИК
procedure DrawPoly(n: integer; color: string);
begin
// цвет линий:
gw.PenColor := color;
// толщина линий:
gw.PenWidth := 2;
gw.BrushColor := 'ForestGreen';
// чертим многоугольник:
Turtle.X := coordsX[0];
Turtle.Y := coordsY[0];
Turtle.CreateFigure;
for var i := 0 to n * 2 - 2 do begin
Turtle.X := coordsX[i];
Turtle.Y := coordsY[i];
Turtle.MoveTo(coordsX[i + 1], coordsY[i + 1]);
end;
// замыкаем:
Turtle.MoveTo(coordsX[0], coordsY[0]);
Turtle.FillFigure;
end;
Пора пожинать плоды рук своих! Запускаем программу и пытаем её на разных
зубчатых многоугольниках (Рис. 2-4).
Задавая разные значения параметрам n и rsmall (радиус может быть и отрицательным!), вы получите очень интересные картинки (Рис. 5-6)!
401
Рис. 2. Draw(6,'Lime')
402
Рис. 3. Draw(40,'Lime')
403
Рис. 4. Draw(100,'Lime')
404
Рис. 5. Draw(11,'Lime')
405
Рис. 6. Draw(32,'Lime')
406
Звёздчатые многоугольники
Если соединять вершины пятиугольника последовательно
0-1
1-2
2-3
3-4
4-0
то получится обычный правильный многоугольник (Рис. 1, слева). Можно соединить вершины через одну:
0-2
1-3
2-4
3-0
4-1
Тогда получится звёздчатый многоугольник (Рис. 1, справа).
Форму правильного пятиугольника имеет всем известное здание Пентагона (что
в переводе и значит пятиугольник) (Рис. 2).
Пятиугольный звёздчатый многоугольник, или пентаграмма – самая известная фигура этого рода. Пентаграмма известна уже несколько тысячелетий и использовалась как оберегающий знак, знак власти или отличительный знак различных сообществ.
Английский учёный Роджер Пенроуз предложил свой вариант пентаграммы. Её
можно нарисовать, но невозможно воспроизвести «в натуре» (Рис. 3).
407
Рис. 1. Правильный и звёздчатый многоугольники
Рис. 2. Пентагон с высоты птичьего полёта
408
Рис. 3. Невозможная пентаграмма
«Пентаграммы» можно найти и в живой природе – это морские звёзды (Рис. 4).
Рис. 4. Морская пентаграмма
Шестиугольный звёздчатый многоугольник – гексаграмма – также очень известен.
Его можно видеть, например, на флаге Израиля (Рис. 5).
409
Рис. 5. Флаг Израиля с гексаграммой
И на фасаде базилики Санта-Кроче во Флоренции (Santa Croce exterior Firenze)
(Рис. 6).
Рис. 6. Церковная гексаграмма
410
Гексаграмму, как и пентаграмму Пенроуза, можно собрать из двух «невозможных» треугольников Пенроуза (Рис. 7).
Рис. 7. Невозможная гексаграмма
Гексаграмму называют также звездой Давида, звездой Голиафа и печатью Соломона.
Поскольку строить правильные многоугольники с числом сторон n мы уже умеем,
то нам нужно только научить программу соединять вершины определённым образом, чтобы получать не только правильные выпуклые многоугольники, но и
звёздчатые (или просто - звёзды).
Принято буквой m обозначать разность номеров соседних вершин звёздчатого
многоугольника. Для обыкновенных, правильных многоугольников m = 1, так как
стороны соединяют две смежные вершины. Для вычерчивания пентаграммы
нужно положить m = 2. Для гексаграммы также m = 2.
В процедуре Draw мы сначала создаём массив всех вершин заданного nугольника:
411
{$reference SmallVisualBasicLibrary.dll}
uses Microsoft.SmallVisualBasic.Library;
type
gw = GraphicsWindow;
// размеры окна:
const
GW_WIDTH = 720;
GW_HEIGHT = 720;
CX = GW_WIDTH div 2;
CY = GW_HEIGHT div 2;
// координаты вершин:
var coordsX, coordsY : array of real;
procedure Prepare();
begin
gw.Hide();
gw.Title := 'Зубчатые многоугольники';
gw.Width := GW_WIDTH;
gw.Height := GW_HEIGHT;
gw.Show();
gw.Left := (Desktop.Width - gw.Width) / 2;
gw.Top := (Desktop.Height - gw.Height) / 2;
gw.BackgroundColor := 'DarkGreen';
end;
// СОЗДАЁМ МАССИВ КООРДИНАТ ВЕРШИН МНОГОУГОЛЬНИКА
procedure CreateCoords(n: integer);
begin
var alpha := 0.0;
var da := 360.0 / n;
var r := GW_WIDTH / 2 - 10;
var dcx := 0.0;
dcx := 0.0;
var dcy := 0.0;
var xc := CX + dcx;
var yc := CY + dcy;
412
// координаты вершин:
coordsX := new double[n];
coordsY := new double[n];
// вычисляем координаты вершин многоугольника:
for var i := 0 to n - 1 do begin
coordsX[i] := xc + r * Cos(DegToRad(alpha));
coordsY[i] := yc - r * Sin(DegToRad(alpha));
alpha += da;
end
end;
procedure Draw(n,m: integer);
begin
// создаём массив координат вершин многоугольника:
CreateCoords(n);
// чертим многоугольник:
DrawPoly(n, m);
end;
begin
Prepare;
Draw(5,2);
//Draw(6,2);
//Draw(18,1);
//Draw(18,2);
//Draw(18,3);
//Draw(18,4);
//Draw(18,5);
//Draw(18,6);
//Draw(18,7);
//Draw(18,8);
end.
А затем, используя этот массив, чертим обычный (m=1) или звёздчатый (m > 1)
многоугольник:
413
// БЫСТРЫЙ АЛГОРИТМ ЕВКЛИДА
function NOD(n1, n2: integer): integer;
begin
while (n2 > 0) do begin
var n := n1 mod n2;
(n1, n2) := (n2, n);
end;
Result := n1;
end;
// ЧЕРТИМ МНОГОУГОЛЬНИК
procedure DrawPoly(n,m: integer);
begin
// цвет линий:
if (NOD(n, m) = 1) then
gw.PenColor := 'Lime'
else
gw.PenColor := 'Yellow';
// толщина линий:
gw.PenWidth := 2;
// чертим многоугольник:
for var i := 0 to n - 1 do begin
Turtle.X := coordsX[i];
Turtle.Y := coordsY[i];
var nv2 := (i + m) mod n;
Turtle.MoveTo(coordsX[nv2], coordsY[nv2]);
end;
end;
Как вы видите на Рис. 8-9, мы окрасили звёздчатые многоугольники в зелёный и
жёлтый цвета.
Так мы разделяем связные и несвязные звёзды. Связные звёзды можно вычертить
одним росчерком пера (это уникурсальные фигуры). Несвязные звёзды можно
начертить только в несколько приёмов, поскольку они состоят из нескольких одинаковых частей – правильных многоугольников или связных звёзд.
414
Рис. 8. Draw(5,2)
415
Рис. 9. Draw(6,2)
416
Для определения типа звезды мы пользуемся простым правилом:
Если числа n и m взаимно простые, то звезда получится связная.
Если числа n и m не взаимно простые, то звезда получится несвязная.
Как вы знаете, если числа взаимно простые, то их наибольший общий делитель
равен 1. В противном случае – больше 1. Для проверки чисел мы пользуемся методом NOD, реализующим быстрый алгоритм Евклида.
Если взять многоугольник с большим числом сторон, то он порождает несколько
звёзд разного типа – как связные, так и несвязные (Рис. 10-17).
Если m > n/2, то звёзды повторяются, поэтому такие значения использовать нет
смысла.
417
Рис. 10. Draw(18,1)
418
Рис. 11. Draw(18,2)
419
Рис. 12. Draw(18,3)
420
Рис. 13. Draw(18,4)
421
Рис. 14. Draw(18,5)
422
Рис. 15. Draw(18,6)
423
Рис. 16. Draw(18,7)
424
Рис. 17. Draw(18,8). Все 18-угольники
425
Рекурсивные квадраты
Рекурсия заключается в многократном повторении одних и тех же действий с разными значениями параметров. В этом рекурсивные вызовы методов сходны с
циклами.
Рекурсивные вызовы, как и циклы, могут стать бесконечными, поэтому в каждом
рекурсивном методе нужно предусмотреть условие выхода из него.
В этом проекте Черепашка нарисует квадраты. Самый большой из них имеет ширину и высоту в половину ширины окна программы. Под ним лежат 4 квадрата
вдвое меньшего размера, под каждым из них также по 4 квадрата – и так далее
до бесконечности. Это – теоретически, а практически – размеры сторон квадрата
очень быстро достигнут таких размеров, что их будет невозможно показать на
экране.
Вполне разумно ограничить длину сторон семью пикселями. Достижение этой
длины и послужит условием выхода из рекурсии.
После запуска программы вызывается процедура Draw, которая, в свою очередь,
передаёт управление процедуре DrawRects, сообщая ей координаты центра самого большого квадрата и длину его сторон:
{$reference SmallVisualBasicLibrary.dll}
uses Microsoft.SmallVisualBasic.Library;
type
gw = GraphicsWindow;
// размеры окна:
const
GW_WIDTH = 720;
GW_HEIGHT = 720;
CX = GW_WIDTH div 2;
426
CY = GW_HEIGHT div 2;
procedure Prepare();
begin
gw.Hide();
gw.Title := 'Рекурсивные квадраты';
gw.Width := GW_WIDTH;
gw.Height := GW_HEIGHT;
gw.Show();
gw.Left := (Desktop.Width - gw.Width) / 2;
gw.Top := (Desktop.Height - gw.Height) / 2;
gw.BackgroundColor := 'Black';
end;
procedure DrawRect(x, y, size: integer);
begin
// x, y - коорд. верхн. лев. угла
Turtle.X := x;
Turtle.Y := y;
Turtle.Angle := 90;
Turtle.CreateFigure;
loop 4 do begin
Turtle.Move(size);
Turtle.Turn(90);
end;
Turtle.FillFigure();
end;
Процедура DrawRects не вычерчивает этот квадрат сразу, а вызывает сама себя для построения квадратов меньших размеров, центры которых находятся в вершинах более габаритных квадратов:
procedure DrawRects(xc, yc, size: integer);
begin
if (size <= 6) then exit;
var size2 := size div 2;
427
DrawRects(xc
DrawRects(xc
DrawRects(xc
DrawRects(xc
+
+
size2,
size2,
size2,
size2,
yc
yc
yc
yc
+
+
size2,
size2,
size2,
size2,
size2);
size2);
size2);
size2);
DrawRect(xc - size2, yc - size2, size);
end;
begin
Prepare;
Turtle.Speed := 100;
gw.PenWidth := 1;
gw.PenColor := 'White';
gw.BrushColor := 'OrangeRed';
var w := GW_WIDTH div 2;
var h := GW_HEIGHT div 2;
DrawRects(w, h, Min(w, h));
end.
Вызовы процедуры DrawRects продолжаются до тех пор, пока величина сторон
квадрата не станет меньше 7 пикселей. И только тогда начнут рисоваться самые
маленькие квадратики, за ними – крупнее, ещё крупнее, пока, наконец, не будет
нарисован самый большой квадрат в центре окна программы. В итоге большие
квадраты всегда рисуются поверх меньших (Рис. 1).
Если вы внимательно наблюдали за порханием Черепашки, то заметили, что первым появляется самый маленький квадратик в левом верхнем углу окна программы, затем такой же квадратик ниже, затем справа, затем по диагонали.
Начертив 4 маленьких квадратика, Черепашка начертит квадратик побольше – с
вершинами в центре маленьких квадратов. После этого будут построены ещё 3
группы таких же квадратов, причём в том же порядке – снизу, справа, по диагонали. Поверх этих групп квадратиков Черепашка начертит квадрат ещё большего
размера.
428
Рис. 1. Рекурсивные квадраты
429
Очерёдность рисования квадратов однозначно определяется
последовательностью вызовов метода DrawRects:
DrawRects(x
DrawRects(x
DrawRects(x
DrawRects(x
+
+
size2,
size2,
size2,
size2,
y
y
y
y
+
+
size2,
size2,
size2,
size2,
size2);
size2);
size2);
size2);
При желании вы можете легко изменить её, переставив
строки.
Рисование продолжается до тех пор, пока не будет построен самый
большой
квадрат в половину окна приложения.
Пристально наблюдая за этим процессом, вы наверняка лучше разберётесь, как
действует рекурсия в этом примере.
Слоёный торт
Вы научились вычерчивать одноцветные рекурсивные
квадраты всех размеров. А было бы куда интереснее
каждый слой квадратов окрашивать в свой цвет.
Так как размеры сторон квадратов уменьшаются вдвое
от половины ширины окна до 7, то для раскрашивания
квадратов нам необходимо 7 разных цветов, которые
удобно хранить в массиве:
{$reference SmallVisualBasicLibrary.dll}
uses Microsoft.SmallVisualBasic.Library;
430
type
gw = GraphicsWindow;
// размеры окна:
const
GW_WIDTH = 720;
GW_HEIGHT = 720;
CX = GW_WIDTH div 2;
CY = GW_HEIGHT div 2;
// Цвета квадратов:
var iColor := |'Yellow',
'Red',
'Lime',
'Blue',
'Aqua',
'Fuchsia',
'White',
'Navy'
|;
procedure Prepare();
begin
gw.Hide();
gw.Title := 'Слоёный торт';
gw.Width := GW_WIDTH;
gw.Height := GW_HEIGHT;
gw.Show();
gw.Left := (Desktop.Width - gw.Width) / 2;
gw.Top := (Desktop.Height - gw.Height) / 2;
gw.BackgroundColor := 'Black';
end;
procedure DrawRect(x, y, size,id: integer);
begin
// x, y - коорд. верхн. лев. угла
Turtle.X := x;
Turtle.Y := y;
Turtle.Angle := 90;
gw.BrushColor := iColor[id];
431
Turtle.CreateFigure;
loop 4 do begin
Turtle.Move(size);
Turtle.Turn(90);
end;
Turtle.FillFigure();
end;
begin
Prepare;
Turtle.Speed := 100;
gw.PenWidth := 1.4;
gw.PenColor := 'White';
var w := GW_WIDTH div 2;
var h := GW_HEIGHT div 2;
DrawRects(w, h, Min(w, h), 0);
end.
Вычерчивая очередной квадрат, мы определяем его размер и цвет в массиве
iColor, после чего закрашиваем его этим цветом:
procedure DrawRects(xc, yc, size, id: integer);
begin
if (size <= 6) then exit;
var size2 := size div 2;
DrawRects(xc
DrawRects(xc
DrawRects(xc
DrawRects(xc
+
+
size2,
size2,
size2,
size2,
yc
yc
yc
yc
+
+
size2,
size2,
size2,
size2,
size2,id+1);
size2,id+1);
size2,id+1);
size2,id+1);
DrawRect(xc - size2, yc - size2, size, id);
end;
Рекурсивный рисунок теперь напоминает современный коврик (Рис. 1).
432
Рис. 1. Рекурсивный коврик
433
Попробуйте подобрать другие расцветки для этих ковриков!
Рекурсивные круги
Давайте сделаем рисунок «покруглее», заменив угловатые квадраты геометрически толерантными кругами:
{$reference SmallVisualBasicLibrary.dll}
uses Microsoft.SmallVisualBasic.Library;
type
gw = GraphicsWindow;
// размеры окна:
const
GW_WIDTH = 720;
GW_HEIGHT = 720;
CX = GW_WIDTH div 2;
CY = GW_HEIGHT div 2;
// Цвета:
var iColor := |'Yellow',
'Red',
'Lime',
'Blue',
'Aqua',
'Fuchsia',
'White',
'Navy'
|;
procedure Prepare();
begin
434
gw.Hide();
gw.Title := 'Рекурсивные круги';
gw.Width := GW_WIDTH;
gw.Height := GW_HEIGHT;
gw.Show();
gw.Left := (Desktop.Width - gw.Width) / 2;
gw.Top := (Desktop.Height - gw.Height) / 2;
gw.BackgroundColor := 'Black';
end;
// ЧЕРТИМ ОКРУЖНОСТЬ
procedure DrawCircle(xc, yc, size,id: integer);
begin
// число сторон:
var n := 50;
// радиус окружности:
var r := size * 0.7;
// текущий угол:
var alpha := 0.0;
// приращение угла:
var da := 360.0 / n;
gw.BrushColor := iColor[id];
Turtle.X := xc + r * Cos(DegToRad(0));
Turtle.Y := yc - r * Sin(DegToRad(0));
Turtle.CreateFigure;
// чертим окружность:
for var i := 0 to n+1 do begin
var x := xc + r * Cos(DegToRad(alpha));
var y := yc - r * Sin(DegToRad(alpha));
// чертим отрезок:
Turtle.MoveTo(x, y);
alpha += da;
end;
Turtle.FillFigure;
end;
procedure DrawCircles(xc, yc, size, id: integer);
begin
435
if (size <= 16) then exit;
var size2 := size div 2;
DrawCircles(xc - size2, yc
DrawCircles(xc - size2, yc
DrawCircles(xc + size2, yc
DrawCircles(xc + size2, yc
+
+
size2,
size2,
size2,
size2,
size2,id+1);
size2,id+1);
size2,id+1);
size2,id+1);
DrawCircle(xc, yc, size, id);
end;
begin
Prepare;
Turtle.Speed := 100;
gw.PenWidth := 1.4;
gw.PenColor := 'White';
var w := GW_WIDTH div 2;
var h := GW_HEIGHT div 2;
DrawCircles(w, h, Min(w, h), 0);
end.
Как вы видите, большая часть нашего усердия и энтузиазма ушла на простое переименование процедур. Зато микки-маусы получились весёлые и забавные (Рис.
1)!
436
Рис. 1. Рекурсивные микки-маусы
437
Рекурсивные круграты и квадруги
А теперь давайте усложним себе жизнь попеременным рисованием кругов и квадратов. То есть вокруг каждого круга нарисуем по 4 квадрата и наоборот.
Проблема легко решается с помощью двух процедур - DrawCircles и DrawRects,
которые рекурсивно вызывают друг друга (Рис. 1-2):
{$reference SmallVisualBasicLibrary.dll}
uses Microsoft.SmallVisualBasic.Library;
type
gw = GraphicsWindow;
// размеры окна:
const
GW_WIDTH = 720;
GW_HEIGHT = 720;
CX = GW_WIDTH div 2;
CY = GW_HEIGHT div 2;
// Цвета:
var iColor := |'Yellow',
'Red',
'Lime',
'Blue',
'Aqua',
'Fuchsia',
'White',
'Navy'
|;
procedure DrawCircles(xc, yc, size, id: integer); forward;
procedure DrawRects(xc, yc, size, id: integer); forward;
procedure Prepare();
begin
gw.Hide();
gw.Title := 'Рекурсивные круграты и квадруги';
438
gw.Width := GW_WIDTH;
gw.Height := GW_HEIGHT;
gw.Show();
gw.Left := (Desktop.Width - gw.Width) / 2;
gw.Top := (Desktop.Height - gw.Height) / 2;
gw.BackgroundColor := 'Black';
end;
// ЧЕРТИМ ОКРУЖНОСТЬ
procedure DrawCircle(xc, yc, size,id: integer);
begin
// число сторон:
var n := 50;
// радиус окружности:
var r := size * 0.7;
// текущий угол:
var alpha := 0.0;
// приращение угла:
var da := 360.0 / n;
gw.BrushColor := iColor[id];
Turtle.X := xc + r * Cos(DegToRad(0));
Turtle.Y := yc - r * Sin(DegToRad(0));
Turtle.CreateFigure;
// чертим окружность:
for var i := 0 to n+1 do begin
var x := xc + r * Cos(DegToRad(alpha));
var y := yc - r * Sin(DegToRad(alpha));
// чертим отрезок:
Turtle.MoveTo(x, y);
alpha += da;
end;
Turtle.FillFigure;
end;
procedure DrawRect(x, y, size,id: integer);
begin
// x, y - коорд. верхн. лев. угла
Turtle.X := x;
439
Turtle.Y := y;
Turtle.Angle := 90;
gw.BrushColor := iColor[id];
Turtle.CreateFigure;
loop 4 do begin
Turtle.Move(size);
Turtle.Turn(90);
end;
Turtle.FillFigure();
end;
procedure DrawRects(xc, yc, size, id: integer);
begin
if (size <= 16) then exit;
var size2 := size div 2;
DrawCircles(xc
DrawCircles(xc
DrawCircles(xc
DrawCircles(xc
+
+
size2,
size2,
size2,
size2,
yc
yc
yc
yc
+
+
size2,
size2,
size2,
size2,
size2,id+1);
size2,id+1);
size2,id+1);
size2,id+1);
DrawRect(xc - size2, yc - size2, size, id);
end;
procedure DrawCircles(xc, yc,
begin
if (size <= 16) then exit;
var size2 := size div 2;
DrawRects(xc - size2, yc DrawRects(xc - size2, yc +
DrawRects(xc + size2, yc DrawRects(xc + size2, yc +
size, id: integer);
size2,
size2,
size2,
size2,
size2,id+1);
size2,id+1);
size2,id+1);
size2,id+1);
DrawCircle(xc, yc, size, id);
end;
begin
Prepare;
Turtle.Speed := 100;
gw.PenWidth := 1.4;
440
gw.PenColor := 'White';
var w := GW_WIDTH div 2;
var h := GW_HEIGHT div 2;
DrawCircles(w, h, Min(w, h), 0);
//DrawRects(w, h, Min(w, h),0);
end.
Последовательность рисования кругов и квадратов легко изменить, оставляя незакомментированной нужную строчку в главном блоке.
441
Рис. 1. Рекурсивная смесь из кругов и квадратов
442
Рис. 2. Рекурсивная смесь из квадратов и кругов
443
Снежинка Коха
Со снежинкой Коха вы уже встречались. Её, как вы помните,
придумал в 1904 году шведский математик Гельг фон Кох, и
это один из первых фракталов, построение которого было
описано строго математически.
Чтобы внимательно проследить весь процесс построения
снежинки Коха разной сложности (она определяется числом
итераций), мы последовательно нарисуем все снежинки и
выделим их цветом:
{$reference SmallVisualBasicLibrary.dll}
uses Microsoft.SmallVisualBasic.Library;
type
gw = GraphicsWindow;
// размеры окна:
const
GW_WIDTH = 720;
GW_HEIGHT = 720+20;
CX = GW_WIDTH div 2;
CY = GW_HEIGHT div 2;
// Цвета:
var iColor := |'Yellow',
'Red',
'Lime',
'Blue',
'Aqua',
'Fuchsia',
'White',
'Navy'
|;
444
Если фрактальная Черепашка строила снежинки без нашей помощи, опираясь на
свой инстинкт, который записывался очень просто, то в этом проекте нам придётся самостоятельно вычислять координаты вершин всех отрезков, образующих
снежинку Коха.
За геометрическими подробностями обращайтесь к книгам Дойны Логофату
(Doina Logofatu) Grundlegende Algorithmen mit Java или Algorithmen und
Problemlösungen mit C++ (Рис. 1).
Рис. 1. Две обложки «одной» книги
Для вычислений нам понадобятся 2 константы:
const Sqrt3 = Sqrt(3.0);
Sqrt36 = Sqrt(3.0) / 6.0;
445
В главном блоке цикл for рисует снежинки различной степени детализации разными цветами. После каждого цикла рисования следует двухсекундная пауза,
чтобы вы могли перевести дух от восторга и восхищения:
begin
Prepare;
Turtle.Speed := 100;
gw.PenWidth := 2;
for var i := 1 to 5 do begin
gw.PenColor := iColor[i];
Draw(i);
Sleep(2000);
end;
end.
Для вычерчивания собственно снежинки вызывается процедура Draw, которой
сообщается желательная степень детализации:
// РИСУЕМ СНЕЖИНКУ
procedure Draw(iter: integer);
begin
var size := GW_WIDTH / 2;
var t := size;
var xa := size;
var ya := size - t;
var xb := size - Sqrt3 * t / 2;
var yb := size + t / 2;
DrawKoch(iter, xa, ya,
xa := size + Sqrt3 * t
ya := size + t / 2;
DrawKoch(iter, xb, yb,
xb := size;
yb := size - t;
DrawKoch(iter, xa, ya,
end;
xb, yb);
/ 2;
xa, ya);
xb, yb);
446
Процедура Draw, в свою очередь, трижды вызывает процедуру DrawKoch с различными значениями параметров:
procedure DrawKoch(n: integer; xa, ya, xb, yb : double);
begin
if ( n > 1 ) then begin
var xm := (xa + xb) / 2 + Sqrt36 * (ya - yb);
var ym := (ya + yb) / 2 + Sqrt36 * (xb - xa);
var xc := (2 * xa + xb) / 3;
var yc := (2 * ya + yb) / 3;
var xd := (xa + 2 * xb) / 3;
var yd := (ya + 2 * yb) / 3;
DrawKoch(n - 1, xa, ya, xc, yc);
DrawKoch(n - 1, xc, yc, xm, ym);
DrawKoch(n - 1, xm, ym, xd, yd);
DrawKoch(n - 1, xd, yd, xb, yb);
end
else begin
Turtle.X := xa;
Turtle.Y := ya+10;
Turtle.MoveTo(xb, yb+10);
end;
end;
Если число итераций равно единице,
то Черепашка начертит равносторонний треугольник, который послужит основой для построения последующих
кривых.
Красочный итог нашей кропотливой
работы вы можете наблюдать на Рис.
2.
447
Рис. 2. Праздничная снежинка Коха
448
Триколор
Мы уже освоили шахматную доску, клетки которой окрашены в 2
цвета. Давайте поставим перед собой и решим более общую задачу:
пусть клетки окрашиваются в любое число цветов! Тогда, например,
мы легко нарисуем российский триколор (Рис. 1).
Чтобы оперировать любым числом цветов, заведём для них строковый массив colors:
{$reference SmallVisualBasicLibrary.dll}
uses Microsoft.SmallVisualBasic.Library;
type
gw = GraphicsWindow;
// размеры окна:
const
GW_WIDTH = 720;
GW_HEIGHT = 720;
CX = GW_WIDTH div 2;
CY = GW_HEIGHT div 2;
// Цвета квадратов:
var colors := |'White','Blue','Red'|;
Процедура Draw с помощью свойства Length узнаёт число цветов и периодически
окрашивает «квадраты» в эти цвета:
// РИСУЕМ
procedure Draw(cellWidth, cellHeight : integer);
begin
// ширина рисунка:
var w := GW_WIDTH div cellWidth * cellWidth;
// высота рисунка:
var h := GW_HEIGHT div cellHeight * cellHeight;
449
// отступы:
var offsetX := (GW_WIDTH - w) div 2;
var offsetY := (GW_HEIGHT - h) div 2;
// число цветов:
var nclr := colors.Length;
for var y := 0 to GW_HEIGHT div cellHeight - 1 do begin
for var x := 0 to GW_WIDTH div cellWidth - 1 do begin
gw.BrushColor := colors[(x + y) mod nclr];
var xr := offsetX + x * cellWidth;
var yr := offsetY + y * cellHeight;
var wr := cellWidth;
var hr := cellHeight;
DrawRect(xr, yr, wr, hr);
end;
end;
end;
Квадраты наша Черепашка рисовать научилась. У прямоугольников длина горизонтальных и вертикальных сторон могут различаться, поэтому Черепашка рисует
их парами:
// РИСУЕМ ЗАКРАШЕННЫЙ ПРЯМОУГОЛЬНИК
procedure DrawRect(x, y, w, h: integer);
begin
// x, y - коорд. верхн. лев. угла
Turtle.X := x;
Turtle.Y := y;
Turtle.Angle := 90;
Turtle.CreateFigure;
loop 2 do begin
Turtle.Move(w);
Turtle.Turn(90);
Turtle.Move(h);
Turtle.Turn(90);
end;
Turtle.FillFigure();
end;
450
Рис. 1. Российский флаг
451
В главном блоке программы вызываем процедуру Draw с желаемыми размерами
прямоугольника:
begin
Prepare;
Turtle.Speed := 100;
gw.PenWidth := 0;
// Российский флаг:
Draw(120, 44);
end.
Записываем в массив все яркие цвета – и получаем великолепный узор (Рис. 2-7):
var colors := |'Red', 'Orange', 'Yellow',
'Green', 'DeepSkyBlue', 'Blue', 'Purple'|;
Ничуть не труднее получить более модные нынче абстрактные расцветки!
Используйте в процедуре Draw «генератор псевдослучайных цветов» и принимайтесь за художества:
var clr1 := Random(nclr);
while clr1 = clr2 do
clr1 := Random(nclr);
gw.BrushColor := colors[clr1];
clr2 := clr1;
Всего за пару минут вы осчастливите просвещённый мир десятками своих креативных творений (Рис. 3-)!
452
Рис. 2. Разноцветная шахматная доска
453
Рис. 3. Draw(16, 16)
454
Рис. 4. Draw(16, 180)
455
Рис. 5. Draw(720, 2)
456
Рис. 6. Draw(240, 240)
457
Рис. 7. Draw(360, 240)
458
Компьютерная радуга
Модель RGB не позволяет легко находить цвет, близкий к заданному, поэтому
нарисовать, например, радугу или градиент совсем непросто. А вот в цветовом
пространстве HSV мы без особого труда сможем нарисовать почти настоящую радугу!
На Рис. 1 видно, что наша модель радуги вполне реалистична, хотя и не совсем
точно передаёт настоящие цвета.
Рис. 1. Компьютерная радуга о семи цветах
Для вычисления цветов мы воспользуемся новой функцией HSV2RGB, которая получает заданное значение тона n * p. А насыщенность и яркость всегда максимальны, чтобы создать «чистые» цвета:
459
// КОНВЕРТИРУЕМ ЦВЕТ HSV В RGB
function HSV2RGB(h: double; s: double := 100; v:double := 100):
string;
begin
s := s / 100.0;
v := v / 100.0;
var r := 0.0;
var g := 0.0;
var b := 0.0;
//оттенки серого:
if (s = 0) then
begin
r := v;
g := v;
b := v;
end
else
begin
var Hi := Ceil(Math.Floor(h / 60));
var f := h / 60 - Hi;
//цветовые
var p := v
var q := v
var t := v
оси:
* (1 - s);
* (1 - (s * f));
* (1 - (s * (1 - f)));
case Hi of
0: begin
r := v;
g := t;
b := p;
end;
1: begin
r := q;
g := v;
b := p;
end;
2: begin
r := p;
g := v;
b := t;
460
end;
3: begin
r := p;
g := q;
b := v;
end;
4: begin
r := t;
g := p;
b := v;
end;
5: begin
r := v;
g := p;
b := q;
end;
end;
end;
Result := gw.GetColorFromRGB(Ceil(r * 255),
Ceil(g * 255),
Ceil(b * 255));
end;
// ВОЗВРАЩАЕМ СПИСОК ЦВЕТОВ
function GetRainbowColors(num : integer):List<string>;
begin
var res := new List<string>(num);
var p := 360.0 / num;
for var n := 0 to num-1 do
res.Add(Hsv2Rgb(n * p, 100.0, 100.0));
Result := res;
end;
Благодаря непрерывному цветовому кольцу HSB мы получаем нужные цвета, пропорционально увеличивая угол (тон) для каждого следующего цвета!
Так как цвета в радуге следуют друг за другом, то мы не будем рисовать двухмерную таблицу, а ограничимся всего одной строкой:
461
// РИСУЕМ ЗАКРАШЕННЫЙ ПРЯМОУГОЛЬНИК
procedure DrawRect(x, y, w, h: integer);
begin
// x, y - коорд. верхн. лев. угла
Turtle.X := x;
Turtle.Y := y;
Turtle.Angle := 90;
Turtle.CreateFigure;
loop 2 do begin
Turtle.Move(w);
Turtle.Turn(90);
Turtle.Move(h);
Turtle.Turn(90);
end;
Turtle.FillFigure();
end;
// РИСУЕМ
procedure DrawColorGrid(nclr : integer);
begin
var lstColor := GetRainbowColors(nclr);
// число цветов:
var count := lstColor.Count;
// размеры клеток:
var w := GW_WIDTH div count;
var h := GW_HEIGHT - 3;
// цвет рамки:
gw.PenColor := 'Black';
gw.PenWidth := 0;
// по всем цветам:
for var i := 0 to count-1 do begin
var x := i mod count;
var y := i div count;
gw.BrushColor := lstColor[i];
DrawRect(2 + x * (1 + w), 1 + y * (3 + h), w, h);
end;
end;
462
begin
Prepare;
Turtle.Speed := 100;
DrawColorGrid(7);
//DrawColorGrid(4);
//DrawColorGrid(80);
end.
Для изменения числа цветов в радуге (у нас всё-таки компьютерная радуга, а не
настоящая!) мы задаём значения в диапазоне 1..150.
Так вы сможете увидеть 4 цвета (Рис. 2).
Рис. 2. Четырёхцветная радуга
А радуга 80 цветов из у нас получилась очень весёлая (Рис. 3)!
463
Рис. 3. Многоцветная радуга
Астроида
Астроида – это плоская кривая, напоминающая
звезду с четырьмя лучами. Такая форма кривой и
дала ей название. Конечно, название кривой дала
не сама форма, а австрийский астроном Йозеф
Иоганн фон Литров в 1838 году, который назвал её
astrois в книге Kurze Anleitung zur gesammten Mathematik, которая вышла в Вене в 1838 году (Рис.
1). Современное название кривой происходит от
греческих слов αστρον — звезда и ειδος — вид, что
можно перевести как звездообразная.
464
Рис. 1. Свидетельство о рождении
Уравнение астроиды в декартовых координатах:
X2/3 + y2/3 = R2/3
Астроиду описывает точка окружности радиуса r, которая катится внутри окружности радиуса R = 4r (Рис. 2).
Рис. 2. Рождение астроиды
Но астроиду можно построить и как огибающую семейства отрезков, соединяющих точки на осях координат. На Рис. 3 хорошо видно, как нужно расставить точки
на координатных осях и как затем соединить их.
465
Рис. 3. Построение астроиды
466
Поскольку мы уже знаем теоретические основы построения астроиды, то легко
вычертим координатные оси и все отрезки прямых, которые и представят нам во
всей красе нашу замечательную кривую:
{$reference SmallVisualBasicLibrary.dll}
uses Microsoft.SmallVisualBasic.Library;
type
gw = GraphicsWindow;
// размеры окна:
const
GW_WIDTH = 720;
GW_HEIGHT = 720;
CX = GW_WIDTH div 2;
CY = GW_HEIGHT div 2;
// толщина линий:
PEN_WIDTH = 1.6;
procedure Prepare();
begin
gw.Hide();
gw.Title := 'Астроида';
gw.Width := GW_WIDTH;
gw.Height := GW_HEIGHT;
gw.Show();
gw.Left := (Desktop.Width - gw.Width) / 2;
gw.Top := (Desktop.Height - gw.Height) / 2;
gw.BackgroundColor := 'White';
end;
// ЧЕРТИМ АСТРОИДУ
procedure Draw();
begin
// шаг:
var step := 10;
467
// толщина линий:
gw.PenWidth := 1;
gw.PenColor := 'DarkGray';
// проводим линии:
for var i := 0 to CY div step do begin
// левый верхний квадрант:
Turtle.X := 0 + i * step;
Turtle.Y := CY;
Turtle.MoveTo(CX, CY - i * step);
// правый верхний квадрант:
Turtle.X := GW_WIDTH - i * step;
Turtle.Y := CY;
Turtle.MoveTo(CX, CY - i * step);
// левый нижний квадрант:
Turtle.X := 0 + i * step;
Turtle.Y := CY;
Turtle.MoveTo(CX, CY + i * step);
// правый нижний квадрант:
Turtle.X := GW_WIDTH - i * step;
Turtle.Y := CY;
Turtle.MoveTo(CX, CY + i * step);
end;
// проводим оси -->
gw.PenWidth := PEN_WIDTH;
gw.PenColor := 'Black';
// горизонтальная:
Turtle.X := 0;
Turtle.Y := CY;
Turtle.MoveTo(GW_WIDTH, CY);
// вертикальная:
Turtle.X := CX;
Turtle.Y := 0;
Turtle.MoveTo(CX, GW_HEIGHT);
end;
begin
Prepare;
Turtle.Speed := 100;
468
Draw();
end.
Астроида 2
Мы вполне можем отказаться от «сложных» вычислений и геометрических построений, если воспользуемся параметрическим уравнением астроиды:
Параметрические кривые мы умеем вычерчивать:
{$reference SmallVisualBasicLibrary.dll}
uses Microsoft.SmallVisualBasic.Library;
type
gw = GraphicsWindow;
// размеры окна:
const
GW_WIDTH = 720;
GW_HEIGHT = 720;
CX = GW_WIDTH div 2;
CY = GW_HEIGHT div 2;
BACKGROUND = 'DarkGreen';
procedure Prepare();
begin
gw.Hide();
gw.Title := 'Астроида 2';
gw.Width := GW_WIDTH;
gw.Height := GW_HEIGHT;
gw.Show();
gw.Left := (Desktop.Width - gw.Width) / 2;
gw.Top := (Desktop.Height - gw.Height) / 2;
469
gw.BackgroundColor := BACKGROUND;
end;
// КЛАСС КООРДИНАТ ТОЧЕК
type Point = auto class
x, y : double;
end;
// СОЗДАЁМ АСТРОИДУ
procedure CreateCurve(fill : boolean := false);
begin
// задаём цвет линии:
var clr := 'Lime';
//clr := 'Red';
gw.PenWidth := 2.5;
gw.PenColor := clr;
// шаг:
var step := 0.025;
// коэффициент увеличения:
var k := 217;
// радиус:
var R := 1.65;
// список точек:
var lstPoints := new List<Point>();
// добавляем вершины в список:
var t := 0.0;
while t < PI * 2 do begin
var ct := Cos(t);
var x := R * ct * ct * ct;
var st := Sin(t);
var y := R * st * st * st;
x := x * k + CX;
y := y * k + CY;
lstPoints.Add(new Point(x, y));
t += step;
end;
// последняя точка кривой:
Turtle.X := lstPoints[lstPoints.Count-1].x;
Turtle.Y := lstPoints[lstPoints.Count-1].y;
Turtle.CreateFigure;
foreach var pt in lstPoints do begin
470
var x := pt.x;
var y := pt.y;
// чертим отрезок:
Turtle.MoveTo(x, y);
end;
if fill then Turtle.FillFigure();
end;
begin
Prepare;
Turtle.Speed := 100;
//CreateCurve();
gw.BrushColor := 'Yellow';
CreateCurve(true);
end.
Правда, астроида в параметрическом исполнении выглядит не столь привлекательно, как раньше, даже с цветной раскраской (Рис. 1-2).
471
Рис. 1. Простая астроида
472
Рис. 2. Закрашенная астроида
473
Астроида 3
На самом деле звёзды имеют форму шара, а лучи образуются в хрусталике глаза
или в линзах оптических приборов. Их может быть 4, 6 или 8 (Рис. 1).
Рис. 1. Многолучевые звёзды
Геометрический способ построения астроид мы можем легко распространить на
большее число осей. Проще всего добавлять пары осей. Тогда для чётного числа
осей мы получим очень простую программу (Рис. 2-5):
{$reference SmallVisualBasicLibrary.dll}
uses Microsoft.SmallVisualBasic.Library;
type
gw = GraphicsWindow;
// размеры окна:
const
GW_WIDTH = 720;
GW_HEIGHT = 720;
CX = GW_WIDTH div 2;
CY = GW_HEIGHT div 2;
// толщина линий:
PEN_WIDTH = 1.6;
474
procedure Prepare();
begin
gw.Hide();
gw.Title := 'Астроида 3';
gw.Width := GW_WIDTH;
gw.Height := GW_HEIGHT;
gw.Show();
gw.Left := (Desktop.Width - gw.Width) / 2;
gw.Top := (Desktop.Height - gw.Height) / 2;
gw.BackgroundColor := 'White';
end;
procedure DrawLines(r: integer; step, a1, a2: double);
begin
// число линий:
var n :=Round(CY / step);
var rad1 := DegToRad(a1);
var rad2 := DegToRad(a2);
// проводим линии:
for var i := 0 to n do begin
var x1 := CX + r / n * i * Cos(rad1);
var y1 := CY - r / n * i * Sin(rad1);
var x2 := CX + r / n * (n - i) * Cos(rad2);
var y2 := CY - r / n * (n - i) * Sin(rad2);
Turtle.X := x1;
Turtle.Y := y1;
Turtle.MoveTo(x2, y2);
end;
end;
// ЧЕРТИМ АСТРОИДУ
procedure Draw(n : integer);
begin
// толщина линий:
gw.PenWidth := 1;
gw.PenColor := 'DarkGray';
475
// шаг:
var step := 10.0;
// число лучей:
var star := n;
// шаг:
var alpha := 360.0 / star;
// длина осей:
var r := GW_WIDTH div 2;
for var i := 0 to star-1 do
DrawLines(r, step, i * alpha, alpha * (i + 1));
// проводим оси -->
gw.PenWidth := PEN_WIDTH;
gw.PenColor := 'Black';
// угол:
var a := 0.0;
for var i := 0 to star-1 do begin
var rad := DegToRad(a);
// начало линии:
var x1 := CX - r * Cos(rad);
var y1 := CY - r * Sin(rad);
// конец линии:
var x2 := CX + r * Cos(rad);
var y2 := CY + r * Sin(rad);
Turtle.X := x1;
Turtle.Y := y1;
Turtle.MoveTo(x2, y2);
a += alpha;
end;
end;
begin
Prepare;
Turtle.Speed := 100;
//Draw(6);
Draw(8);
end.
476
Рис. 2. Шестилучевая астроида
477
Рис. 3. Восьмилучевая астроида
478
Рис. 4. Десятилучевая астроида
479
Рис. 5. Двенадцатилучевая астроида
480
Однако чаще мы представляем себе звезду с пятью лучами (Рис. 6-7).
Рис. 6. Правильная звезда
481
Рис. 7. Морская звезда
Слегка подработав «чётные» процедуры, мы сможем нарисовать «астроиды» с
пятью лучами (Рис. 8):
begin
Prepare;
Turtle.Speed := 100;
//Draw(6);
//Draw(8);
//DrawN(5);
//DrawN(9);
DrawN(13);
end.
Эта процедура позволяет вычерчивать астроиды с 9-ю и с 13-ю лучами (Рис. 9-10).
482
Рис. 8. Пятилучевая астроида
483
Рис. 9. Девятилучевая астроида
484
Рис. 10. Тринадцатилучевая астроида
485
А вот 7- и 11-лучевые звёзды так вычертить не удастся.
Попробуйте самостоятельно справиться с этой геометрической проблемой!
Если обойтись без осей, то нечётные звёзды вычертить даже проще, чем чётные:
// ЧЕРТИМ АСТРОИДУ
procedure Draw(n : integer; axes: boolean := true);
begin
// толщина линий:
gw.PenWidth := 1;
gw.PenColor := 'DarkGray';
// шаг:
var step := 10.0;
// число лучей:
var star := n;
// шаг:
var alpha := 360.0 / star;
// длина осей:
var r := GW_WIDTH div 2;
for var i := 0 to star-1 do
DrawLines(r, step, i * alpha, alpha * (i + 1));
if not axes then exit;
// проводим оси -->
gw.PenWidth := PEN_WIDTH;
gw.PenColor := 'Black';
// угол:
var a := 0.0;
for var i := 0 to star-1 do begin
var rad := DegToRad(a);
// начало линии:
var x1 := CX - r * Cos(rad);
var y1 := CY - r * Sin(rad);
486
// конец линии:
var x2 := CX + r * Cos(rad);
var y2 := CY + r * Sin(rad);
Turtle.X := x1;
Turtle.Y := y1;
Turtle.MoveTo(x2, y2);
a += alpha;
end;
end;
Вот так выглядят «недоступные» 7-лучевые и 11-лучевые звёзды (Рис. 11).
487
Рис. 11. Семилучевая астроида
488
Рис. 12. Одиннадцатилучевая астроида
489
Цветные линии
Если соединять пары точек, которые находятся на границах окна программы, а не
на осях координат, то Черепашка нарисует картинку из лучей астроиды разного
цвета (Рис. 1).
Как изменяются координаты начала и конца линии, хорошо видно на рисунке –
они скользят по границам клиентской области окна с заданным шагом 10 пикселей.
Изменяя значение этого параметра, а также цвет линий,
вы сможете получить и другие узоры.
// ПРОГРАММА "ЦВЕТНЫЕ ЛИНИИ"
{$reference SmallVisualBasicLibrary.dll}
uses Microsoft.SmallVisualBasic.Library;
type
gw = GraphicsWindow;
// размеры окна:
const
GW_WIDTH = 720;
GW_HEIGHT = 720;
CX = GW_WIDTH div 2;
CY = GW_HEIGHT div 2;
// толщина линий:
PEN_WIDTH = 1.0;
procedure Prepare();
begin
gw.Hide();
gw.Title := 'Цветные линии';
gw.Width := GW_WIDTH;
gw.Height := GW_HEIGHT;
490
gw.Show();
gw.Left := (Desktop.Width - gw.Width) / 2;
gw.Top := (Desktop.Height - gw.Height) / 2;
gw.BackgroundColor := 'Black';
end;
Чтобы каждый из четырёх наборов линий имел свой собственный цвет, мы перед
рисованием прямой линии макаем перо в чернила нужного цвета:
// ЧЕРТИМ
procedure Draw();
begin
// отношение высоты окна к ширине:
var ratio := GW_HEIGHT / GW_WIDTH;
var x := 0;
// чертим цветные линии
while(x <= GW_WIDTH) do begin
// красная линия:
gw.PenColor := 'Red';
Turtle.X := 0;
Turtle.Y := x * ratio;
Turtle.MoveTo(GW_WIDTH - x, 0);
// жёлтая линия:
gw.PenColor := 'Yellow';
Turtle.X := 0;
Turtle.Y := (GW_WIDTH - x) * ratio;
Turtle.MoveTo(GW_WIDTH-x, GW_WIDTH * ratio);
// синяя линия:
gw.PenColor := 'Blue';
Turtle.X := GW_WIDTH - x;
Turtle.Y := 0;
Turtle.MoveTo(GW_WIDTH, (GW_WIDTH - x) * ratio);
// зелёная линия:
gw.PenColor := 'Green';
Turtle.X := GW_WIDTH - x;
Turtle.Y := GW_WIDTH * ratio;
491
Turtle.MoveTo(GW_WIDTH, x * ratio);
// смещение:
x += 10;
end; // While
end;
begin
Prepare;
Turtle.Speed := 100;
Draw();
end.
492
Рис. 1. Четыре четырки от астроиды
493
Новые розетки
Продолжаем глумиться над окружностями. На этот
раз мы будем чертить одинаковые окружности,
центр которых удалён от центра канвы на некоторое расстояние offset:
// радиус:
var R := 176.0;
var offset := 176;
Центр каждой следующей окружности поворачивается вокруг центра канвы на заданный угол
dangle:
var angle := 0.0;
var dangle := DegToRad(6);
В итоге мы получим множество окружностей, которые образуют фигуру, называемую розеткой. Лучше известны
электрические розетки, которые совсем не похожи на розочки (Рис. 1).
В старых квартирах, на потолке
можно увидеть декоративные розетки с растительным орнаментом
(Рис. 2).
494
Рис. 1. Электрическая розетка
Рис. 2. Декоративная розетка
Наша розетка проще, но тоже чертовски хороша (Рис. 3)!
{$reference SmallVisualBasicLibrary.dll}
uses Microsoft.SmallVisualBasic.Library;
type
gw = GraphicsWindow;
// размеры окна:
const
GW_WIDTH = 720;
GW_HEIGHT = 720;
CX = GW_WIDTH div 2;
CY = GW_HEIGHT div 2;
// толщина линий:
PEN_WIDTH = 1.6;
procedure Prepare();
begin
gw.Hide();
495
gw.Title := 'Новые розетки';
gw.Width := GW_WIDTH;
gw.Height := GW_HEIGHT;
gw.Show();
gw.Left := (Desktop.Width - gw.Width) / 2;
gw.Top := (Desktop.Height - gw.Height) / 2;
gw.BackgroundColor := 'DarkGreen';
end;
// КЛАСС КООРДИНАТ ТОЧЕК
type Point = auto class
x, y : double;
end;
// ЧЕРТИМ РОЗЕТКУ
procedure DrawRosette();
begin
// задаём цвет линии:
var clr := 'Lime';
gw.PenColor := clr;
gw.PenWidth := 2.0;
var angle := 0.0;
var dangle := DegToRad(6);
// радиус:
var R := 176.0;
var offset := 176;
// список точек:
var lstPoints := new List<Point>();
while angle <= PI * 2 do begin
// шаг:
var step := 0.1;
// очищаем список точек:
lstPoints.Clear;
// добавляем вершины в список:
var t := 0.0;
while t < PI * 2 do begin
var x := CX + Cos(t) * R + offset * Cos(angle);
var y := CY + Sin(t) * R + offset * Sin(angle);
496
lstPoints.Add(new Point(x, y));
t += step;
end;
angle += dangle;
// последняя точка кривой:
Turtle.X := lstPoints[lstPoints.Count-1].x;
Turtle.Y := lstPoints[lstPoints.Count-1].y;
foreach var pt in lstPoints do begin
var x := pt.x;
var y := pt.y;
// чертим отрезок:
Turtle.MoveTo(x, y);
end;
end;
end;
begin
Prepare;
Turtle.Speed := 100;
// чертим розетку:
DrawRosette();
end.
Изменяя значения радиуса окружностей и их удаление от центра, вы будете получать другие красивые розетки (Рис. 4):
// ЧЕРТИМ РОЗЕТКУ
procedure DrawRosette2();
begin
// задаём цвет линии:
var clr := 'Lime';
gw.PenColor := clr;
gw.PenWidth := 2.0;
var angle := 0.0;
var dangle := DegToRad(6);
// радиус:
var k := 1.18;
497
var R := 119 * k;
var offset := 180 * k;
// список точек:
var lstPoints := new List<Point>();
while angle <= PI * 2 do begin
// шаг:
var step := 0.1;
// очищаем список точек:
lstPoints.Clear;
// добавляем вершины в список:
var t := 0.0;
while t < PI * 2 do begin
var x := CX + Cos(t) * R + offset * Cos(angle);
var y := CY + Sin(t) * R + offset * Sin(angle);
lstPoints.Add(new Point(x, y));
t += step;
end;
angle += dangle;
// последняя точка кривой:
Turtle.X := lstPoints[lstPoints.Count-1].x;
Turtle.Y := lstPoints[lstPoints.Count-1].y;
foreach var pt in lstPoints do begin
var x := pt.x;
var y := pt.y;
// чертим отрезок:
Turtle.MoveTo(x, y);
end;
end;
end;
Если слегка недовертеть окружности, то, во-первых, хорошо видно, как образуется розетка, а, во-вторых, получается весьма изящная фигура (Рис. 6):
// ЧЕРТИМ РОЗЕТКУ
procedure DrawRosette3();
begin
// задаём цвет линии:
var clr := 'Lime';
gw.PenColor := clr;
498
gw.PenWidth := 2.0;
var angle := DegToRad(45.0);//0.0;
var dangle := DegToRad(6);
// радиус:
var k := 1.52;
var R := 160 * k;
var offset := 70 * k;
// список точек:
var lstPoints := new List<Point>();
while angle <= PI * 2 - DegToRad(45) do begin
// шаг:
var step := 0.1;
// очищаем список точек:
lstPoints.Clear;
// добавляем вершины в список:
var t := 0.0;
while t < PI * 2 do begin
var x := CX + Cos(t) * R + offset * Cos(angle +
DegToRad(-90));
var y := CY + Sin(t) * R + offset * Sin(angle +
DegToRad(-90));
lstPoints.Add(new Point(x, y));
t += step;
end;
angle += dangle;
// последняя точка кривой:
Turtle.X := lstPoints[lstPoints.Count-1].x;
Turtle.Y := lstPoints[lstPoints.Count-1].y;
foreach var pt in lstPoints do begin
var x := pt.x;
var y := pt.y;
// чертим отрезок:
Turtle.MoveTo(x, y);
end;
end;
end;
499
Рис. 3. Компьютерная розетка
500
Рис. 5. Бубличная розетка
501
Рис. 6. Недокрученная розетка
502
Ещё одна симпатичная розетка (Рис. 7):
// ЧЕРТИМ РОЗЕТКУ
procedure DrawRosette4();
begin
// задаём цвет линии:
var clr := 'Lime';
gw.PenColor := clr;
gw.PenWidth := 2;
var angle := 0.0;
var dangle := DegToRad(6);
// радиус:
var k := 1.19;
var R := 119 * k;
var offset := 180 * k;
// список точек:
var lstPoints := new List<Point>();
while angle <= PI * 2 - dangle do begin
// шаг:
var step := 0.1;
// очищаем список точек:
lstPoints.Clear;
// добавляем вершины в список:
var t := 0.0;
while t < PI * 2 + 0.2 do begin
var x := CX + Cos(t) * R + offset * Cos(angle +
DegToRad(-107));
var y := CY + Sin(t) * R + offset * Sin(angle +
DegToRad(-107));
lstPoints.Add(new Point(x, y));
t += step;
end;
angle += dangle;
// последняя точка кривой:
Turtle.X := lstPoints[lstPoints.Count-1].x;
Turtle.Y := lstPoints[lstPoints.Count-1].y;
foreach var pt in lstPoints do begin
var x := pt.x;
503
var y := pt.y;
// чертим отрезок:
Turtle.MoveTo(x, y);
end;
end;
end;
Добавим немного смекалки, чтобы получить очаровательную розетку (Рис. 8):
// ЧЕРТИМ РОЗЕТКУ
procedure DrawRosette5();
begin
// задаём цвет линии:
var clr := 'Lime';
gw.PenColor := clr;
gw.PenWidth := 2;
var angle := 0.0;
var dangle := DegToRad(6);
// радиус:
var R := 176;
var offset := 176;
// список точек:
var lstPoints := new List<Point>();
while angle <= PI * 2 - dangle*24 do begin
// шаг:
var step := 0.1;
// очищаем список точек:
lstPoints.Clear;
// добавляем вершины в список:
var t := 0.0;
while t < PI * 2 do begin
var x := CX + Cos(t) * R + offset * Cos(angle +
DegToRad(-15.5));
var y := CY + Sin(t) * R + offset * Sin(angle +
DegToRad(-15.5));
lstPoints.Add(new Point(x, y));
t += step;
end;
504
angle += dangle;
// последняя точка кривой:
Turtle.X := lstPoints[lstPoints.Count-1].x;
Turtle.Y := lstPoints[lstPoints.Count-1].y;
foreach var pt in lstPoints do begin
var x := pt.x;
var y := pt.y;
// чертим отрезок:
Turtle.MoveTo(x, y);
end;
end;
end;
505
Рис. 7. Симпатичная розетка
506
Рис. 8. Очаровательная розетка
507
Вращающиеся квадраты
Чтобы вам сразу стал понятен замысел проекта, посмотрите на готовую картинку
(Рис. 1):
{$reference SmallVisualBasicLibrary.dll}
uses Microsoft.SmallVisualBasic.Library;
type
gw = GraphicsWindow;
// размеры окна:
const
GW_WIDTH = 720;
GW_HEIGHT = 720;
CX = GW_WIDTH div 2;
CY = GW_HEIGHT div 2;
procedure Prepare();
begin
gw.Hide();
gw.Title := 'Вращающиеся квадраты';
gw.Width := GW_WIDTH;
gw.Height := GW_HEIGHT;
gw.Show();
gw.Left := (Desktop.Width - gw.Width) / 2;
gw.Top := (Desktop.Height - gw.Height) / 2;
gw.BackgroundColor := gw.GetColorFromRGB(240,240,240);
end;
// ЧЕРТИМ КВАДРАТЫ
procedure DrawSquares(n, nrot: integer);
begin
// текущий угол:
var alpha := 0.0;
// приращение угла:
var da := 360.0 / n;
// радиус описанной окружности:
508
var r := GW_WIDTH / 1.42 - 10.0;
// координаты точек:
var x := 0.0;
var y := 0.0;
// толщина пера:
gw.PenWidth := 4;
gw.PenColor := 'Black';
// цвет заливки:
var clr := 240.0;
gw.BrushColor := gw.GetColorFromRGB(clr, clr, clr);
var xc := CX;
var yc := CY;
for var j := 0 to nrot-1 do begin
alpha := j * 45.0 - 45;
// чертим квадрат:
Turtle.X := xc + r * Cos(DegToRad(alpha));
Turtle.Y := yc - r * Sin(DegToRad(alpha));
Turtle.CreateFigure;
for var i := 0 to n do begin
x := xc + r * Cos(DegToRad(alpha));
y := yc - r * Sin(DegToRad(alpha));
if i = 0 then begin
Turtle.X := x;
Turtle.Y := y;
end
else
// чертим отрезок:
Turtle.MoveTo(x, y);
alpha += da;
end;
Turtle.FillFigure;
gw.PenWidth := 1;
// цвет заливки более тёмный:
clr /= 1.1;
gw.BrushColor := gw.GetColorFromRGB(clr, clr, clr);
r := r * Sqrt(2) / 2;
end;
end;
509
begin
Prepare;
Turtle.Speed := 100;
DrawSquares(4,12);
end.
Легко увидеть здесь фигуры из проекта Цветные вращающиеся многоугольники,
в роли которых выступают исключительно квадраты.
Пора приступать к экспериментам. Добавим в процедуру DrawSquares параметр
k, который отвечает за уменьшение длины сторон квадратов:
// ЧЕРТИМ КВАДРАТЫ
procedure DrawSquares(n, nrot: integer; k: double);
begin
// текущий угол:
var alpha := 0.0;
// приращение угла:
var da := 360.0 / n;
// радиус описанной окружности:
var r := GW_WIDTH / 1.42 - 10.0;
r /= k;
// координаты точек:
var x := 0.0;
var y := 0.0;
// толщина пера:
gw.PenWidth := 4;
gw.PenColor := 'Black';
// цвет заливки:
var clr := 240.0;
gw.BrushColor := gw.GetColorFromRGB(clr, clr, clr);
var xc := CX;
var yc := CY;
510
for var j := 0 to nrot-1 do begin
alpha := j * 45.0 - 45;
// чертим квадрат:
Turtle.X := xc + r * Cos(DegToRad(alpha));
Turtle.Y := yc - r * Sin(DegToRad(alpha));
Turtle.CreateFigure;
for var i := 0 to n do begin
x := xc + r * Cos(DegToRad(alpha));
y := yc - r * Sin(DegToRad(alpha));
if i = 0 then begin
Turtle.X := x;
Turtle.Y := y;
end
else
// чертим отрезок:
Turtle.MoveTo(x, y);
alpha += da;
end;
Turtle.FillFigure;
gw.PenWidth := 1;
// цвет заливки более тёмный:
clr /= 1.1;
gw.BrushColor := gw.GetColorFromRGB(clr, clr, clr);
//r := r * Sqrt(2) / 2;
r /= k;
end;
end;
begin
Prepare;
Turtle.Speed := 100;
//DrawSquares(4,12);
При k = 1.18 получается интересная картинка (Рис. 2):
DrawSquares(4,20,1.18);
end.
511
Рис. 1. Квадратура квадрата
512
Рис. 2. Квадраты уходят вдаль
513
Добавим в процедуру DrawSquares ещё один параметр – rot, который определяет
угол поворота следующего квадрата по отношению к текущему:
// ЧЕРТИМ КВАДРАТЫ
procedure DrawSquares(n, nrot: integer; k, rot: double);
begin
// текущий угол:
var alpha := 90.0;
// приращение угла:
var da := 360.0 / n;
// радиус описанной окружности:
var r := GW_WIDTH / 1.42 - 10.0;
//r /= k;
// координаты точек:
var x := 0.0;
var y := 0.0;
// толщина пера:
gw.PenWidth := 4;
gw.PenColor := 'Black';
// цвет заливки:
var clr := 240.0;
gw.BrushColor := gw.GetColorFromRGB(clr, clr, clr);
var xc := CX;
var yc := CY;
for var j := 0 to nrot-1 do begin
alpha := j * rot + 45;
// чертим квадрат:
Turtle.X := xc + r * Cos(DegToRad(alpha));
Turtle.Y := yc - r * Sin(DegToRad(alpha));
Turtle.CreateFigure;
for var i := 0 to n do begin
x := xc + r * Cos(DegToRad(alpha));
y := yc - r * Sin(DegToRad(alpha));
if i = 0 then begin
514
Turtle.X := x;
Turtle.Y := y;
alpha += da;
continue;
end
else
// чертим отрезок:
Turtle.MoveTo(x, y);
alpha += da;
end;
Turtle.FillFigure;
gw.PenWidth := 1;
// цвет заливки более тёмный:
clr /= 1.1;
gw.BrushColor := gw.GetColorFromRGB(clr, clr, clr);
r /= k;
end;
end;
begin
Prepare;
Turtle.Speed := 100;
//DrawSquares(4,12);
//DrawSquares(4,20,1.18);
DrawSquares(4,20,1.225, -15);
end.
Первоначально закрутка квадратов равна 45 градусам. Если вы уменьшите её до
15 градусов, то квадраты образуют вот такую замечательную спираль (Рис. 3).
Если вы хотите, чтобы вершины внутренних квадратов лежали на сторонах внешних, то подбирайте значение коэффициента s.
515
Рис. 3. Квадратная спираль
516
Движение объекта к цели
В этом проекте Черепашка будет собирать игровые объекты на сцене. Вы можете
представлять их как вкусняшки для Черепашки, а мы просто нарисуем красные
кружочки, которые хорошо видны как нам, так и Черепашке.
Сразу после запуска программы мы создаём первую цель в случайном месте
сцены и сразу отправляем к ней нашу Черепашку:
{$reference SmallVisualBasicLibrary.dll}
uses Microsoft.SmallVisualBasic.Library;
type
gw = GraphicsWindow;
// размеры окна:
const
GW_WIDTH = 720;
GW_HEIGHT = 720;
CX = GW_WIDTH div 2;
CY = GW_HEIGHT div 2;
var targetX, targetY : double;
// СОЗДАЁМ ЦЕЛЬ
procedure CreateTarget(rnd : boolean := false);
begin
var r := 32;
gw.BrushColor := 'Red';
if rnd then begin
targetX := Random(20, GW_WIDTH - 20);
targetY := Random(20, GW_HEIGHT - 20);
end
else begin
targetX := double(Mouse.X - gw.Left) - 6;
targetY := double(Mouse.Y - gw.Top) - r;
end;
gw.FillEllipse(targetX-r/2, targetY-r/2, r, r);
517
Turtle.MoveTo(targetX, targetY);
end;
begin
Prepare;
Turtle.Speed := 7;
Turtle.Show;
CreateTarget(true);
end.
В процедуре Prepare мы назначаем процедуру-обработчик для события нажатия
кнопки мышки:
procedure Prepare();
begin
gw.Hide();
gw.Title := 'Цвижение объекта к цели';
gw.Width := GW_WIDTH;
gw.Height := GW_HEIGHT;
gw.Show();
gw.Left := (Desktop.Width - gw.Width) / 2;
gw.Top := (Desktop.Height - gw.Height) / 2;
gw.BackgroundColor := 'LightGreen';
gw.MouseDown += OnMouseDown;
end;
Следующие цели создаются не случайно, а на месте клика мышки, то есть мы сами
управляем действиями Черепашки:
// МЕТОД-ОБРАБОТЧИК НАЖАТИЯ КНОПКИ МЫШКИ
procedure OnMouseDown();
begin
CreateTarget();
end;
Рис. 1 показывает Черепашку в действии.
518
Рис. 1. Рождённая ползать
519
Мелкие цели не будят энтузиазм, но тогда и сама Черепашка мелковата для столь
возвышенных целей.
Не дожидаясь результатов естественной эволюции, мы можем увеличить Черепашку до подобающих случаю размеров. Для этого у Черепашки есть свойства
Width и Height:
begin
Prepare;
Turtle.Show;
Turtle.Speed := 7;
Turtle.Width := 24;
Turtle.Height := 24;
CreateTarget(true);
end.
Теперь Черепашка вполне гармонирует с размерами целей и возложенными на
неё задачами (Рис. 2).
520
Рис. 2. Крупная Черепашка
521
Все в сборе
В этом проекте мы добавим нашей Черепашке толику интеллекта, чтобы она собирал артефакты по всей сцене. Этот процесс можно сравнить с покупкой вкусняшек в разных магазинах.
Как известно, уборочной предшествует посевная, поэтому мы
начинаем программу с создания некоторого числа целей.
Вполне достаточно десятка:
{$reference SmallVisualBasicLibrary.dll}
uses Microsoft.SmallVisualBasic.Library;
type
gw = GraphicsWindow;
// КЛАСС КООРДИНАТ ТОЧЕК
type Point = auto class
x, y : double;
end;
// размеры окна:
const
GW_WIDTH = 720;
GW_HEIGHT = 720;
CX = GW_WIDTH div 2;
CY = GW_HEIGHT div 2;
// число целей:
var nTargets := 10;
Для покупок Черепашке нужен список целей:
// список целей:
listTargets := new List<Point>();
522
После запуска программа создаёт цели в случайных местах сцены и помещает их
в список:
procedure Prepare();
begin
gw.Hide();
gw.Title := 'Все в сборе';
gw.Width := GW_WIDTH;
gw.Height := GW_HEIGHT;
gw.Show();
gw.Left := (Desktop.Width - gw.Width) / 2;
gw.Top := (Desktop.Height - gw.Height) / 2;
gw.BackgroundColor := 'LightGreen';
end;
// СОЗДАЁМ ЦЕЛИ
procedure CreateTargets();
begin
var r := 32;
gw.BrushColor := 'Red';
// создаём цели:
loop nTargets do begin
var x := Random(20, GW_WIDTH - 20);
var y := Random(20, GW_HEIGHT - 20);
listTargets.Add(new Point(x,y));
gw.FillEllipse(x-r/2, y-r/2, r, r);
end;
end;
После создания целей Черепашка отправляется на их собирательство:
// ЧЕРЕПАШКА ОБХОДИТ ЦЕЛИ
procedure Собирайка;
begin
foreach var p in listTargets do begin
// координаты очередной цели:
var x := p.x;
var y := p.y;
523
Turtle.MoveTo(x,y);
end;
end;
begin
Prepare;
Turtle.Show;
Turtle.Speed := 7;
Turtle.Width := 24;
Turtle.Height := 24;
CreateTargets();
Собирайка;
end.
В процедуре Собирайка Черепашка последовательно получает координаты целей
в порядке их создания и отправляется к очередной цели.
Запускаем программу, и наша Черепашка быстро и аккуратно собирает все цели
на сцене (Рис. 1).
524
Рис. 1. Не поспешишь – людей не насмешишь
525
Все в сборе 2
Поведение нашей Черепашки станет более разумным, если она будет собирать
цели не точно по списку, а каждый раз находить ближайшую цель.
Число целей я увеличил до 20, чтобы напрячь интеллект Черепашки до предела:
// число целей:
var nTargets := 20;
Весь искусственный разум Черепашки мы записали в процедуру Собирайка:
// ЧЕРЕПАШКА ОБХОДИТ ЦЕЛИ
procedure Собирайка;
begin
// координаты ближайшей цели:
var xb, yb : double;
var id := -1;
Пока список целей не пустой, мы находим в списке listTargets ближайшую цель:
while listTargets.Count > 0 do begin
var minDist := double.MaxValue;
// координаты Черепашки:
var mx := double(Turtle.X);
var my := double(Turtle.Y);
// ищем ближайшую цель:
for var i := 0 to listTargets.Count-1 do begin
var p := listTargets[i];
// координаты очередной цели:
var x := p.x;
var y := p.y;
Если очередная цель ближе предыдущих, мы запоминаем квадрат расстояния до
неё, координаты этой цели и её индекс в списке:
526
// дистанция:
var dist := (mx - x) ** 2 + (my - y) ** 2;
if dist < minDist then begin
minDist := dist;
xb := x;
yb := y;
id := i;
end;
end;
Если в списке есть цели, то найдётся и ближайшая к Черепашке. Эту цель мы удаляем из списка, и список укорачивается на 1 цель. Черепашка отправляется к выбранной цели, а мы в заголовке окна получаем информацию – сколько целей собрала Черепашка:
listTargets.RemoveAt(id);
Turtle.MoveTo(xb,yb);
gw.Title := $' Найдено целей: {nTargets - listTargets.Count}';
end;
end;
Запускаем программу и Черепашку.
Рис. 1 показывает, что Черепашка ведёт
себя не менее разумно, чем мы, если
бы вдруг она отправила нас на сбор целей.
527
Рис. 1. Вполне разумно
528
Лабиринт
Черепашка охотно и ловко ползает, поэтому в этом проекте мы запустим её в
настоящий Лабиринт! Наша задача – построить Лабиринт надлежащих размеров,
запустить туда Черепашку и водить её по запутанным коридорам до самого финиша этой книги. При большом желании вы можете научить Черепашку самостоятельно выбираться из Лабиринта, добавить к программе звуковые и прочие эффекты, но это уже слишком далеко от темы этой скромной книги.
Начнём издаля – с главного блока программы:
begin
Prepare;
// создаём Лабиринт:
MakeLabyrinth;
Turtle.Speed := 5;
Turtle.Show;
colTurtle := 1;
rowTurtle := 1;
Turtle.X := GetTurtleXY(1,1).x;
Turtle.Y := GetTurtleXY(1,1).y;
Turtle.Angle := 90;
gw.PenColor := 'Blue';
end.
В процедуре Prepare новинок не очень много:
procedure Prepare();
begin
gw.Hide();
gw.Title := 'Лабиринт';
gw.Width := GW_WIDTH;
gw.Height := GW_HEIGHT;
gw.Show();
529
gw.Left := (Desktop.Width - gw.Width) / 2;
gw.Top := (Desktop.Height - gw.Height) / 2;
gw.BackgroundColor := 'DarkGreen';
Поскольку Лабиринт двумерный, то вполне разумно описать его двумерным массивом. Тип элементов массива может быть самым простым:
// лабиринт:
var Labyrinth : array [,] of integer;
// массив Лабиринта:
Labyrinth := new integer[nCol, nRow];
Размеры Лабиринта в клетках хранятся в глобальных переменных:
// размеры Лабиринта в клетках по умолчанию:
var nCol := 49;
var nRow := 39;
Для ручного управления Черепашкой нам нужна соответствующая процедура:
// назначаем процедуру для обработки
// события нажатия на клавишу:
gw.KeyDown += OnKeyDown;
end;
Черепашка должна появиться в левом верхнем углу Лабиринта (Рис. 1). Эта её
стартовая позиция отмечена красным цветом. Выход из Лабиринта находится в
правом нижнем углу. Финишная клетка окрашена в зелёный цвет. Важно отметить, что Лабиринт со всех сторон окружён непроходимой стеной, так что покинуть его без динамита невозможно. Стены выкрашены в коричневый цвет, а проходы/коридоры – в белый. Наша Черепашка не настолько пробивная, чтобы проходить сквозь стены, поэтому должна передвигаться строго по белым клеткам.
530
Рис. 1. На старт!
При каждом запуске программы создаётся новый Лабиринт, так что вы можете
гонять Черепашку вечно. За случайность у нас отвечает переменная rand:
531
// генератор псевдослучайных чисел:
var rand := new Random;
Для этого мы подключаем к программе новые динамические библиотеки:
{$reference SmallVisualBasicLibrary.dll}
uses Microsoft.SmallVisualBasic.Library, System, System.Collections;
type
gw = GraphicsWindow;
// размеры окна:
const
GW_WIDTH = 800;
GW_HEIGHT = 640;
Построение Лабиринта начинается с расстановки стен и пустых клеток:
// СОЗДАЁМ НОВЫЙ ЛАБИРИНТ
procedure MakeLabyrinth();
begin
// всего пустых клеток в Лабиринте:
var EmptyCells := 0;
for var row := 0 to nRow-1 do
for var col := 0 to nCol-1 do begin
Labyrinth[col,row] := WALL;
if (col*row mod 2 = 1) then begin
Labyrinth[col,row] := EMPTY;
EmptyCells += 1;
end;
end;
Статус клеток мы обозначили константами:
// клетки в Лабиринте -->
// пустая клетка в лабиринте:
532
const EMPTY = 0;
// свободная клетка (проход, коридор):
const FREE = 1;
// стена:
const WALL = 15;
После первого этапа строительства Лабиринт совершенно непроходим (Рис. 2).
Теперь мы должны пробить проходы между соседними клетками так, чтобы получился Лабиринт, из которого можно выбраться. Для этого есть много алгоритмов, но нам достаточно самого простого алгоритма со стеком.
Построение Лабиринта можно начинать с любой пустой клетки, потому что все
свободные клетки будут связаны друг с другом проходами.
Для разнообразия мы выбираем случайную пустую клетку и объявляем её свободной:
// алгоритм построения лабиринта DFS
// ---------------------------------// старт - в верхнем левом углу:
var col := 1;
var row := 1;
// старт в произаольной клетке:
repeat
col := rand.Next(nCol-1) + 1;
row := rand.Next(nRow-1) + 1;
until (col * row mod 2 = 1);
Labyrinth[col,row] := FREE;
DrawLabyrinth;
На экране свободные клетки окрашены в белый цвет (Рис. 3). Только по таким
клеткам может ползать Черепашка.
Для алгоритма нужен стек:
533
// очищаем стек:
var st := new System.Collections.Stack();
Рис. 2. Выхода нет…
534
Рис. 3. Проходная клетка
Кладём на стек первую свободную клетку:
// добавляем в него первую клетку:
st.Push(col*1000+row);
535
// число готовых клеток:
var nDone := 1;
Чтобы не мудрить со структурами данных, мы считаем, что на стеке лежат целые
числа, поэтому упаковываем 2 координаты клетки в одно число.
Сейчас мы находимся в свободной клетке, и хотим пробить стену в соседнюю пустую клетку. Для этого мы ищем возможные направления для продолжения прохода. Все направления запоминаем в массиве Direction:
// направления из текущей клетки:
var Direction := new integer[5];
// пробиваем проходы между пустыми клетками:
while (nDone < EmptyCells) do begin
// считаем стены возле текущей клетки,
// после которых есть ещё не посещённая клетка -->
var cr := integer(st.Peek());
// число стен:
var nWall := 0;
// сверху:
col := cr div 1000;
row := cr mod 1000 - 1;
if (row > 1) and
(Labyrinth[col,row] = WALL) and
(Labyrinth[col, (row-1)] = EMPTY) then begin
nWall += 1;
Direction[nWall] := NORTH;
end;
// справа:
col := cr div 1000 + 1;
row := cr mod 1000;
if (col < nCol-2) and
(Labyrinth[col,row] = WALL) and
(Labyrinth[col+1, row] = EMPTY) then begin
nWall += 1;
Direction[nWall] := EAST;
end;
// снизу:
536
col := cr div 1000;
row := cr mod 1000 + 1;
if (row < nRow-2) and
(Labyrinth[col,row] = WALL) and
(Labyrinth[col,(row+1)] = EMPTY) then begin
nWall += 1;
Direction[nWall] := SOUTH;
end;
// слева:
col := cr div 1000 - 1;
row := cr mod 1000;
if (col > 1) and
(Labyrinth[col,row] = WALL) and
(Labyrinth[col-1,row] = EMPTY) then begin
nWall += 1;
Direction[nWall] := WEST;
end;
Направления движения в Лабиринте обозначаем константами:
// направления движения в Лабиринте:
const NORTH = 0;
const EAST = 1;
const SOUTH = 2;
const WEST = 3;
Если у свободной клетки есть стены, а за ними свободная клетка, то мы выбираем
случайное направление:
// есть стены:
if (nWall > 0) then begin
nDone += 1;
// выбираем случайное направление:
var rndDir := rand.Next(nWall)+1;
В зависимости от выбранного направления мы пробиваем стену. Клетка с бывшей
стеной и следующая за ней пустая клетка становятся свободными:
537
// идём наверх:
if (Direction[rndDir] = NORTH) then begin
col := cr div 1000;
row := cr mod 1000 - 1;
Labyrinth[col,row] := FREE;
Labyrinth[col,(row-1)] := FREE;
DrawCell(col,row, FREE_COLOR);
DrawCell(col,row-1,FREE_COLOR);
Мы закрашиваем их белым цветом и запоминаем координаты второй из новых
свободных клеток на стеке, чтобы потом идти из неё дальше:
st.Push(col*1000 + row-1);
end
// идём направо:
else if (Direction[rndDir] = EAST) then begin
col := cr div 1000 + 1;
row := cr mod 1000;
Labyrinth[col,row] := FREE;
Labyrinth[col+1,row] := FREE;
DrawCell(col,row,FREE_COLOR);
DrawCell(col+1,row,FREE_COLOR);
st.Push((col+1)*1000 + row);
end
// идём вниз:
else if (Direction[rndDir] = SOUTH) then begin
col := cr div 1000;
row := cr mod 1000 + 1;
Labyrinth[col,row] := FREE;
Labyrinth[col, row+1] := FREE;
DrawCell(col,row, FREE_COLOR);
DrawCell(col,row+1, FREE_COLOR);
st.Push(col*1000 + row+1);
end
// идём влево:
else begin
col := cr div 1000 - 1;
row := cr mod 1000;
Labyrinth[col,row] := FREE;
538
Labyrinth[col-1,row] := FREE;
DrawCell(col,row, FREE_COLOR);
DrawCell(col-1,row, FREE_COLOR);
st.Push((col-1)*1000 + row);
end
end
Если у текущей свободной клетки нет стен, то мы извлекаем со стека предпоследнюю свободную клетку, чтобы попытаться продвинуться из неё дальше:
// нет стен:
else
st.Pop();
end; // end Loop
Рано или поздно все пустые клетки превратятся в свободные, и на этом построение Лабиринта закончится:
DrawCell(1,1, 'Red');
DrawCell(nCol-2,nRow-2, 'Lime');
// Лабиринт построен:
//gw.ShowMessage('Лабиринт готов!', 'Лабиринт');
end;
К этому времени весь Лабиринт уже должен быть нарисован на экране, и мы
должны запрограммировать этот процесс.
Весь Лабиринт состоит из клеток. Очень удобно рисовать каждую клетку в отдельной процедуре DrawCell:
// РИСУЕМ ОДНУ КЛЕТКУ
procedure DrawCell(col, row: integer; color: string);
begin
// col, row - координаты клетки в Лабиринте
// сolor - её цвет
539
// координаты верхнего левого угла клетки:
var pos := GetXY(col, row);
var x := pos.x;
var y := pos.y;
gw.BrushColor := color;
gw.FillRectangle(x, y, CELL_WIDTH, CELL_HEIGHT);
end;
Опять же для удобства координаты каждой клетки и Черепашки вычисляем в отдельных процедурах:
// ВОЗВРАЩАЕТ ПИКСЕЛЬНЫЕ КООРДИНАТЫ КЛЕТКИ
function GetXY(col, row: integer) : Point;
begin
var offsetX := (GW_WIDTH - (nCol+1) * CELL_WIDTH) / 2;
var offsetY := (GW_HEIGHT - (nRow+1) * CELL_HEIGHT) / 2;
// координаты верхнего левого угла клетки:
var xPosition := offsetX + CELL_WIDTH div 2 + CELL_WIDTH * col;
var yPosition := offsetY + CELL_HEIGHT div 2 + CELL_HEIGHT * row;
Result := new Point(xPosition, yPosition);
end;
// ВОЗВРАЩАЕТ ПИКСЕЛЬНЫЕ КООРДИНАТЫ ЧЕРЕПАШКИ
function GetTurtleXY(col, row: integer) : Point;
begin
var x := GetXY(col,row).x + CELL_WIDTH div 2;
var y := GetXY(col,row).y + CELL_HEIGHT div 2;
Result := new Point(x, y);
end;
Размеры и цвета клеток храним в константах:
// размеры клеток:
const CELL_WIDTH = 16;
const CELL_HEIGHT = 16;
// цвета клеток -->
// цвет стен:
const WALL_COLOR = 'Brown';
540
//цвет проходов:
const FREE_COLOR = 'White';
//цвет пустых клеток:
const EMPTY_COLOR = 'Yellow';
Теперь мы без труда нарисуем и весь Лабиринт целиком:
// РИСУЕМ ЛАБИРИНТ НА ЭКРАНЕ
procedure DrawLabyrinth();
begin
// число клеток в Лабиринте:
var nCell := nRow*nCol;
for var i := 0 to nCell-1 do begin
var row := i div nCol;
var col := i - row * nCol;
if (Labyrinth[col, row] = WALL) then
DrawCell(col, row, WALL_COLOR)
else if (Labyrinth[col, row] = FREE) then
DrawCell(col, row, FREE_COLOR)
else if (Labyrinth[col, row] = EMPTY) then
DrawCell(col, row,EMPTY_COLOR);
end;
end;
Лабиринт готов к эксплуатации, а Черепашка заняла своё место на старте. Теперь
пришёл наш черёд нажимать клавиши со стрелками:
// НАЖИМАЕМ КЛАВИШУ
procedure OnKeyDown();
begin
var dir := gw.LastKey.ToString;
case dir of
'Right': TurtleMoveRight;
'Left': TurtleMoveLeft;
'Up': TurtleMoveUp;
'Down': TurtleMoveDown;
end;
end;
541
Свойство LastKey хранит данные о последней нажатой клавише. Клавиши со
стрелками имеют вполне понятные названия и вызывают вполне понятные процедуры.
В этих процедурах мы, в первую очередь, проверяем, допустим ли правилами
игры ход в указанном направлении. Если Черепашка уже упёрлась в стену, то она
так и останется на своём месте:
// ХОД ЧЕРЕПАШКИ
procedure TurtleMoveRight;
begin
var newCol := colTurtle + 1;
if Labyrinth[newCol, rowTurtle] = WALL then exit;
Если Черепашка может дать ходу, то мы вычисляем её новые координаты и отсылаем её куда поближе:
colTurtle := newCol;
var xy := GetTurtleXY(colTurtle, rowTurtle);
Turtle.MoveTo(xy.x, xy.y);
IsGameOver;
end;
procedure TurtleMoveLeft;
begin
var newCol := colTurtle - 1;
if Labyrinth[newCol, rowTurtle] = WALL then exit;
colTurtle := newCol;
var xy := GetTurtleXY(colTurtle, rowTurtle);
Turtle.MoveTo(xy.x, xy.y);
После каждого хода Черепашки проверяем, не закончилась ли игра:
IsGameOver;
end;
542
procedure TurtleMoveUp;
begin
var newRow := rowTurtle - 1;
if Labyrinth[colTurtle, newRow] = WALL then exit;
rowTurtle := newRow;
var xy := GetTurtleXY(colTurtle, rowTurtle);
Turtle.MoveTo(xy.x, xy.y);
IsGameOver;
end;
procedure TurtleMoveDown;
begin
Println('TurtleMoveDown');
var newRow := rowTurtle + 1;
if Labyrinth[colTurtle, newRow] = WALL then exit;
rowTurtle := newRow;
var xy := GetTurtleXY(colTurtle, rowTurtle);
Turtle.MoveTo(xy.x, xy.y);
IsGameOver;
end;
Наша незамысловатая игра заканчивается в единственном случае – когда Черепашка доберётся до финишной клетки в правом нижнем углу Лабиринта:
// ПРОВЕРЯЕМ, НЕ ЗАКОНЧИЛАСЬ ЛИ ИГРА
procedure IsGameOver();
begin
if (colTurtle = nCol - 2) and (rowTurtle = nRow - 2) then begin
gw.Title := 'ИГРА ЗАКОНЧЕНА!';
end;
end;
В отличие от подслеповатой Черепашки мы видим весь Лабиринт полностью с
высоты человеческого взора и разума, поэтому блуждания по Лабиринту ограничиваются только нашей наблюдательностью и прозорливостью. Конечно и естественно, каждый любитель прекрасного и программирования, добравшийся вместе с Черепашкой до финиша, тот ещё проходимец в Лабиринте (Рис. 4).
543
Рис. 4. ПОБЕДА БУДЕТ ЗА НАМИ!
544
Задания для самостоятельного решения
Черепашка
Напишите такие программы для Черепашки, чтобы она построила следующие
фигуры (Рис. 1).
Рис. 1. Черепашьи фигуры
Черепашка Tortoise
Какую фигуру вычертит Черепашка, выполняя следующий код:
Tortoise.Move(100);
Tortoise.Turn(90);
Tortoise.Move(100);
545
А такой код:
for var i := 1 to 4 do
begin
Tortoise.Move(100);
Tortoise.Turn(90);
end;
Полярные кривые
Постройте другие кривые в полярных координатах, например, овалы Кассини. Поэкспериментируйте с различными значениями параметров, входящих в формулу.
546
Справочник
Здесь вы найдёте полезную информацию о классах библиотеки
Microsoft.SmallBasic.Library, которую мы использовали в наших проектах.
Таблица <Класс Desktop>
Класс Desktop предоставляет методы для определения размеров Рабочего стола.
Класс находится в пространстве имён Microsoft.SmallBasic.Library.
Методы
Описание
Width()
integer
Возвращает ширину Рабочего стола в пикселях.
Height()
integer
Возвращает высоту Рабочего стола в пикселях.
SetWallPaper()
string fileOrUrl
Заменяет текущие обои картинкой, путь к которой задан строкой fileOrUrl.
Таблица <Класс GraphicsWindow>
Класс GraphicsWindow предоставляет свойства и методы для работы с Графическим окном.
Класс находится в пространстве имён Microsoft.SmallBasic.Library.
Свойства
Описание
547
BackgroundColor
string
string
Устанавливает или возвращает цвет фона Графического окна.
BrushColor
string
string
Устанавливает или возвращает цвет заливки фигур и текста.
PenColor
string
string
Устанавливает или возвращает цвет контура фигур и линий.
PenWidth
double
double
Устанавливает или возвращает толщину контура
фигур и линий.
CanResize
boolean
boolean
Если значение свойства равно true, то размеры
окна приложения допускается изменять. Если
значение свойства равно false, то размеры окна
приложения изменять нельзя.
FontBold
boolean
boolean
Если значение свойства равно true, то надписи
выводятся жирным шрифтом. Если значение
свойства равно false, то обычным шрифтом.
FontItalic
boolean
boolean
Если значение свойства равно true, то надписи
выводятся наклонным шрифтом. Если значение
свойства равно false, то прямым шрифтом.
FontName
string
Устанавливает или возвращает название текущего шрифта.
548
string
FontSize
integer
integer
Устанавливает или возвращает размер текущего шрифта.
Height
integer
integer
Устанавливает или возвращает высоту Графического окна.
Width
integer
integer
Устанавливает или возвращает ширину Графического окна.
Left
integer
integer
Устанавливает или возвращает x-координату левого верхнего угла Графического окна.
Top
integer
integer
Устанавливает или возвращает y-координату левого верхнего угла Графического окна.
Title
string
string
Устанавливает или возвращает заголовок Графического окна.
LastKey
string
Возвращает название последней нажатой клавиши.
LastText
string
Возвращает символ последней нажатой клавиши.
549
MouseX
integer
Возвращает x-координату курсора мышки в Графическом окне.
MouseY
integer
Возвращает y-координату курсора мышки в Графическом окне.
Методы
Описание
Clear()
Стирает Графическое окно, заливая его цветом
фона.
DrawText()
double х
double y
string text
Печатает текст text текущим шрифтом, начиная
с указанных координат (x,y) верхнего левого
угла описанного прямоугольника.
DrawBoundText()
double х
double y
double width
string text
Печатает текст text текущим шрифтом, начиная
с указанных координат (x,y) верхнего левого
угла описанного прямоугольника, ширина которого равна width.
DrawEllipse()
double х
double y
double width
double height
Чертит эллипс с осями width и height. Координаты верхнего левого угла описанного прямоугольника находятся в заданной точке (x,y).
Цвет символов определяется текущим значением свойства BrushColor.
Цвет символов определяется текущим значением свойства BrushColor.
550
Цвет контура определяется текущим значением
свойства PenColor. Толщина контура определяется текущим значением свойства PenWidth.
DrawImage()
string imageName
double х
double y
Печатает заданную картинку imageName так, что
координаты её верхнего левого угла находятся
в заданной точке (x,y), а размеры не изменяются.
DrawResizedImage()
string imageName
double х
double y
double width
double height
Печатает заданную картинку imageName так, что
координаты её верхнего левого угла находятся
в заданной точке (x,y), а размеры - width х height
DrawLine()
double х1
double y1
double х2
double y2
Чертит отрезок прямой с началом в точке (x1,y1)
и концом в точке (x2,y2) .
DrawRectangle()
double х
double y
double width
double height
Чертит прямоугольник размером width х height.
Координаты верхнего левого угла прямоугольника находятся в точке (x,y).
Цвет линии определяется текущим значением
свойства PenColor. Толщина линии определяется текущим значением свойства PenWidth.
Цвет контура определяется текущим значением
свойства PenColor. Толщина контура определяется текущим значением свойства PenWidth.
551
DrawTriangle()
double х1
double y1
double х2
double y2
double х3
double y3
Чертит треугольник с заданными координатами
вершин (x1,y1), (x2,y2) и (x3,y3).
FillEllipse()
double х
double y
double width
double height
Рисует эллипс с осями width и height. Координаты верхнего левого угла описанного прямоугольника находятся в заданной точке (x,y).
FillRectangle()
double х
double y
double width
double height
Рисует прямоугольник размером width х height.
Координаты верхнего левого угла прямоугольника находятся в точке (x,y).
FillTriangle()
double х1
double y1
double х2
double y2
double х3
double y3
Рисует треугольник с заданными координатами
вершин (x1,y1), (x2,y2) и (x3,y3).
GetColorFromRGB()
double red
Возвращает цвет, заданный цветовыми составляющими red, green, blue.
Цвет контура определяется текущим значением
свойства PenColor. Толщина контура определяется текущим значением свойства PenWidth.
Цвет заливки определяется текущим значением
свойства BrushColor.
Цвет заливки определяется текущим значением
свойства BrushColor.
Цвет заливки определяется текущим значением
свойства BrushColor.
552
double green
double blue
string
GetRandomColor()
string
Возвращает случайный цвет.
GetPixel()
integer х
integer y
string
Возвращает цвет пикселя с координатами (x,y).
SetPixel()
integer х
integer y
string color
Окрашивает пиксель с координатами (x,y) в цвет
color.
Hide()
Скрывает Графическое окно.
Show()
Показывает Графическое окно.
ShowMessage()
string text
string title
Показывает диалоговое окно с кнопкой ОК и сообщением text.
События
Заголовок окна определяется значением параметра title.
Описание
KeyDown
Возникает при нажатии на клавишу.
KeyUp
Возникает при отпускании клавиши.
553
MouseDown
Возникает при нажатии на кнопку мышки.
MouseUp
Возникает при отпускании кнопки мышки.
MouseUp
Возникает при перемещении мышки.
TextInput
Возникает при вводе текста с клавиатуры.
Таблица <Класс Timer>
Класс Timer предоставляет свойства и методы для работы с системными часами.
Класс находится в пространстве имён Microsoft.SmallBasic.Library.
Свойства
Interval
integer interval
integer
Описание
Устанавливает заданный интервал interval в
миллисекундах между срабатываниями (тиками) таймера.
Возвращает интервал в миллисекундах между
срабатываниями таймера.
Методы
Описание
Pause()
Приостанавливает работу таймера.
Resume()
Возобновляет работу таймера.
События
Описание
554
Tick
Возникает при каждом срабатывании (тике) таймера.
Таблица <Класс Turtle>
Статический класс Turtle предоставляет свойства и методы для работы с Черепашкой.
Класс находится в пространстве имён Microsoft.SmallBasic.Library.
Свойства
Angle
double angle
double
Описание
Устанавливает заданный угол angle в градусах.
Положительные углы отсчитываются против часовой
стрелки, отрицательные – по часовой стрелке. При значении
свойства Angle = 0 Черепашка смотрит вверх.
Возвращает угол поворота Черепашки в градусах.
X
double x
double
Устанавливает Черепашку в точку канвы, с x-координатой,
равной x.
Y
double y
double
Устанавливает Черепашку в точку канвы, с y-координатой,
равной y.
Возвращает x-координату Черепашки в пикселях.
Возвращает y-координату Черепашки в пикселях.
555
Speed
integer speed
integer
Устанавливает относительную скорость перемещения Черепашки в диапазоне от 1 (очень медленно) до 10 (очень
быстро).
Возвращает относительную скорость перемещения Черепашки.
Методы
Описание
Hide()
Скрывает Черепашку.
Show()
Показывает Черепашку.
Move()
Перемещает Черепашку на заданное расстояние distance
double dis- в пикселях в направлении текущего поворота Черепашки
tance
(вперёд).
MoveTo()
double x
double y
Перемещает Черепашку в точку канвы, заданную координатами (x,y).
TurnLeft()
Поворачивает Черепашку на 90 градусов против часовой
стрелки (влево) относительно текущего положения.
TurnRight()
Поворачивает Черепашку на 90 градусов по часовой стрелке
(вправо) относительно текущего положения.
Turn()
double angle
Поворачивает Черепашку на заданный угол angle в градусах
относительно текущего положения.
556
Если угол angle положительный, то Черепашка поворачивается вокруг своей оси против часовой стрелки, если отрицательный – по часовой стрелке.
PenDown()
Опускает карандаш – Черепашка при перемещении чертит
линии.
PenUp()
Поднимает карандаш – Черепашка при перемещении не
чертит линии.
Таблица <Класс Tortoise>
Статический класс Tortoise предназначен для управления Черепашкой.
Класс находится в библиотеке SmallBasicFun.
Методы
Описание
GetAngle()
double
Возвращает текущий угол поворота Черепашки.
GetPenColor()
string
Возвращает цвет линий.
GetPenWidth()
double
Возвращает толщину линий в пикселях.
GetSpeed()
integer
Возвращает текущую скорость перемещения Черепашки.
557
GetX()
double
Возвращает x-координату Черепашки.
GetY()
double
Возвращает y-координату Черепашки.
Hide()
Скрывает Черепашку.
InstantSpeed()
boolean goFast
Устанавливает максимально возможную скорость Черепашки, если значение параметра goFast равно true.
Show()
Показывает Черепашку на экране.
Move()
double distance
Перемещает Черепашку на заданное расстояние distance в
пикселях в направлении текущего поворота Черепашки
(вперёд).
MoveTo()
double x
double y
Перемещает Черепашку в точку канвы, заданную координатами (x,y).
Reset()
Создаёт Черепашку с параметрами по умолчанию.
SetAngle()
double angle
Поворачивает Черепашку в заданное направление.
SetOrientation()
int x
int y
Одновременно перемещает Черепашку в точку (x,y) и поворачивает её в заданное направление.
Если angle = 0, то Черепашка смотрит вверх.
Если angle = 180, то Черепашка смотрит вниз.
558
double angle
SetPenColor()
string color
Устанавливает цвет линий.
SetPenWidth()
double width
Устанавливает толщину линий в пикселях.
SetPosition()
integer x
integer y
Переносит Черепашку в указанную точку (x,y).
SetSpeed()
integer speed
Устанавливает относительную скорость перемещения Черепашки в диапазоне от 1 (очень медленно) до 10 (очень
быстро).
SetX()
double x
Задаёт Черепашке x-координату.
SetY()
double y
Задаёт Черепашке y-координату.
Turn()
double angle
Поворачивает Черепашку на заданный угол angle в градусах относительно текущего положения.
Если угол angle положительный, то Черепашка поворачивается вокруг своей оси против часовой стрелки, если отрицательный – по часовой стрелке.
PenDown()
Опускает карандаш – Черепашка при перемещении чертит
линии.
559
PenUp()
Поднимает карандаш – Черепашка при перемещении не
чертит линии.
560
Литература
[ГМ93]
Гарднер Мартин
От мозаик Пенроуза к надёжным шифрам
М.: Мир, 1993. – 417 с.
ISBN 5-03-001991-Х
[ММ05]
Максим Мозговой
Занимательное программирование
Питер, 2005. – 208 с.
Броуновское движение – стр. 20-23.
Черепашья графика – стр. 45 -48.
561
[HR81] [HR74]
Ross Honsberger
Mathematische Edelsteine der elementaren
Kombinatorik, Zahlentheorie und Geometrie
Friedr. Vieweg & Sohn - Braunschweig;
Wiesbadeo: Vieweg, 1981.
Ross Honsberger
Mathematical Gems I: The Dolciani Mathematical Expositions from elementary combinatorics, number theory, and geometry
The Mathematical Assotiation of America,
1974. - 176 c.
562