Игра “15” на чистом CSS, без единой строчки на javascript

Начало
Для начала создадим index.html. Нам будет нужна доска (.board)
и на ней разместим 15 костяшек (tiles: #t01, #t02, … #t15)
1 |
|
Для управления будем использовать radio-кнопки. Для каждой из 15-ти костяшек
придется создать группу из 16-ти radio-кнопок, по количеству ячеек в игре.
Чтобы состояние кнопок могло оказывать воздействие на доску, все кнопки
надо расположить выше элемента с доской, и нельзя их помещать ни в какой элемент.
Тогда с помощью селектора ~ и псевдокласса :checked можно будет считывать состояние
игры.
15 групп из 16 кнопок дают нам 15¹⁶ возможных состояний. В таком режиме не исключены варианты, что
две костяшки могут находиться в одной позиции. Но это дает большое упрощение для написания логики.
Так что создадим 16*15 radio-кнопок, каждой группе дадим уникальное имя
(name=c01, c02…c15 - controls), также дадим контролам css классы
(x1…x4 - означает что контрол управляет соответствующим столбцом костяшки 1-4, y1-y4 - строкой)
1 | <body> |
Позиционируем костяшки
Добавим к элементу .board css-переменные с x и y координатами каждой костяшки. Итого будет 32 переменные.
Переменные будут вида --c01-x для x-координаты первой костяшки (может принимать значения 1, 2, 3, 4, в зависимости от столбца, с котором она находится, либо 0 если не инициализирована)
Сначала проинициализируем все переменные в 0:
1 | .board { |
А затем напишем по 4 правила на каждую их 30-ти переменных, которые выставят значение
1 | .c01.x1:checked ~ .board { --c01-x: 1; } /* нажата radio-кнопка #c01-x1, значит x-координата равна 1 */ |
Все эти переменные нам понадобятся для вычисления координаты пустой ячейки, но пока для каждой костяшки
скопируем в ее элемент ее координаты.
Например, у div#t01 соответствующего первой костяшке, выставим переменные --x и --y равным ее координатам, то есть, равным --c01-x и --c01--y
1 | #t01 { --x: var(--c01-x); --y: var(--c01-y); } |
Добавим немножнко стилей. У элемента .board зададим ширину 4em, чтоб удобнее было управлять размером
и позиционированием костяшек. Каждая из них будет иметь размер 1em x 1em, спозиционированная
абсолютно, с координатами 0em, 1em, 2em или 3em
1 | .board { |
В итоге получим такую страничку:

Пока что нам нужно вручную нажать на radio-кнопки, чтоб костяшки оказались на доске.
Вычисляем координаты свободной ячейки
Для вычисления, куда двигать костяшку при нажатии, потребуется знание того, какая из ячеек является
свободной. У нас уже есть x и y координаты каждой костяшки. При правильной расстановке всех
костяшек на доске, так, что никакие две костяшки не находятся в одной ячейке, оказывается, что
просуммировав все координаты всех 15-ти костяшек и прибавить к ним координату свободной ячейки,
мы получим 40. Мы получим четыре единички, четыре двойки, четыре тройки и четыре четверки.
Поэтому, отняв от 40 координаты всех костяшек, мы получим соответствующую координату свободной ячейки
1 | .board { |
Обрабатываем щелчки
По щелчку на костяшку мы должны поменять ее координату, то есть, в терминах нашей модели,
нужно поменять одну из radio-кнопок. Чтобы этого добиться, мы можем воспользоваться элементом<label for="...">.
В каждый из 15-ти элементов для костяшки положим 16 элементов label соответствующих ее radio-кнопкам.
Также для ужобство каждой из label дадим классы x1…x4 и y1…y4 по координатам, которыми она управляет.
1 | <div class="board"> |
Нам нужно чтоб только один из label был доступен для клика. Спозиционируем их всех абсолютно,
и будем вычислять их координаты, в зависимости от текущей свободной ячейки.
1 | .board > div { overflow: hidden; } |
Смысл здесь следующий: в каждой костяшке, размер которой равен 1em находятся 16 элементов, тоже размера 1em
спозицинированных по разным координатам. Но, из за overflow: hidden будет “видна” только одна, у которой
вычисленные координаты left и top обе равны нулю.
А для костяшки с координатой x выражение x - free-x будет равно нулю при их равенстве.
Поэтому, например, для всех label с классом x1, то есть, отправляющих костяшку по координатам 1, этаlabel будет видна (left: 0) только если free-x = 1. Аналогично для всех других.
Теперь если нажать на все radio-кнопки так, чтоб все костяшки оказались на поле и не накладывались
друг на друга, по клику костяшки будут перемещаться в свободное поле. Но, пока что, можно щелкнуть по любой костяшке, никакой проверки того, что поле рядом, нет.
Добавляем проверку костяшки, соседней с пустым полем
Для удобства добавим каждой костяшке переменные --dx и --dy равные смещению этой костяшки от свободной ячейки. Ранее мы добавляли костяшкам --x и --y, поэтому вычислим их так:
1 | .board > div { /* для каждой костяшки */ |
Нам нужно добиться того, чтоб |dx|=1 либо |dy|=1. Мы оперируем смещением label, и при таких значениях смещение должно быть нулем. А при других значениях должно быть ненулевым.
Модуля в css нет, но мы можем возвести в квадрат (то есть, домножить на себя). Тогда сложив dx² и dy² мы должны будем получить 1. Отняв единицу, получим искомое нулевое смещение.
В этом рассуждении есть небольшая ошибка. Например, если dx=2 а dy=0, после этих вычислений мы получим смещение 4 - 1 = 3. Но может так оказаться, что сдвинув все label на это смещение, по координатам 0 все же окажется другая label вычисленная по формуле x - free-x. Поэтому домножим все слагаемые в нашей формуле
на некоторое число, больше общей ширины доски, например, на 10
Тогда смещение будет 10 * dx² + 10 * dy² - 10, и оно будет:
- либо равно нулю, если одно из значений
dxилиdyравно единице, а другое нулю (т.е. ячейка соседняя со свободной) - либо равно достаточно большому числу, такому, что все
labelсильно сместятся от координаты0
1 | .board > div { |
Кстати, это смещение достаточно сделать лишь для одной из координат.
Теперь щелкаются костяшки только смежные с пустой ячейкой.
Генератор случайных чисел
Для начально расположения нам надо каким-то случайным образом заполнить начальные значения для всех
radio-кнопок. Но случайных числел в css тоже нет. Поэтому мы попросим пользователя 16 раз щелкнуть по
полю, а под мышку будем подставлять разные