Creo que todos han jugado alguna vez al clásico juego del Minesweeper. Tiene reglas simples pero es altamente adictivo. ¿Alguna vez has pensado en desarrollarlo tú mismo? Hoy, crearemos una versión web del Minesweeper. Primero, echemos un vistazo a una captura de pantalla de la interfaz.
👀 Vista previa
🎯 Tareas
En este proyecto, aprenderás:
Cómo diseñar el algoritmo del juego para el Minesweeper
Cómo crear la estructura de archivos para el proyecto
Cómo implementar el diseño de la página utilizando HTML y CSS
Cómo dibujar la cuadrícula utilizando JavaScript
Cómo agregar eventos de clic a las celdas para manejar la jugabilidad
Cómo implementar funciones de control del juego como el inicio y el final del juego
🏆 Logros
Después de completar este proyecto, podrás:
Diseñar e implementar algoritmos de juego
Crear la estructura de archivos para un proyecto web
Utilizar HTML y CSS para crear diseños de página
Utilizar JavaScript para dibujar cuadrículas y manejar eventos
Implementar funciones de control del juego
Preparación para el desarrollo
Antes de comenzar el desarrollo, primero diseñemos el algoritmo del juego.
Las reglas del juego del Minesweeper son simples:
Hay varios cuadrados en el tablero de juego. Cada cuadrado contiene un número (un espacio en blanco indica que el número es 0) o una bomba. El número en el cuadrado representa la cantidad de bombas en los cuadrados adyacentes. La tarea del jugador es encontrar los cuadrados con números en el menor tiempo posible.
Excepto los cuadrados en los bordes, cada cuadrado tiene 8 cuadrados vecinos: arriba, abajo, izquierda, derecha y 4 cuadrados diagonales. Por lo tanto, el rango de números es de 0 a 8.
Entonces, nuestro algoritmo es el siguiente:
Basado en el nivel de dificultad seleccionado por el usuario (hay tres niveles: principiante, intermedio y avanzado, y a medida que aumenta el nivel, hay más bombas y cuadrados), generar aleatoriamente una cierta cantidad de bombas y colocarlas aleatoriamente en los cuadrados. Luego, recorrer los cuadrados, calcular el número en cada cuadrado y marcarlo en el cuadrado. Cuando el jugador hace clic izquierdo en un cuadrado, se muestra el contenido del cuadrado (si el cuadrado contiene una bomba, el desafío falla y el juego termina), y cuando el jugador hace clic derecho en un cuadrado, el cuadrado se marca como una bomba. El desafío es exitoso solo cuando todas las bombas están correctamente marcadas y todos los cuadrados sin bombas están abiertos, y el juego termina.
Consejo útil: Dado que el rango de números en los cuadrados es de 0 a 8, podemos considerar marcar los números en los cuadrados donde hay bombas como 9 para facilitar el cálculo.
Primero, necesitamos un panel para mostrar la información del juego, incluyendo el número de minas restantes, el tiempo transcurrido, el nivel de dificultad, etc. Dado que el número de cuadrados no es fijo, no dibujaremos los cuadrados por ahora y los dibujaremos en el código JS en su lugar.
Crea un archivo index.html y agrega el siguiente código:
A continuación, necesitamos ajustar la posición de la información del juego en el panel y agregar algunos estilos. Agrega el siguiente código a index.css y guarda:
Después de completar los pasos anteriores, necesitamos dibujar la cuadrícula. Para que el código sea más claro, separamos la parte de implementación del juego y la parte de llamada. La parte de implementación del juego se coloca en jms.js, que está en el mismo directorio que index.html, y la parte de llamada del juego se coloca en index.js, también en el mismo directorio.
Para dibujar la cuadrícula, necesitamos pasar algunos parámetros, como el id de la tabla donde se colocará la cuadrícula y el número de celdas (representado por el número de filas y columnas). Además, se deben inicializar otros datos relacionados con el juego.
Parte de jms.js
Agrega el siguiente código a jms.js y guárdalo:
(function () {
// Inicializar el objeto Minesweeper e inicializar los datos
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); // Tabla para dibujar la cuadrícula
this.cells = this.table.getElementsByTagName("td"); // Celdas
this.rowCount = rowCount || 10; // Número de filas en la cuadrícula
this.colCount = colCount || 10; // Número de columnas en la cuadrícula
this.landMineCount = 0; // Número de minas
this.markLandMineCount = 0; // Número de minas marcadas
this.minLandMineCount = minLandMineCount || 10; // Número mínimo de minas
this.maxLandMineCount = maxLandMineCount || 20; // Número máximo de minas
this.arrs = []; // Arreglo correspondiente a las celdas
this.beginTime = null; // Tiempo de inicio del juego
this.endTime = null; // Tiempo de fin del juego
this.currentSetpCount = 0; // Número de pasos dados
this.endCallBack = null; // Función de devolución de llamada cuando el juego termina
this.landMineCallBack = null; // Función de devolución de llamada para actualizar el número restante de minas cuando se marca una mina
this.doc.oncontextmenu = function () {
// Deshabilitar el menú contextual
return false;
};
this.drawMap();
};
// Crear celdas en el prototipo de JMS
JMS.prototype = {
// Dibujar la cuadrícula
drawMap: function () {
var tds = [];
// Para la compatibilidad con navegadores
if (
window.ActiveXObject &&
parseInt(navigator.userAgent.match(/msie ([\d.]+)/i)[1]) < 8
) {
// Crear un nuevo archivo de estilo CSS
var css = "#JMS_main table td{background-color:#888;}",
// Obtener la etiqueta head
head = this.doc.getElementsByTagName("head")[0],
// Crear una etiqueta de estilo
style = this.doc.createElement("style");
style.type = "text/css";
if (style.styleSheet) {
// Asignar el estilo CSS a la etiqueta de estilo
style.styleSheet.cssText = css;
} else {
// Crear un nodo en la etiqueta de estilo
style.appendChild(this.doc.createTextNode(css));
}
// Adjuntar la etiqueta de estilo como etiqueta hija de la etiqueta head
head.appendChild(style);
}
// Bucle para crear la tabla
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(""));
},
// Agregar HTML a la tabla
setTableInnerHTML: function (table, html) {
if (navigator && navigator.userAgent.match(/msie/i)) {
// Crear un div dentro del documento propietario de la tabla
var temp = table.ownerDocument.createElement("div");
// Crear el contenido del tbody de la tabla
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;
})();
El código anterior incluye algunos códigos para hacer que sea compatible con navegadores IE, que se pueden ignorar.
Parte de index.js
En el código de llamada en index.js, necesitamos enlazar el evento de los botones de selección de dificultad y luego llamar a la JMS que definimos anteriormente para comenzar a dibujar la cuadrícula.
Agrega el siguiente código a index.js y guárdalo:
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("¿Está seguro de que desea terminar el juego actual?"))
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);
}
Luego abre index.html en el navegador y se debería mostrar la cuadrícula. El efecto es el siguiente:
Haga clic en la selección de dificultad en la derecha para ver cómo cambia el número de celdas.
Ahora, comencemos con la inicialización del juego, lo que principalmente implica tres pasos:
Inicializar todas las celdas (representadas por un arreglo en el código) a 0.
Generar un número aleatorio de minas y colocarlas aleatoriamente en el arreglo, estableciendo el valor del elemento del arreglo en 9.
Calcular los números en las otras celdas y almacenar los valores en el arreglo.
Agrega el siguiente código dentro de JMS.prototype en jms.js:
// Inicialización: establecer el valor predeterminado del arreglo en 0 y determinar el número de minas
init: function () {
for (var i = 0; i < this.rowCount; i++) {
this.arrs[i] = [];
for (var j = 0; j < this.colCount; j++) {
this.arrs[i][j] = 0;
}
}
this.landMineCount = this.selectFrom(this.minLandMineCount, this.maxLandMineCount);
this.markLandMineCount = 0;
this.beginTime = null;
this.endTime = null;
this.currentSetpCount = 0;
},
// Establecer el valor de los elementos del arreglo que contienen minas en 9
landMine: function () {
var allCount = this.rowCount * this.colCount - 1,
tempArr = {};
for (var i = 0; i < this.landMineCount; i++) {
var randomNum = this.selectFrom(0, allCount),
rowCol = this.getRowCol(randomNum);
if (randomNum in tempArr) {
i--;
continue;
}
this.arrs[rowCol.row][rowCol.col] = 9;
tempArr[randomNum] = randomNum;
}
},
// Calcular los números en las otras celdas
calculateNoLandMineCount: function () {
for (var i = 0; i < this.rowCount; i++) {
for (var j = 0; j < this.colCount; j++) {
if (this.arrs[i][j] == 9)
continue;
if (i > 0 && j > 0) {
if (this.arrs[i - 1][j - 1] == 9)
this.arrs[i][j]++;
}
if (i > 0) {
if (this.arrs[i - 1][j] == 9)
this.arrs[i][j]++;
}
if (i > 0 && j < this.colCount - 1) {
if (this.arrs[i - 1][j + 1] == 9)
this.arrs[i][j]++;
}
if (j > 0) {
if (this.arrs[i][j - 1] == 9)
this.arrs[i][j]++;
}
if (j < this.colCount - 1) {
if (this.arrs[i][j + 1] == 9)
this.arrs[i][j]++;
}
if (i < this.rowCount - 1 && j > 0) {
if (this.arrs[i + 1][j - 1] == 9)
this.arrs[i][j]++;
}
if (i < this.rowCount - 1) {
if (this.arrs[i + 1][j] == 9)
this.arrs[i][j]++;
}
if (i < this.rowCount - 1 && j < this.colCount - 1) {
if (this.arrs[i + 1][j + 1] == 9)
this.arrs[i][j]++;
}
}
}
},
// Obtener un número aleatorio
selectFrom: function (iFirstValue, iLastValue) {
var iChoices = iLastValue - iFirstValue + 1;
return Math.floor(Math.random() * iChoices + iFirstValue);
},
// Encontrar los números de fila y columna basados en el valor
getRowCol: function (val) {
return {
row: parseInt(val / this.colCount),
col: val % this.colCount
};
},
Ahora, es necesario agregar eventos de clic a las celdas. Al hacer clic con el botón izquierdo, mostrar el número en la celda (si es una mina, el juego terminará). Al hacer clic con el botón derecho, marcarla como mina.
Además, cuando se hace clic por primera vez en la celda (por lo general, implica suerte), si hay un área en blanco alrededor de ella, debe expandirse directamente.
Agrega el siguiente código al JMS.prototype en jms.js:
// Obtener elemento
$: function (id) {
return this.doc.getElementById(id);
},
// Vincular eventos de clic (izquierdo y derecho) a cada celda
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);
}
}
},
// Expandir el área sin minas
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);
}
}
}
},
// Mostrar
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();
}
},
// Mostrar minas
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";
}
}
}
},
// Mostrar información de todas las celdas
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";
}
}
}
},
// Ocultar la información de las celdas mostradas
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 = "";
}
}
},
// Quitar los eventos vinculados a las celdas
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;
}
}
},
Hasta ahora, se ha completado la parte principal del juego. El siguiente paso es agregar funciones de control del juego para que el juego funcione sin problemas. Los pasos principales son los siguientes:
Agregar un controlador de eventos de clic al botón de inicio para restablecer los parámetros del juego.
Comenzar a contar el tiempo cuando el juego comienza.
Detener el conteo del tiempo y mostrar un mensaje cuando el juego termina.
Sección de jms.js
Agrega la función de entrada y inicio del juego al JMS.prototype en jms.js:
// Inicio del juego
begin: function() {
this.currentSetpCount = 0; // Restablece el contador de pasos a cero
this.markLandMineCount = 0;
this.beginTime = new Date(); // Tiempo de inicio del juego
this.hideAll();
this.bindCells();
},
// Fin del juego
end: function() {
this.endTime = new Date(); // Tiempo de fin del juego
if (this.endCallBack) { // Llama a la función de devolución de llamada si existe
this.endCallBack();
}
},
// Éxito del juego
success: function() {
this.end();
this.showAll();
this.disableAll();
alert("¡Felicitaciones!");
},
// Fallo del juego
failed: function() {
this.end();
this.showAll();
this.disableAll();
alert("GAME OVER!");
},
// Punto de entrada
play: function() {
this.init();
this.landMine();
this.calculateNoLandMineCount();
},
Sección de index.js
En index.js, agrega un controlador de eventos al botón de inicio para comenzar el juego y mostrar el tiempo del juego y el número restante de minas (después de jms = JMS("landmine", rowCount, colCount, minLandMineCount, maxLandMineCount); en la función init de index.js):
jms.endCallBack = function () {
clearInterval(timeHandle);
};
jms.landMineCallBack = function (count) {
landMineCountElement.innerHTML = count;
};
// Vincular evento al botón "Iniciar Juego"
beginButton.onclick = function () {
jms.play(); // Inicializar el juego
// Mostrar el número de minas
landMineCountElement.innerHTML = jms.landMineCount;
// Comenzar el juego
jms.begin();
// Actualizar el tiempo transcurrido
timeHandle = setInterval(function () {
timeShow.innerHTML = parseInt((new Date() - jms.beginTime) / 1000);
}, 1000);
};
Este experimento utiliza principalmente JavaScript para implementar una versión web del clásico juego Minesweeper. Se cree que a través de este experimento, los usuarios pueden mejorar su comprensión y habilidades de aplicación de JavaScript. Además, los usuarios también pueden aprender cómo utilizar el lenguaje JavaScript para abstraer y encapsular objetos en el juego.