Я полагаю, что каждый из вас когда-то играл в классическую игру "Сапёр". Она имеет простые правила, но крайне увлекательна. Вы когда-нибудь думали о том, чтобы создать её самостоятельно? Сегодня мы создадим веб-версию игры "Сапёр". Давайте сначала взглянем на скриншот интерфейса.
👀 Предпросмотр
🎯 Задачи
В этом проекте вы научитесь:
Как проектировать алгоритм игры для игры "Сапёр"
Как создавать файловую структуру для проекта
Как реализовать макет страницы с использованием HTML и CSS
Как рисовать сетку с использованием JavaScript
Как добавлять события нажатия на клетки для обработки игрового процесса
Как реализовать функции управления игрой, такие как начало и конец игры
🏆 Достижения
После завершения этого проекта вы сможете:
Проектировать и реализовывать алгоритмы игр
Создавать файловую структуру для веб-проекта
Использовать HTML и CSS для создания макетов страниц
Использовать JavaScript для рисования сеток и обработки событий
Реализовывать функции управления игрой
Skills Graph
%%%%{init: {'theme':'neutral'}}%%%%
flowchart RL
javascript(("JavaScript")) -.-> javascript/DOMManipulationGroup(["DOM Manipulation"])
javascript(("JavaScript")) -.-> javascript/BasicConceptsGroup(["Basic Concepts"])
javascript/BasicConceptsGroup -.-> javascript/cond_stmts("Conditional Statements")
javascript/BasicConceptsGroup -.-> javascript/loops("Loops")
javascript/BasicConceptsGroup -.-> javascript/functions("Functions")
javascript/BasicConceptsGroup -.-> javascript/array_methods("Array Methods")
javascript/BasicConceptsGroup -.-> javascript/obj_manip("Object Manipulation")
javascript/DOMManipulationGroup -.-> javascript/dom_select("DOM Selection")
javascript/DOMManipulationGroup -.-> javascript/dom_manip("DOM Manipulation")
javascript/DOMManipulationGroup -.-> javascript/event_handle("Event Handling")
javascript/DOMManipulationGroup -.-> javascript/dom_traverse("DOM Traversal")
subgraph Lab Skills
javascript/cond_stmts -.-> lab-445713{{"Создание игры #quot;Сапёр#quot; с использованием JavaScript"}}
javascript/loops -.-> lab-445713{{"Создание игры #quot;Сапёр#quot; с использованием JavaScript"}}
javascript/functions -.-> lab-445713{{"Создание игры #quot;Сапёр#quot; с использованием JavaScript"}}
javascript/array_methods -.-> lab-445713{{"Создание игры #quot;Сапёр#quot; с использованием JavaScript"}}
javascript/obj_manip -.-> lab-445713{{"Создание игры #quot;Сапёр#quot; с использованием JavaScript"}}
javascript/dom_select -.-> lab-445713{{"Создание игры #quot;Сапёр#quot; с использованием JavaScript"}}
javascript/dom_manip -.-> lab-445713{{"Создание игры #quot;Сапёр#quot; с использованием JavaScript"}}
javascript/event_handle -.-> lab-445713{{"Создание игры #quot;Сапёр#quot; с использованием JavaScript"}}
javascript/dom_traverse -.-> lab-445713{{"Создание игры #quot;Сапёр#quot; с использованием JavaScript"}}
end
Подготовка к разработке
Прежде чем начать разработку, давайте сначала спроектируем алгоритм игры.
Правила игры "Сапёр" просты:
На игровом поле есть несколько квадратов, каждый квадрат содержит число (пустое означает, что число равно 0) или бомбу. Число в квадрате обозначает количество бомб в соседних квадратах. Задача игрока - найти квадраты с числами за как можно меньшее время.
За исключением квадратов на краях, каждый квадрат имеет 8 соседних квадратов: вверх, вниз, влево, вправо и 4 диагональных квадрата. Поэтому диапазон чисел от 0 до 8.
Итак, наш алгоритм следующий:
В зависимости от выбранного пользователем уровня сложности (есть три уровня: начинающий, средний и продвинутый, при этом количество бомб и квадратов увеличивается с повышением уровня), случайным образом генерируется определенное количество бомб и расставляется их случайным образом по квадратам. Затем просматриваются квадраты, вычисляется число в каждом квадрате и помечается на квадрате. Когда игрок щелкает левой кнопкой мыши по квадрату, содержимое квадрата отображается (если в квадрате есть бомба, вызов не удался и игра заканчивается), а когда игрок щелкает правой кнопкой мыши по квадрату, квадрат помечается как бомба. Победа достигается только в том случае, если все бомбы правильно помечены и все квадраты без бомб открыты, и игра заканчивается.
Полезный совет: Поскольку диапазон чисел в квадратах от 0 до 8, мы можем рассмотреть, что числа в квадратах, где находятся бомбы, помечаются как 9, чтобы облегчить вычисления.
Структура файлов проекта
Сначала нам нужно создать следующую структуру файлов в пути ~/project:
mine.png и flag.png в директории проекта - это изображения для мины и флага соответственно.
Макет страницы
Сначала нам нужна панель для отображения игровой информации, включая количество оставшихся мин, пройденное время, уровень сложности и т.д. Поскольку количество квадратов не фиксировано, мы не будем сейчас рисовать квадраты, а вместо этого нарисуем их в коде JS.
Создайте файл index.html и добавьте следующий код:
После завершения предыдущих шагов нам нужно нарисовать сетку. Чтобы сделать код более понятным, мы разделяем часть реализации игры и часть вызова. Часть реализации игры размещается в jms.js, который находится в том же каталоге, что и index.html, а часть вызова игры размещается в index.js, также в том же каталоге.
Для отрисовки сетки нам нужно передать некоторые параметры, такие как id таблицы, в которую будет нарисована сетка, и количество ячеек (представляется количеством строк и столбцов). Кроме того, другие данные, связанные с игрой, нужно инициализировать.
Часть jms.js
Добавьте следующий код в jms.js и сохраните его:
(function () {
// Инициализируем объект "Сапёр" и инициализируем данные
var JMS = function (
id,
rowCount,
colCount,
minLandMineCount,
maxLandMineCount
) {
if (!(this instanceof JMS))
return new JMS(
id,
rowCount,
colCount,
minLandMineCount,
maxLandMineCount
);
this.doc = document;
this.table = this.doc.getElementById(id); // Таблица для рисования сетки
this.cells = this.table.getElementsByTagName("td"); // Ячейки
this.rowCount = rowCount || 10; // Количество строк в сетке
this.colCount = colCount || 10; // Количество столбцов в сетке
this.landMineCount = 0; // Количество мин
this.markLandMineCount = 0; // Количество отмеченных мин
this.minLandMineCount = minLandMineCount || 10; // Минимальное количество мин
this.maxLandMineCount = maxLandMineCount || 20; // Максимальное количество мин
this.arrs = []; // Массив, соответствующий ячейкам
this.beginTime = null; // Время начала игры
this.endTime = null; // Время окончания игры
this.currentSetpCount = 0; // Количество сделанных шагов
this.endCallBack = null; // Коллбэк-функция при окончании игры
this.landMineCallBack = null; // Коллбэк-функция для обновления оставшегося количества мин при пометке мины
this.doc.oncontextmenu = function () {
// Отключаем контекстное меню
return false;
};
this.drawMap();
};
// Создаем ячейки в прототипе JMS
JMS.prototype = {
// Рисуем сетку
drawMap: function () {
var tds = [];
// Для совместимости с браузерами
if (
window.ActiveXObject &&
parseInt(navigator.userAgent.match(/msie ([\d.]+)/i)[1]) < 8
) {
// Создаем новый CSS-стильный файл
var css = "#JMS_main table td{background-color:#888;}",
// Получаем тег head
head = this.doc.getElementsByTagName("head")[0],
// Создаем тег style
style = this.doc.createElement("style");
style.type = "text/css";
if (style.styleSheet) {
// Назначаем CSS-стили к тегу style
style.styleSheet.cssText = css;
} else {
// Создаем узел в теге style
style.appendChild(this.doc.createTextNode(css));
}
// Добавляем тег style в качестве дочернего тега к тегу head
head.appendChild(style);
}
// Цикл для создания таблицы
for (var i = 0; i < this.rowCount; i++) {
tds.push("<tr>");
for (var j = 0; j < this.colCount; j++) {
tds.push("<td id='m_" + i + "_" + j + "'></td>");
}
tds.push("</tr>");
}
this.setTableInnerHTML(this.table, tds.join(""));
},
// Добавляем HTML в таблицу
setTableInnerHTML: function (table, html) {
if (navigator && navigator.userAgent.match(/msie/i)) {
// Создаем div внутри владельца документа таблицы
var temp = table.ownerDocument.createElement("div");
// Создаем содержимое tbody таблицы
temp.innerHTML = "<table><tbody>" + html + "</tbody></table>";
if (table.tBodies.length == 0) {
var tbody = document.createElement("tbody");
table.appendChild(tbody);
}
table.replaceChild(temp.firstChild.firstChild, table.tBodies[0]);
} else {
table.innerHTML = html;
}
}
};
window.JMS = JMS;
})();
Вышеприведенный код содержит некоторый код для совместимости с браузерами IE, который можно игнорировать.
Часть index.js
В коде вызова в index.js нам нужно привязать событие кнопок выбора уровня сложности, а затем вызвать JMS, который мы определили выше, чтобы начать рисовать сетку.
Добавьте следующий код в index.js и сохраните его:
var jms = null,
timeHandle = null;
window.onload = function () {
var radios = document.getElementsByName("level");
for (var i = 0, j = radios.length; i < j; i++) {
radios[i].onclick = function () {
if (jms != null)
if (jms.landMineCount > 0)
if (!confirm("Вы уверены, что хотите закончить текущую игру?"))
return false;
var value = this.value;
init(value, value, (value * value) / 5 - value, (value * value) / 5);
document.getElementById("JMS_main").style.width =
value * 40 + 180 + 60 + "px";
};
}
init(10, 10);
};
function init(rowCount, colCount, minLandMineCount, maxLandMineCount) {
var doc = document,
landMineCountElement = doc.getElementById("landMineCount"),
timeShow = doc.getElementById("costTime"),
beginButton = doc.getElementById("begin");
if (jms != null) {
clearInterval(timeHandle);
timeShow.innerHTML = 0;
landMineCountElement.innerHTML = 0;
}
jms = JMS("landmine", rowCount, colCount, minLandMineCount, maxLandMineCount);
}
Затем откройте index.html в браузере, и сетка должна быть отображена. Эффект выглядит так:
Нажмите на выбор уровня сложности справа, чтобы увидеть изменение количества ячеек.
Теперь необходимо добавить события клика к ячейкам. При нажатии левой кнопки мыши отображается число в ячейке (если это мина, игра завершается). При нажатии правой кнопки мыши она помечается как мина.
此外,当单元格首次被点击时(通常涉及运气),如果其周围有空白区域,应直接展开。
Добавьте следующий код в JMS.prototype в jms.js:
// Получить элемент
$: function (id) {
return this.doc.getElementById(id);
},
// Привязать события клика (левый и правый) к каждой ячейке
bindCells: function () {
var self = this;
for (var i = 0; i < this.rowCount; i++) {
for (var j = 0; j < this.colCount; j++) {
(function (row, col) {
self.$("m_" + i + "_" + j).onmousedown = function (e) {
e = e || window.event;
var mouseNum = e.button;
var className = this.className;
if (mouseNum == 2) {
if (className == "flag") {
this.className = "";
self.markLandMineCount--;
} else {
this.className = "flag";
self.markLandMineCount++;
}
if (self.landMineCallBack) {
self.landMineCallBack(self.landMineCount - self.markLandMineCount);
}
} else if (className!= "flag") {
self.openBlock.call(self, this, row, col);
}
};
})(i,j);
}
}
},
// Раскрыть область без мин
showNoLandMine: function (x, y) {
for (var i = x - 1; i < x + 2; i++)
for (var j = y - 1; j < y + 2; j++) {
if (!(i == x && j == y)) {
var ele = this.$("m_" + i + "_" + j);
if (ele && ele.className == "") {
this.openBlock.call(this, ele, i, j);
}
}
}
},
// Отобразить
openBlock: function (obj, x, y) {
if (this.arrs[x][y]!= 9) {
this.currentSetpCount++;
if (this.arrs[x][y]!= 0) {
obj.innerHTML = this.arrs[x][y];
}
obj.className = "clicked-cell";
if (this.currentSetpCount + this.landMineCount == this.rowCount * this.colCount) {
this.success();
}
obj.onmousedown = null;
if (this.arrs[x][y] == 0) {
this.showNoLandMine.call(this, x, y);
}
} else {
this.failed();
}
},
// Отобразить мины
showLandMine: function () {
for (var i = 0; i < this.rowCount; i++) {
for (var j = 0; j < this.colCount; j++) {
if (this.arrs[i][j] == 9) {
this.$("m_" + i + "_" + j).className = "landMine";
}
}
}
},
// Показать информацию о всех ячейках
showAll: function () {
for (var i = 0; i < this.rowCount; i++) {
for (var j = 0; j < this.colCount; j++) {
if (this.arrs[i][j] == 9) {
this.$("m_" + i + "_" + j).className = "landMine";
} else {
var ele=this.$("m_" + i + "_" + j);
if (this.arrs[i][j]!= 0)
ele.innerHTML = this.arrs[i][j];
ele.className = "normal";
}
}
}
},
// Удалить отображенную информацию о ячейках
hideAll: function () {
for (var i = 0; i < this.rowCount; i++) {
for (var j = 0; j < this.colCount; j++) {
var tdCell = this.$("m_" + i + "_" + j);
tdCell.className = "";
tdCell.innerHTML = "";
}
}
},
// Удалить события, привязанные к ячейкам
disableAll: function () {
for (var i = 0; i < this.rowCount; i++) {
for (var j = 0; j < this.colCount; j++) {
var tdCell = this.$("m_" + i + "_" + j);
tdCell.onmousedown = null;
}
}
},
До сих пор основная часть игры завершена. Следующим шагом является добавление функций управления игрой, чтобы игра работала гладко. Основные шаги следующие:
Добавить слушатель события клика на кнопку "Старт" для сброса параметров игры.
Начинать отсчитывать время при запуске игры.
Остановить отсчет времени и показать сообщение при окончании игры.
Секция jms.js
Добавьте функцию входа в игру и запуска в JMS.prototype в jms.js:
// Запуск игры
begin: function() {
this.currentSetpCount = 0; // Сбросить счетчик шагов до нуля
this.markLandMineCount = 0;
this.beginTime = new Date(); // Время начала игры
this.hideAll();
this.bindCells();
},
// Конец игры
end: function() {
this.endTime = new Date(); // Время окончания игры
if (this.endCallBack) { // Вызвать коллбэк-функцию, если она существует
this.endCallBack();
}
},
// Победа в игре
success: function() {
this.end();
this.showAll();
this.disableAll();
alert("Поздравляем!");
},
// Неудача в игре
failed: function() {
this.end();
this.showAll();
this.disableAll();
alert("ИГРА ОКОНЧЕНА!");
},
// Точка входа
play: function() {
this.init();
this.landMine();
this.calculateNoLandMineCount();
},
Секция index.js
В index.js добавьте слушатель события на кнопку "Старт", чтобы запустить игру, и отобразить время игры и оставшееся количество мин (после jms = JMS("landmine", rowCount, colCount, minLandMineCount, maxLandMineCount); в функции init в index.js):
jms.endCallBack = function () {
clearInterval(timeHandle);
};
jms.landMineCallBack = function (count) {
landMineCountElement.innerHTML = count;
};
// Привязать событие к кнопке "Начать игру"
beginButton.onclick = function () {
jms.play(); // Инициализировать игру
// Отобразить количество мин
landMineCountElement.innerHTML = jms.landMineCount;
// Запустить игру
jms.begin();
// Обновить прошедшее время
timeHandle = setInterval(function () {
timeShow.innerHTML = parseInt((new Date() - jms.beginTime) / 1000);
}, 1000);
};
В этом эксперименте в основном используется JavaScript для реализации веб-версии классической игры "Сапёр". Предполагается, что в результате этого эксперимента пользователи смогут повысить свои знания и навыки в использовании JavaScript. Кроме того, пользователи также могут научиться использовать язык JavaScript для абстрагирования и инкапсуляции объектов в игре.