Игра «Пятнашки» на JavaScript

Пятнашки — простая и многим с детства знакомая головоломка, поэтому я не буду рассказывать про правила, надеюсь, они вам знакомы. Во многих учебниках по программированию предлагается в качестве урока написать эту головоломку. Напишем и мы её на JavaScript. Верстать тут много не придётся, html-код короток и банален:

1
2
3
4
5
6
7
8
9
10
<html>
	<head>
		<title>Пятнашки</title>
		<script type="text/javascript" src="15.js"></script>
	</head>
	<body>
		<div id="box"></div>
           		<button id="reset">New Game<button>
	</body>
</html>

В

<div id="box"></div>

будет располагаться игровое поле. JavaScript код расположим в файле 15.js. Для хранения номеров костяшек будем использовать ступенчатый массив, то есть массив массивов. Забьём его по порядку числами от одного до пятнадцати, а значение последнего элемента установим равным пустой строке. Этому элементу будет соответствовать место не занятое костяшкой. Создадим функцию newGame и в её тело запишем:

1
2
3
4
5
6
7
8
9
10
var arr = [];
for(i = 0; i &lt; 4; ++i){
		arr[i] = [];
		for(j = 0; j &lt; 4; ++j){
			if(i + j != 6)
				arr[i][j] = i*4 + j + 1;
			else
				arr[i][j] = "";
		}
	}

Такое расположение элементов в массиве соответствует выигрышному расположению костяшек.

Перемешаем элементы в массиве по правилам игры, то есть будем менять местами элемент, содержащий пустую строку, с соседним, выбранным случайно, элементом достаточно много раз. Для этого, в начале, понадобится написать функцию, которая будет менять местами два элемента массива с заданными индексами.

1
2
3
4
5
function swap(arr,i1,j1,i2,j2){				
t = arr[i1][j1];
	arr[i1][j1] = arr[i2][j2];
	arr[i2][j2] = t;
}

Теперь напишем код, который будет отвечать за перемешивание. Этот код тоже расположим в функции newGame. Её мы будем вызывать, как только страничка будет загружена, или когда пользователь кликнет по кнопке “New game”.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
ei = 3;//Запоминаем индексы элемента массива,
ej = 3;// в котором записана пустая строка.
for(i = 0; i &lt; 2012; ++i) // На мой взгляд 2012 это достаточно много =)
		// Случайным образом выбираем число от 0 до 3
		switch(Math.round(3*Math.random())){
	/*
	 * 0 соответсвует верхней соседней костяшке, 1 - правой  и т.д.
	 * обратим внимание что обмен местами, например,
	 * с верхней костяшкой возможен, если "пустое место"
	 * не ноходится у верхней границы игрового поля. Аналогично и для
	 * других соседних костяшек. При обмене изменяем переменные ei и ej.
	 */						
			case 0: if(ei != 0) swap(arr,ei,ej,--ei,ej); break; 
			case 1: if(ej != 3) swap(arr,ei,ej,ei, ++ej); break; 
			case 2: if(ei != 3) swap(arr,ei,ej,++ei,ej); break; 
			case 3: if(ej != 0) swap(arr,ei,ej,ei,--ej); 
		}

Забить массив arr случайными неповторяющимися цифрами от 0 до 15 нельзя, поскольку не любое расположение костяшек можно привести к собранной комбинации. Теперь пишем код, с помощью которого мы сформируем таблицу, в ячейках которой будут располагаться уже перемешанные элементы массива arr. Этот код так же будет располагаться в функции newGame.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
var table = document.createElement("table"); //Cоздаём таблицу	
for(i = 0; i &lt; 4; ++i){
var row = document.createElement("tr"); //Добавляем в неё строки
		for(j = 0; j &lt; 4; ++j){
			var cell = document.createElement("td");//Cоздаём ячейки
			cell.id = i + " " + j;
/*
			 * Привязываем к событию, происходящему
			 * при клике по ячейке таблицы функцию
			 * cellClick 
			 */
			cell.onclick = cellClick;
//Записываем в ячейку соответсвующий эл-т массива
			cell.innerHTML = arr[i][j];
row.appendChild(cell);// Добавляем ячейку в строку
		}
	table.appendChild(row);// Добавляем строку в итаблицу			
}
/*
	 * Проверяем, нет ли у
 
<div id="box"> дочернего эл-та.
	 * То есть таблицы. Она уже будет на странице
	 * если  функция newGame вызвана нажатием
	 * кнопки "New game", а не при загрузки страницы.
*/
if(box.childNodes.length == 1) 
box.removeChild(box.firstChild); //Удаляем таблицу, если она есть	
box.appendChild(table);// Запихиваем в box table</div>

При клике на ячейку таблицы необходимо менять её местами с пустой ячейкой, если она соседняя. Мы уже привязали функцию cellClick к событию, происходящему при клике по ячейке таблицы. Теперь напишем код этой функции.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
function cellClick(e){				
var el = e.srcElement || e.target;
	/*
	 * получаем номер строки и столбца, на пересечении которых
	 * расположена ячейка. Мы записали их ранее в её id ячейки.
	 */
	var i = el.id.charAt(0),
	j = el.id.charAt(2);
	/*
	 * Если пустая ячейка расположена в одном стобце или строке
	 * с ячейкой, по которой кликнули, и расстояние между
	 * этими ячейками 1, то меняем их содержимое местами
	 */
if((i == ei &amp;&amp; Math.abs(j - ej) == 1) || (j == ej &amp;&amp; Math.abs(i - ei) == 1)){
document.getElementById(ei + " " + ej).innerHTML = el.innerHTML;
	el.innerHTML = "";
	//Запоминаем положение пустой ячейки
	ei = i;
	ej = j;
	var q = true;
	//Проверяем не в выигрышной ли комбинации находятся ячейки.
	for(i = 0; i &lt; 4; ++i)
		for(j = 0; j &lt; 4; ++j)
			if(i + j != 6 &amp;&amp; document.getElementById(i + " " + j).innerHTML != i*4 + j + 1){
				q = false;
				break;
		}
	if(q) alert("Victory!");
	}
}

При загрузке страницы вызываем функцию newGame, в которой мы перемешиваем элементы массива arr, добавляем на страницу игровое поле, записываем в переменную box — div, в который мы, в последствии, запихнём игровое поле.

1
2
3
4
5
window.onload = function() {
		box = document.getElementById("box");
			newGame();				
			document.getElementById("reset").onclick = newGame;
}

Осталось только всё это оформить по вкусу и готово!

Демо | Исходники

  • Кеша

    box = document.getElementById(«box»);

    И даже без var, глобальную создаём? Продвигаем best practices?

    • Кеша, посмотрите исходники. Переменная box создаётся в первой строчке при помощи var. В функции newGame() происходит её инициализация.

  • бьём его по порядку числами от одного до пятнадцати, а значение последнего элемента установим равным пустой строке.

    Зачем лепить массив массивов чисел с одним элементом строковым? Нелогично как-то.

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

    И чем достаточно много отличается от одного перемешивания?

    • Зачем лепить массив массивов чисел с одним элементом строковым?

      Элементы этого массива потом присваиваются свойству innerHTML каждой ячейки таблицы. Если пустую строку заменить нулём, то придётся проверять что присваивается этому свойтву и, если ноль, то присваивать пустую строку. Это неудобно.

      И чем достаточно много отличается от одного перемешивания?
      При одном «перемешивании» пустая ячейка меняется местами со случайно выбранной соседней ячейкой. Если «перемешивание» будет одно, то для выигрыша будет достоточно одного клика.

  • Алик

    Сам написал пятнашки, игра, конечно, выглядит не так красиво как у вас 🙂 Возник один вопрос, как привязать клавиши? Допустим, что бы играть с помощью клавиш up, left, right, down (стрелочки). Прошу помочь, заранее огромное спасибо!

    • Как-то так, Алик:

      var LEFT = 37, UP = 38, RIGHT = 39, DOWN = 40;
       
      window.onload = function () {
        document.body.onkeydown = function (e) {
          var e = e || window.event,
            keyCode = e.keyCode;
       
          switch (keyCode) {
            case LEFT :
              // обрабатываем нажатие клавиши "влево"
              break;
            case UP :
              // обрабатываем нажатие клавиши "вверх"
              break;
            case RIGHT :
              // обрабатываем нажатие клавиши "вправо"
              break;
            case DOWN :
              // обрабатываем нажатие клавиши "вниз"
              break;
            default :
               // если нажато что-то другое завершаем выполнение ф-и
              return false;
          }
       
          return false;    
        }
      }