На главную страницу Написать письмо Добавить в избранное Сделать www.comprog.ru стартовой

PUZZLE на JavaScript

  Ты не должен жить так, словно твоя жизнь - это только подготовка к жизни. Подготовка - это иллюзия. То, к чему ты готовился, и есть то, чем ты занимаешься, то есть подготовка.
Р. Шекли. "Координаты чудес".

От автора-1.
Когда черновой вариант статьи был окончен, мне попалась на глаза разработка Jigsaw Puzzle Version 2 by Xin Yang. Это довольно продвинутая вещь, в которой реализована возможность создания puzzle без ручного нарезания исходной картинки. Идея, которую применил Xin, мне очень понравилась (отчасти, из-за своей простоты), но я не стал переписывать сделанное с тем, чтобы не запутать читателя и не погрязнуть в ненужных технических деталях, т.к. стремился раскрыть основные моменты создания puzzle в JS на возможно более простом уровне.

От автора-2.
Когда статья была уже полностью написана, многократные ее перечитывания привели меня к мысли, что она стала слишком тяжеловесной. Я решил, что некоторые из собранных разнородных сведений стоит оформить в виде отдельных статей, а в основной статье просто на них ссылаться. Так мне удалось не только сократить данную статью, но и представить читателям, которые не интересуются созданием JS-puzzle, несколько небольших и, надеюсь, полезных проблемных статей.

От автора-3.
Когда я, в конце-концов ;), сделал абсолютно все, что хотел, я написал "От автора-1" и "От автора-2". А потом решил все-таки приложить к статье и такой puzzle, чтобы кусочки нарезать не было нужно. После чего написал уже и "От автора-3". А потом быстренько вложил статью в письмо и отправил куда-подальше (т.е. в журнал ;)), чтобы не зацепиться еще за что-нибудь ;)

Что здесь есть.
Некоторые моменты, с которыми неизбежно сталкивается разработчик puzzle на JS в IE. В приложении приведены два работающих скрипта (один требует предварительной нарезки фрагментов puzzle, а второй - нет) и страница-пример, которая их использует.

Чего здесь нет.
"Разжевывания" кода скриптов в приложениях. Там нет ничего сложного для читателя, владеющего JS на среднем уровне, и изучившего настоящую статью.

Подготовка картинки.
Перед тем, как програмить puzzle (нарезную версию), нужно нарезать выбранную картинку на одинаковые кусочки. Быстро сделать это вручную можно в Photoshop (пользуясь сеткой (grid) и режимом "Snap To Grid") либо других программах работы с графикой, некоторые из которых могут предоставлять специальный инструмент для нарезки.
В первом скрипте из приложения принято такое соглашение о нумерации файлов с кусочками: кусочек, стоящий в i-й строке и j-м столбце (обе величины отсчитываются от 1) имеет имя "ij". Расширение - в зависимости от выбранного формата.

Примечание - А.Р.
Если есть возможность, можно нарезать вообще безо всяких усилий, запустив такую вот PHP-программку (не забудьте раскомментировать строку "extension=php_gd2.dll" в php.ini и указать свои значения в каждой строке, за которой стоит комментарий):

<?
  $m = imagecreatefromjpeg ("all.jpg"); # картинка-жертва
  $rows = 5; # количество строк
  $cols = 3; # и столбцов нарезки
  $cellwidth = 83;  # ширина и
  $cellheight = 70; # высота кусочка
  $t = imagecreatetruecolor($cellwidth,$cellheight);

  for ( $i=0; $i<$rows; $i++)
   for ( $j=0; $j<$cols; $j++) {
    imagecopy($t,$m,0,0,$j*$cellwidth,
$i*$cellheight,$cellwidth,$cellheight);
    imagejpeg($t,"new/".($i+1).($j+1).".jpg");
    # файлы-кусочки пишутся в каталог new/
   }

  imagedestroy($t);
  imagedestroy($m);
?>

Примечание - А.Р.
Пока резать ничего не надо. В приложении к статье есть уже нарезанная картинка.

Все начинается с логики:
Игра может находиться в одном из трех возможных состояний:

S1. Начальное состояние, оно же - конечное.
На игровом поле изображена исходная картинка и под ней кнопка "Новая игра".
S2. После нажатия кнопки "Новая игра".
На игровом поле изображена картинка, разбитая на кусочки и под ней - кнопка "Разбросать". Перемещать кусочки запрещено.
S3. После нажатия кнопки "Разбросать".
На игровом поле изображена картинка, разбитая на кусочки. Кусочки расставлены в случайном порядке. Теперь можно их перемещать. Доступны кнопки "Разбросать" и "Собрать".

Используя указанные состояния, логику игры можно изобразить в виде следующей автоматной диаграммы:

конечный автомат игры

И все-таки это puzzle.
Разумеется, для сборки puzzle нужно предоставить игроку возможность перетаскивать кусочки картинки. Как программируется перетягивание в JS, я написал в отдельной статье "О том, что плохо лежит на странице. Программируем драг на JS" из CompDocs#14. Изучите эту статью перед тем, как двигаться дальше.

При перетаскивании кусочков самым важным оказывается момент, когда кусочек отпускается, т.к. именно при этом необходимо решить, производить ли перестановку кусочков и, если нужно, - произвести таковую. Кроме того, игра заканчивается именно здесь, когда будет выполнена последняя перестановка кусочков и картинка будет собрана.

Бутерброд нужно держать так, чтобы вектор, соединяющий центр тяжести колбасы с центром тяжести хлеба, был коллинеарен вектору силы тяжести: Сколько-нибудь значительные отклонения приводят к диалектическим потерям: бутерброд теряет форму, а желудок - недополучает содержания:
При реализации перетаскивания уже в игре возникает следующая проблемка. Изобразим игровое поле puzzle 2x3:

фрагмент игрового поля

Будем считать, что html-коде страницы с игрой рисунки 1-6 перечислены по порядку от 1 до 6. Теперь пробуем перетащить рисунок 6. Он перекрывает все рисунки 1-5. Но если взять уже 5, то он не будет перекрывать рисунок 6! Суть здесь проста. Как известно, порядок перекрытия объектов на странице задается с помощью стилевого параметра zIndex: объект с большим значением zIndex перекрывает объект с меньшим значением. Если же у двух объектов значения zIndex равны, тогда "верхним" оказывается тот объект, который появляется в коде страницы вторым.
Чтобы решить эту проблему, достаточно установить для рисунков 1-6 стилевой параметр z-index (при доступе через js - zIndex), к примеру, в значение 80000. В начале перетаскивания (левая кнопка нажата) - "поднимать" кусочек над другими, установив для него z-index = 88000, и в конце перетаскивания (левая кнопка отжата) - "опускать" на прежний уровень z-index = 80000. Тогда каждый перетаскиваемый кусочек будет перекрывать все остальные сверху.

Командовать парадом буду я!
Для полного управления самой игрой достаточно следующих переменных:

state
- (целое число) текущее состояние игры.
order
- (массив) перестановка кусочков puzzle. Длина массива равна количеству кусочков N. Кусочки будем нумеровать слева-направо и сверху-вниз (т.е. в том же порядке, что и на рисунке выше) от 0 до N-1. Начальное и конечное состояния соответствуют тождественной перестановке.

Программная структура.
Для реализации игры достаточно следующего набора функций:

mousedown, mousemove, mouseup
- обработчики мышиных событий для реализации перетаскивания
swap
- непосредственный обмен двух кусочков
complete
- проверка, собран ли puzzle, т.е. окончена ли игра
right
- действия по окончании игры
stress
- перемешивание кусочков (кнопка "Разбросать") + старт игры из состояния S1 (кнопка "Новая игра")
restart
- рестарт игры с состояния S2 (кнопка "Собрать")

Последний штрих.
Для того, чтобы не использовать длинные идентификаторы и, в то же время, не загружать глобальное пространство имен скриптов страницы такими ходовыми именами как "restart, swap" и иже с ними, весь глобальный код и все управляющие глобальные переменные нужно скрыть в одном-единственном объекте. Чтобы понять, как это делается, читайте мою статью "Кто был ничем, тот станет всем. Кратко об объектах в JS" из CompDocs#14.

Делай как мы, делай лучше нас!
Что можно улучшить? Разумеется, хотелось бы сделать так, чтобы не приходилось вручную нарезать кусочки puzzle. Но средствами JS картинки резать не удается :( (у объекта Image просто нет соотв. методов). Однако, выход есть :), и такая возможность реализована в Jigsaw Puzzle от Xin Yang (скачать можно здесь http://soft-search.pp.ru/programs/7-036-jigsaw-puzzle-download.shtml). Как Xin это сделал? Довольно просто! Вместо нарезки картинки на N кусочков создаются N картинок, каждая из которых - есть исходная картинка. В начальном состоянии игры все они позиционируются по одним и тем же координатам, и для каждой устанавливается область видимости по границам соответствующего кусочка. Таким образом, получаем "слоеный пирожок", каждый слой которого содержит всю картинку, но отображает только необходимый кусочек. Чтобы узнать, как сделать видимым только фрагмент картинки, советую прочитать мою небольшую статью "О том, как явное становится тайным. CSS-свойство CLIP" из CompDocs#14.

Однако, не стоит так уж восхищаться этим решением. Оно хорошо выглядит в теории, но обладает рядом недостатков на практике. Например, оно является приемлемым только для небольших картинок и для небольшого же количества кусочков разбиения. Не смотря на то, что отображаются только нужные кусочки, похоже, IE скрытно обрабатывает всю картинку, что соответствующим образом отражается на производительности. Кроме того, есть еще один неявный тормоз: если размеры картинки не делятся нацело на заданное количество кусочков разбиения, приходится для тега <img> ставить размеры, отличные от оригинала. В таком случае на масштабирование каждой картинки приходятся дополнительные потери производительности. И еще одно. IE показывает полосы прокрутки, когда невидимая часть картинки выходит за пределы экрана:

Заключение.
Короче говоря, если бы я ставил на свой сайт страницу с JS-Puzzle, я бы выбрал вариант с нарезанием кусочков, и на сервере с помощью, например, PHP нарезал бы картинки на требуемые кусочки. Если же под руками нет ни выхода в сеть, ни PHP, JS-Puzzle без нарезки может прекрасно скрасить вынужденный досуг :)

puzzle_cut.js - скрипт, реализующий puzzle, требующий предварительной нарезки кусочков
puzzle_wcut.js - скрипт, реализующий puzzle, не требующий предварительной нарезки кусочков
puzzle_button.css - содержит описание стилевого класса puzzle_button для кнопок управления
puzzle_both.htm - тестовая страница, использующая оба скрипта
whole.jpg - целая картинка для второго скрипта
pieces - каталог, содержащий кусочки картинки whole.jpg для первого скрипта

Благодарю всех за внимание!

  Поиск по сайту
  
Яндекс цитирования