Находясь под приятным впечатлением от кратенькой, но весьма остроумной и, не побоюсь этого слова, «культовой» программы, задался вопросом: «А можно ли сгенерировать подобную структуру в фоновом изображении сайта?». Захотелось создать бесконечный лабиринт, не повторяющийся в любом направлении. Вспомнил, где-то уже встречал метод, который и поможет мне любому стать Дедалом веб-дизайна.
html {background: url('fon.gif') repeat;}
Первое, что приходит на ум: нарисовать кусок лабиринта и просто размножить его. Лабиринт выйдет «ненастоящий» – повторение рисунка при «паркетной укладке» моментально бросится в глаза.
Усложним подход. Можно создать два фоновых изображения разного размера. В каждом из них в шахматном порядке будем чередовать прозрачные области и стенки лабиринта. И затем одно подложим под другое. Разноразмерность и случайная генерация стенок позволит создавать раличные комбинации ходов при наложении:
html {
background-image: url('fon1.gif');
background-repeat: repeat;
}
body {
background-image: url('fon2.gif');
background-repeat: repeat;
}
Уже теплее, но если неудачно выбрать размеры, то это не даст принципиального выигрыша по сравнению с background-repeat одного изображения. Пусть и не сразу, но структура лабиринта снова начнёт повторяться.
Кто ещё помнит математику за 7-й класс, сразу сообразит, что коэффициент «неповторения» будет равен частному от деления наименьшего общего кратного (НОК) на наибольший общий делитель (НОД) от длин сторон изображений. То есть, если одно изображение 3х3 элемента, а второе 6х6 то уже через 2 итерации лабиринт «начинается сначала».
Из этого следует печальный вывод, что бесконечно неповторяющегося узора достичь нельзя. Во всяком случае методами CSS. Можно рисовать Ява-скриптом, но… Вы понимаете…
Если уж бесконечный лабиринт воздвигнуть не судьба, то хотя бы сделаем фон как можно дольше неповторяющимся. Для этого нужно чтобы стороны обоих изображений представляли собой взаимно простые числа. В этом случае НОК равно произведению сторон, НОД — единице. А количество итераций «неповторения» в этом случае уже будет равно НОК, что весьма немало даже при небольших значениях. Если, например, взять 3х3 и 4х4, то только через 12 наложений мы обнаружим в затейливом узоре знакомые очертания. А если взять 13х23 и 17х19, то уникальность замысловатых маршрутов обеспечена для страницы любого размера.
Ободрившись подобными арифметическими выкладками, может показаться, что пора праздновать победу. Но коварная математика ставит ещё одну подножку.
Шахматные поля удобно накладывать друг на друга в случае чётности сторон. Поскольку мы хотим чтобы соотношение сторон у двух картинок были взаимно простым, то как минимум одна из них должна быть «нечётной». Эта нечётность и преподносит неприятный сюрприз в виде нерегулярного наложения не «теми» квадратиками. Уже при втором прогоне пустые квадратики ложатся на пустые, зарисованные — на зарисованные.
Смещение «нечётной» картинки на один ряд вправо/вниз ничего не решает. Покрутив-повертев варианты с различными комбинациями сторон я не придумал ничего лучшего, как делать нижнюю картинку без прозрачных ячеек. В этом случае 100% гарантия, что в фоне не будет зияющих пустот. Но php-скрипт частично работает вхолостую, отрисовывая стенки, которые всё равно будут не видны. Если есть идеи, как обойтись без «ненужной работы» — предлагайте. Помните, соотношение сторон разных изображений должно быть взаимно простым.
Собственно, всё. Демку смотрите здесь. Если дадут вожделенный инвайт на случай хабраэффекта я отключил генерацию каждый раз заново при загрузке страницы нового лабиринта. Но для интересующихся прилагаю полный листинг кода с php-скриптом, генерирующий фоновые пинги для логова Минотавра.
<?php include_once "generate_fon.php"; ?>
<html>
<title>10 PRINT CHR$(205.5+RND(1));: GOTO 10</title>
<style type='text/css'>
html, body {
padding: 0px; margin: 0px;
width: 100%; height: 100%;
}
html {
background-image: url('fon1.png');
background-repeat: repeat;
}
body {
background-image: url('fon2.png');
background-repeat: repeat;
}
#outer {
height: 300px;
line-height: 300px;
text-align: center;
}
#inner {
outline: solid #99c medium;
padding: 50px;
background: #336;
color: #99c;
font-weight: bold;
font-size: 16pt;
}
</style>
<body>
<div id='outer'><span id='inner'>10 PRINT CHR$(205.5+RND(1));: GOTO 10</span></div>
</body>
</html>
<?php
function create_background_image ($w, $h, $quadro, $is_chess, $numder) {
//w, h - кратность ширины/высоты фонового изображения
//$quadro - длина стороны одного квадратного элемента "стены лабиринта", px
//$is_chess - true/false, в шахматном ли порядке заполнять фон
//$numder - номер фонового файла для его названия (их нужно 2)
$img = imageCreate($w*$quadro, $h*$quadro);//Заготовка изображения, размеры
$color_transparent = imagecolorallocate ($img, 0, 0, 0);//Чёрный цвет будет выполнять роль прозрачного
$color_background = imagecolorallocate ($img, 51, 51, 102);//Цвет фона
$color_line = imagecolorallocate ($img, 153, 153, 204);//Цвет линий
imagesetthickness($img, 4);//Толщина "стенок"
//Рисуем "стенки лабиринта"
for ($i = 0; $i < $w; $i++) {
for ($j = 0; $j < $h; $j++) {
$sparseness = mt_rand(0, 8);//"Разреженность" лабиринта. 1 шанс из 9-ти что не станем рисовать "стенку"
if ($sparseness) {
//Не забываем про "шахматный порядок"
if (!($is_chess && ($i % 2 == $j % 2))) {
//Заливаем очередной квадратик фоновым цветом
imagefilledrectangle (
$img,
$i * $quadro, $j * $quadro, //Верхний левый угол
($i + 1) * $quadro, ($j + 1) * $quadro, //Нижний правый угол
$color_background //Цвет фона
);
//"Кидаем монетку", чтобы решить направление "стенки"
$direction = mt_rand(0, 1);
//Рисуем линию (случайно выбираем: или /)
if ($direction) {
imageline (
$img,
$i * $quadro, $j * $quadro, //Из левого верхнего угла...
($i + 1) * $quadro, ($j + 1) * $quadro, //... в правый нижний угол
$color_line //Цвет линий
);
} else {
imageline (
$img,
($i + 1) * $quadro, $j * $quadro, //Из правого верхнего угла...
$i * $quadro, ($j + 1) * $quadro, //... в левый нижний угол
$color_line //Цвет линий
);
}
} else {
//Этот квадратик должен быть прозрачным
imagefilledrectangle (
$img,
$i * $quadro, $j * $quadro, //Верхний левый угол
($i + 1) * $quadro, ($j + 1) * $quadro, //Нижний правый угол
$color_transparent //Цвет фона - прозрачный
);
}
} else {
//А вот и "разреженность" лабиринта проявилась
//"Стенку" не рисуем, но фон заливаем
imagefilledrectangle (
$img,
$i * $quadro, $j * $quadro, //Верхний левый угол
($i + 1) * $quadro, ($j + 1) * $quadro, //Нижний правый угол
$color_background //Цвет фона
);
}
}
}
imagecolortransparent ($img, $color_transparent);//Прозрачный цвет фона
imagepng ($img, "fon$numder.png");//Сохраняем фон в файл
imageDestroy($img);//Для очистки памяти "уничтожаем" переменную-картинку
}
$w1 = 17; $h1 = 19;//Кратность сторон первого изображения
$w2 = 23; $h2 = 13;//Кратность сторон второго изображения
$wall = 30; //Длина стороны одного квадратного элемента "стены лабиринта", px
$img1 = create_background_image($w1, $h1, $wall, false, 1);//Создаём нижнее фоновое изображение
$img2 = create_background_image($w2, $h2, $wall, true, 2);//Создаём верхнее фоновое изображение
?>
В скрипте я «проредил» генерацию стенок. В оригинальном алгоритме получается слишком много «слепых ходов», в которые невозможно попасть. Пробовал также развернуть на 45 градусов к зрителю, но вертикальные и горизонтальные линии стандартными средствами php рисуются как-то неказисто. В общем, для желающих внести в лабиринтостроительство инновации и улучшения – работы непочатый край.
Автор: valemak