Jump to content

Leaderboard

Popular Content

Showing content with the highest reputation on 09/12/2017 in all areas

  1. Обрабатываем клик по клетке Итак, у нас уже почти все готово для полноценной игры. Осталось только обработать клик по клетке на доске. Давайте создадим метод который назовем showInfo, внутри данного метода мы будем проверять проиграл ли уже пользователь, что отобразить на клетке и т.п. Метод будет принимать в качестве параметра элемент на котором кликнули: var MyMineSweeper = { init: function(o) { this.W = o ? o.W : 7; this.H = o ? o.H : 7; this.bombs = o ? o.bombs : Math.floor(this.W * this.H / 4); this.placedBombs = this.bombs; this.generateGUI(); }, generateGUI: function() { if (!this.game) { this.game = this.writeMainContainer(); this.gameCont = document.createElement('table'); this.gameCont.className = 'game-cont'; this.gameCont.insertRow(0); this.gameCont.insertRow(1); this.gameMenu = this.gameCont.rows[0].insertCell(0); this.gameMenu.rowSpan = 2; this.gameMenu.className = 'game-menu'; this.gameMenu.appendChild(document.createTextNode('Ширина поля:')); this.gameMenu.appendChild(document.createElement('br')); this.WInput = document.createElement('input'); this.WInput.type = 'text'; this.gameMenu.appendChild(this.WInput); this.gameMenu.appendChild(document.createTextNode('Высота поля:')); this.gameMenu.appendChild(document.createElement('br')); this.HInput = document.createElement('input'); this.HInput.type = 'text'; this.gameMenu.appendChild(this.HInput); this.gameMenu.appendChild(document.createTextNode('Кол-во мин:')); this.gameMenu.appendChild(document.createElement('br')); this.BInput = document.createElement('input'); this.BInput.type = 'text'; this.Init = document.createElement('input'); this.Init.type = 'button'; this.Init.value = 'Старт'; this.Init.className = 'game-start-button'; this.gameMenu.appendChild(this.BInput); this.gameMenu.appendChild(this.Init); this.gameStats = this.gameCont.rows[0].insertCell(1); this.gameField = this.gameCont.rows[1].insertCell(0); this.gameField.className = 'game-field'; this.game.appendChild(this.gameCont); } if (this.board) { this.board.parentNode.removeChild(this.board); this.board = null; } this.board = this.generateField(); this.board.cellSpacing = 0; this.board.className = 'game-board'; this.gameField.appendChild(this.board); this.gameStats.innerHTML = 'Новая игра: поле ' + this.W + 'x' + this.H + ', ' + this.bombs + 'мин'; this.setupEvents(); }, setupEvents: function() { var self = this; var buttonClick = function() { self.init({W: self.WInput.value, H: self.HInput.value, bombs: self.BInput.value}); } Event.add(this.Init, 'click', buttonClick); }, generateField: function() { var self = this; var table = document.createElement('table'); for (var i = 0; i < this.H; i++) { var r = table.insertRow(i); for (var j = 0; j < this.W; j++) { var c = r.insertCell(j); c.num = 0; c.index = [i, j]; c.clickHandler = function() { self.showInfo(this); } Event.add(c, 'click', c.clickHandler); } } do { var hNum = this.rand(0, this.H - 1); var wNum = this.rand(0, this.W - 1); if (!table.rows[hNum].cells[wNum].bomb) { table.rows[hNum].cells[wNum].num = null; table.rows[hNum].cells[wNum].bomb = true; this.placedBombs--; } } while (this.placedBombs > 0); for (var i = 0, len = table.rows.length; i < len; i++) { for (var j = 0, len2 = table.rows[i].cells.length; j < len2; j++) { if (table.rows[i].cells[j].bomb) { this.placeNumbers(table, j, i); } } } return table; }, placeNumbers: function(t, x, y) { if (x > 0) { t.rows[y].cells[x - 1].num++; } if (x < this.W - 1) { t.rows[y].cells[x + 1].num++; } if (x > 0 && y > 0) { t.rows[y - 1].cells[x - 1].num++; } if (y > 0) { t.rows[y - 1].cells[x].num++; } if (y > 0 && x < this.W - 1) { t.rows[y - 1].cells[x + 1].num++; } if (x > 0 && y < this.H - 1) { t.rows[y + 1].cells[x - 1].num++; } if (y < this.H - 1) { t.rows[y + 1].cells[x].num++; } if (x < this.W - 1 && y < this.H - 1) { t.rows[y + 1].cells[x + 1].num++; } }, showInfo: function(elem) { if (!elem.bomb) { this.openCell(elem); } else { this.openCell(elem); this.gameOver(); } }, openCell: function(elem) { if (!elem.bomb) { if (elem.num > 0) { elem.innerHTML = '<b>' + elem.num + '</b>'; } else { elem.innerHTML = ' '; } switch (elem.num) { case 1: elem.style.color = 'blue'; break; case 2: elem.style.color = 'green'; break; case 3: elem.style.color = 'red'; break; case 4: elem.style.color = 'dakrblue'; break; case 5: elem.style.color = 'pink'; break; case 6: elem.style.color = 'navy'; break; case 7: elem.style.color = 'brown'; break; case 8: elem.style.color = 'grey'; break; default: elem.style.color = 'black'; } } else { elem.innerHTML = '<b>M</b>'; } elem.style.background = '#d8e0ec'; }, writeMainContainer: function() { var html = '<div id="game" class="game"></div>'; document.writeln(html); return document.getElementById('game'); }, rand: function(min, max) { min = parseInt(min); max = parseInt(max); return Math.floor(Math.random() * (max - min + 1)) + min; } } Обрабатываем Game Over Теперь подумаем, что должно происходить когда юзер нарвался на мину: 1) нам нужно показать все мины на доске, 2) нам нужно снять все слушатели событий с клеток, чтобы пользователь не мог никуда больше кликнуть пока не начнет новую игру, 3) нам нужно уведомить пользователя о том что он проиграл. Реализуем все в коде: gameOver: function() { // пробегаемся циклом по доске for (var i = 0, len1 = this.board.rows.length; i < len1; i++) { // цикл по вертикали for (var j = 0, len2 = this.board.rows[i].cells.length; j < len2; j++) { // цикл по горизонтали if (this.board.rows[i].cells[j].bomb) { // если на клетке стоит мина // показываем мину this.openCell(this.board.rows[i].cells[j]); } // снимаем обработчик события "click" с каждой клетки Event.remove(this.board.rows[i].cells[j], 'click', this.board.rows[i].cells[j].clickHandler); } } // красим ячейку статистики в красный цвет this.gameStats.style.background = 'red'; // и говорим юзеру, что он подорвался this.gameStats.innerHTML = '<b>БАБАХ!!! GAME OVER!</b>'; } У нас все готово для полноценной игры. Ура! Однако радоваться рано. Если посмотреть на настоящего сапёра, то можно увидет одну особенность - если мы кликнем на клетку рядом с которой нет мин (т.е. номер на ней 0), то откроются все соседние пустые клетки, т.е. все клетки, граничащие с открытой, рядом к которыми тоже нет мин. То есть происходит так называемый каскад. Пожалуй это будет самая сложная функция в нашем сапёре. Итак... Реализуем каскад Вы себе даже не представляете как долго я бился на этим методом. Что я только не делал, где только не гуглил И вот, наконец я допер, что в нелегком деле каскадирования нам поможет рекурсия! Что такое рекурсия? Это когда функция вызывает сама себя. Что обязательно должно быть в рекурсивной функции? Первым делом в рекурсивной функции должен быть продуман выход из нее, чтобы не было бесконечного зацикливания. В принципе это все, что нужно знать о рекурсии, но без примера все равно сложно это понять, поэтому я вам приведу пример простейшей рекурсии: // функция принимает в качестве параметра начальное число и конечное число function myRecursion(num, maxNum) { /* * первым делом продумываем * выход из бесконечной рекурсии */ if (num > maxNum) { // если начальное число больше конечного // выходим из функции return; } // пишем в документ текущую цифру document.write(num + ', '); // увеличиваем число на 1 num++; // вызываем саму себя (рекурсивный вызов) myRecursion(num, maxNum); } myRecursion(0, 5); // выведет 0, 1, 2, 3, 4, 5 Ну вот, теперь вы должны представлять что такое рекурсия и мы можем начать писать наш метод-каскад, назовем его roll, метод будет принимать в качестве параметров координаты клетки на доске: roll: function(x, y) { /* * первым делом продумываем * выход из бесконечной рекурсии */ // если x или y выходят за границы доски if (x < 0 || y < 0 || x >= this.W || y >= this.H) { // выходим return; } // открываем клетку this.openCell(this.board.rows[y].cells[x]); // если рядом с клеткой есть бомба (т.е. номер в ней больше 0) if (this.board.rows[y].cells[x].num > 0) { // вставляем в клетку цифру this.board.rows[y].cells[x].innerHTML = '<b>' + this.board.rows[y].cells[x].num + '</b>'; // выходим return; } // если мы уже эту клетку проверяли if (this.board.rows[y].cells[x].check) { // выходим return; } // отмечаем, что эту ячейку мы уже проверяли this.board.rows[y].cells[x].check = true; // далее пошла рекурсия this.roll(x + 1, y); // делаем шаг вправо this.roll(x - 1, y); // делаем шаг влево this.roll(x, y + 1); // делаем шаг вниз this.roll(x, y - 1); // делаем шаг вверх this.roll(x - 1, y - 1); // делаем шаг вверх влево this.roll(x + 1, y - 1); // делаем шаг вверх вправо this.roll(x - 1, y + 1); // делаем шаг вниз влево this.roll(x + 1, y + 1); // делаем шаг вниз вправо } Вызывать наш каскад будем в методе showInfo, для этого нам его надо будет немного изменить, добавив дополнительные проверки: showInfo: function(elem) { if (!elem.bomb) { // если в клетке нет мины if (elem.num > 0) { // если рядом есть мины (номер больше 0) // открываем клетку this.openCell(elem); } else { // если рядом нет ни одной мины /* * запускаем каскад * тут нам и пригодилось дополнительное * свойство хранимое в клетке (index), * в котором храняться в виде массива * координаты клетки на доске */ this.roll(elem.index[1], elem.index[0]); } } else { // если в клетке мина // открываем клетку this.openCell(elem); // завершаем игру this.gameOver(); } } Вот и все. Теперь у нас готов полноценный сапер. Он конечно не совсем похож на того сапера который присутствует в винде. В нем нельзя отмечать места с предполагаемой миной и нет счетчика времени, зато в нем можно варьировать уровень сложности и размер доски по своему усмотрению, а все недостатки легко доделать. Главное вы теперь знаете устройство такой замечательной логической игры как сапёр! Ах да, вот полный код нашего сапера: var MyMineSweeper = { init: function(o) { this.W = o ? o.W : 7; this.H = o ? o.H : 7; this.bombs = o ? o.bombs : Math.floor(this.W * this.H / 4); this.placedBombs = this.bombs; this.generateGUI(); }, generateGUI: function() { if (!this.game) { this.game = this.writeMainContainer(); this.gameCont = document.createElement('table'); this.gameCont.className = 'game-cont'; this.gameCont.insertRow(0); this.gameCont.insertRow(1); this.gameMenu = this.gameCont.rows[0].insertCell(0); this.gameMenu.rowSpan = 2; this.gameMenu.className = 'game-menu'; this.gameMenu.appendChild(document.createTextNode('Ширина поля:')); this.gameMenu.appendChild(document.createElement('br')); this.WInput = document.createElement('input'); this.WInput.type = 'text'; this.gameMenu.appendChild(this.WInput); this.gameMenu.appendChild(document.createTextNode('Высота поля:')); this.gameMenu.appendChild(document.createElement('br')); this.HInput = document.createElement('input'); this.HInput.type = 'text'; this.gameMenu.appendChild(this.HInput); this.gameMenu.appendChild(document.createTextNode('Кол-во мин:')); this.gameMenu.appendChild(document.createElement('br')); this.BInput = document.createElement('input'); this.BInput.type = 'text'; this.Init = document.createElement('input'); this.Init.type = 'button'; this.Init.value = 'Старт'; this.Init.className = 'game-start-button'; this.gameMenu.appendChild(this.BInput); this.gameMenu.appendChild(this.Init); this.gameStats = this.gameCont.rows[0].insertCell(1); this.gameField = this.gameCont.rows[1].insertCell(0); this.gameField.className = 'game-field'; this.game.appendChild(this.gameCont); } if (this.board) { this.board.parentNode.removeChild(this.board); this.board = null; } this.board = this.generateField(); this.board.cellSpacing = 0; this.board.className = 'game-board'; this.gameField.appendChild(this.board); this.gameStats.innerHTML = 'Новая игра: поле ' + this.W + 'x' + this.H + ', ' + this.bombs + 'мин'; this.WInput.value = this.W; this.HInput.value = this.H; this.BInput.value = this.bombs; this.gameStats.style.background = '#bfd3ed'; this.setupEvents(); }, setupEvents: function() { var self = this; var buttonClick = function() { self.init({W: self.WInput.value, H: self.HInput.value, bombs: self.BInput.value}); } Event.add(this.Init, 'click', buttonClick); }, generateField: function() { var self = this; var table = document.createElement('table'); for (var i = 0; i < this.H; i++) { var r = table.insertRow(i); for (var j = 0; j < this.W; j++) { var c = r.insertCell(j); c.num = 0; c.index = [i, j]; c.clickHandler = function() { self.showInfo(this); } Event.add(c, 'click', c.clickHandler); } } do { var hNum = this.rand(0, this.H - 1); var wNum = this.rand(0, this.W - 1); if (!table.rows[hNum].cells[wNum].bomb) { table.rows[hNum].cells[wNum].num = null; table.rows[hNum].cells[wNum].bomb = true; this.placedBombs--; } } while (this.placedBombs > 0); for (var i = 0, len = table.rows.length; i < len; i++) { for (var j = 0, len2 = table.rows[i].cells.length; j < len2; j++) { if (table.rows[i].cells[j].bomb) { this.placeNumbers(table, j, i); } } } return table; }, placeNumbers: function(t, x, y) { if (x > 0) { t.rows[y].cells[x - 1].num++; } if (x < this.W - 1) { t.rows[y].cells[x + 1].num++; } if (x > 0 && y > 0) { t.rows[y - 1].cells[x - 1].num++; } if (y > 0) { t.rows[y - 1].cells[x].num++; } if (y > 0 && x < this.W - 1) { t.rows[y - 1].cells[x + 1].num++; } if (x > 0 && y < this.H - 1) { t.rows[y + 1].cells[x - 1].num++; } if (y < this.H - 1) { t.rows[y + 1].cells[x].num++; } if (x < this.W - 1 && y < this.H - 1) { t.rows[y + 1].cells[x + 1].num++; } }, showInfo: function(elem) { if (!elem.bomb) { if (elem.num > 0) { this.openCell(elem); } else { this.roll(elem.index[1], elem.index[0]); } } else { this.openCell(elem); this.gameOver(); } }, openCell: function(elem) { if (!elem.bomb) { if (elem.num > 0) { elem.innerHTML = '<b>' + elem.num + '</b>'; } else { elem.innerHTML = ' '; } switch (elem.num) { case 1: elem.style.color = 'blue'; break; case 2: elem.style.color = 'green'; break; case 3: elem.style.color = 'red'; break; case 4: elem.style.color = 'dakrblue'; break; case 5: elem.style.color = 'pink'; break; case 6: elem.style.color = 'navy'; break; case 7: elem.style.color = 'brown'; break; case 8: elem.style.color = 'grey'; break; default: elem.style.color = 'black'; } } else { elem.innerHTML = '<b>M</b>'; } elem.style.background = '#d8e0ec'; }, gameOver: function() { for (var i = 0, len1 = this.board.rows.length; i < len1; i++) { for (var j = 0, len2 = this.board.rows[i].cells.length; j < len2; j++) { if (this.board.rows[i].cells[j].bomb) { this.openCell(this.board.rows[i].cells[j]); } Event.remove(this.board.rows[i].cells[j], 'click', this.board.rows[i].cells[j].clickHandler); } } this.gameStats.style.background = 'red'; this.gameStats.innerHTML = '<b>БАБАХ!!! GAME OVER!</b>'; }, roll: function(x, y) { if (x < 0 || y < 0 || x >= this.W || y >= this.H) { return; } this.openCell(this.board.rows[y].cells[x]); if (this.board.rows[y].cells[x].num > 0) { this.board.rows[y].cells[x].innerHTML = '<b>' + this.board.rows[y].cells[x].num + '</b>'; return; } if (this.board.rows[y].cells[x].check) { return; } this.board.rows[y].cells[x].check = true; this.roll(x + 1, y); this.roll(x - 1, y); this.roll(x, y + 1); this.roll(x, y - 1); this.roll(x - 1, y - 1); this.roll(x + 1, y - 1); this.roll(x - 1, y + 1); this.roll(x + 1, y + 1); }, writeMainContainer: function() { var html = '<div id="game" class="game"></div>'; document.writeln(html); return document.getElementById('game'); }, rand: function(min, max) { min = parseInt(min); max = parseInt(max); return Math.floor(Math.random() * (max - min + 1)) + min; } } Всем спасибо за внимание, надеюсь урок был вам полезен. Предложения, замечания и т.п. пишите в личку.
    1 point
This leaderboard is set to Kiev/GMT+02:00
×
×
  • Create New...

Important Information

We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue. See more about our Guidelines and Privacy Policy