Je crois que tout le monde a déjà joué au classique jeu de démineur. Ses règles sont simples mais extrêmement addictives. Avez-vous déjà pensé à le développer vous-même? Aujourd'hui, nous allons créer une version web du démineur. Commençons par regarder une capture d'écran de l'interface.
👀 Aperçu
🎯 Tâches
Dans ce projet, vous allez apprendre :
Comment concevoir l'algorithme du jeu de démineur
Comment créer la structure de fichiers du projet
Comment implémenter la mise en page de la page avec HTML et CSS
Comment dessiner la grille avec JavaScript
Comment ajouter des événements de clic aux cellules pour gérer le déroulement du jeu
Comment implémenter des fonctions de contrôle de jeu telles que le démarrage et la fin du jeu
🏆 Réalisations
Après avoir terminé ce projet, vous serez capable de :
Concevoir et implémenter des algorithmes de jeu
Créer une structure de fichiers pour un projet web
Utiliser HTML et CSS pour créer des mises en page de page
Utiliser JavaScript pour dessiner des grilles et gérer des événements
Implémenter des fonctions de contrôle de jeu
Skills Graph
%%%%{init: {'theme':'neutral'}}%%%%
flowchart RL
javascript(("JavaScript")) -.-> javascript/BasicConceptsGroup(["Basic Concepts"])
javascript(("JavaScript")) -.-> javascript/DOMManipulationGroup(["DOM Manipulation"])
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{{"Création d'un jeu de démineur avec JavaScript"}}
javascript/loops -.-> lab-445713{{"Création d'un jeu de démineur avec JavaScript"}}
javascript/functions -.-> lab-445713{{"Création d'un jeu de démineur avec JavaScript"}}
javascript/array_methods -.-> lab-445713{{"Création d'un jeu de démineur avec JavaScript"}}
javascript/obj_manip -.-> lab-445713{{"Création d'un jeu de démineur avec JavaScript"}}
javascript/dom_select -.-> lab-445713{{"Création d'un jeu de démineur avec JavaScript"}}
javascript/dom_manip -.-> lab-445713{{"Création d'un jeu de démineur avec JavaScript"}}
javascript/event_handle -.-> lab-445713{{"Création d'un jeu de démineur avec JavaScript"}}
javascript/dom_traverse -.-> lab-445713{{"Création d'un jeu de démineur avec JavaScript"}}
end
Préparatifs de développement
Avant de commencer le développement, concevons d'abord l'algorithme du jeu.
Les règles du jeu de démineur sont simples :
Il y a plusieurs carrés sur le plateau de jeu. Chaque carré contient un nombre (un carré vide indique que le nombre est 0) ou une bombe. Le nombre dans le carré représente le nombre de bombes dans les carrés voisins. L'objectif du joueur est de découvrir les carrés numérotés en utilisant le moins de temps possible.
Sauf pour les carrés sur les bords, chaque carré a 8 carrés voisins : en haut, en bas, à gauche, à droite et 4 carrés diagonaux. Par conséquent, la plage de nombres est de 0 à 8.
Notre algorithme est donc le suivant :
Sur la base du niveau de difficulté sélectionné par l'utilisateur (il y a trois niveaux : débutant, intermédiaire et avancé, avec plus de bombes et de carrés à mesure que le niveau augmente), générer aléatoirement un certain nombre de bombes et les placer aléatoirement sur les carrés. Ensuite, parcourir les carrés, calculer le nombre dans chaque carré et le marquer sur le carré. Lorsque le joueur clique gauche sur un carré, le contenu du carré est affiché (si le carré contient une bombe, le défi échoue et le jeu se termine), et lorsque le joueur clique droit sur un carré, le carré est marqué comme une bombe. Le défi est réussi seulement lorsque toutes les bombes sont correctement marquées et que tous les carrés sans bombe sont ouverts, et le jeu se termine.
Astuce pratique : Étant donné que la plage de nombres dans les carrés est de 0 à 8, nous pouvons considérer marquer les nombres dans les carrés où se trouvent les bombes comme 9 pour faciliter les calculs.
Structure de fichiers du projet
Tout d'abord, nous devons créer la structure de fichiers suivante sous le chemin ~/projet :
Les fichiers mine.png et flag.png dans le répertoire du projet sont respectivement les images pour la mine et le drapeau.
Mise en page de la page
Tout d'abord, nous avons besoin d'un panneau pour afficher les informations du jeu, y compris le nombre de mines restantes, le temps écoulé, le niveau de difficulté, etc. Étant donné que le nombre de carrés n'est pas fixe, nous ne dessinons pas les carrés pour le moment et les dessinons plutôt dans le code JS.
Créez un fichier index.html et ajoutez le code suivant :
<!-- index.html -->
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<title>Minesweeper avec JavaScript</title>
<link
href="https://cdn.jsdelivr.net/npm/[email protected]/dist/tailwind.min.css"
rel="stylesheet"
/>
<link rel="stylesheet" type="text/css" href="index.css" />
</head>
<body class="bg-gray-100">
<div
id="JMS_main"
class="container mx-auto p-4 bg-white shadow-lg rounded-lg"
>
<table id="landmine" class="mx-auto"></table>
<div id="operation" class="text-center">
<div class="text-lg text-red-600">
Nombre de mines restantes :
<span class="font-semibold" id="landMineCount">0</span>
</div>
<div class="text-lg text-orange-500">
Durée du temps : <span class="font-semibold" id="costTime">0</span> s
</div>
<fieldset class="my-4">
<legend class="text-lg font-semibold">
Sélection de la difficulté :
</legend>
<input
type="radio"
name="level"
id="llevel"
checked="checked"
value="10"
class="mr-2"
/>
<label for="llevel">Facile (10*10) </label><br />
<input
type="radio"
name="level"
id="mlevel"
value="15"
class="mr-2"
/>
<label for="mlevel">Intermédiaire (15*15) </label><br />
<input
type="radio"
name="level"
id="hlevel"
value="20"
class="mr-2"
/>
<label for="hlevel">Difficile (20*20) </label><br />
</fieldset>
<button
id="begin"
class="px-4 py-2 bg-blue-500 text-white font-semibold rounded-lg hover:bg-blue-700"
>
Démarrer le jeu</button
><br />
<div class="text-left mt-4">
<div class="font-semibold">Astuce :</div>
<ul class="list-disc ml-6">
<li>
1. Cliquez sur "Démarrer le jeu" pour démarrer le chronomètre du
jeu.
</li>
<li>
2. Cliquez sur "Démarrer le jeu" pendant la partie pour commencer
une nouvelle partie.
</li>
</ul>
</div>
</div>
</div>
<script src="jms.js"></script>
<script src="index.js"></script>
</body>
</html>
Ensuite, nous devons ajuster la position des informations du jeu sur le panneau et ajouter quelques styles. Ajoutez le code suivant à index.css et enregistrez :
Après avoir effectué les étapes précédentes, nous devons dessiner la grille. Pour que le code soit plus clair, nous séparons la partie d'implémentation du jeu et la partie d'appel. La partie d'implémentation du jeu est placée dans jms.js, qui se trouve dans le même répertoire que index.html, et la partie d'appel du jeu est placée dans index.js, également dans le même répertoire.
Pour dessiner la grille, nous devons passer certains paramètres, tels que l'id du tableau où la grille sera placée, et le nombre de cellules (représenté par le nombre de lignes et de colonnes). En outre, d'autres données liées au jeu doivent être initialisées.
Partie jms.js
Ajoutez le code suivant à jms.js et enregistrez-le :
(function () {
// Initialise l'objet Minesweeper et initialise les données
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); // Tableau pour dessiner la grille
this.cells = this.table.getElementsByTagName("td"); // Cellules
this.rowCount = rowCount || 10; // Nombre de lignes de la grille
this.colCount = colCount || 10; // Nombre de colonnes de la grille
this.landMineCount = 0; // Nombre de mines
this.markLandMineCount = 0; // Nombre de mines marquées
this.minLandMineCount = minLandMineCount || 10; // Nombre minimum de mines
this.maxLandMineCount = maxLandMineCount || 20; // Nombre maximum de mines
this.arrs = []; // Tableau correspondant aux cellules
this.beginTime = null; // Heure de début du jeu
this.endTime = null; // Heure de fin du jeu
this.currentSetpCount = 0; // Nombre d'étapes effectuées
this.endCallBack = null; // Fonction de rappel lorsque le jeu se termine
this.landMineCallBack = null; // Fonction de rappel pour mettre à jour le nombre restant de mines lorsqu'une mine est marquée
this.doc.oncontextmenu = function () {
// Désactive le menu contextuel
return false;
};
this.drawMap();
};
// Crée les cellules dans le prototype de JMS
JMS.prototype = {
// Dessine la grille
drawMap: function () {
var tds = [];
// Pour la compatibilité des navigateurs
if (
window.ActiveXObject &&
parseInt(navigator.userAgent.match(/msie ([\d.]+)/i)[1]) < 8
) {
// Crée un nouveau fichier de style CSS
var css = "#JMS_main table td{background-color:#888;}",
// Obtient la balise head
head = this.doc.getElementsByTagName("head")[0],
// Crée une balise style
style = this.doc.createElement("style");
style.type = "text/css";
if (style.styleSheet) {
// Affecte le style CSS à la balise style
style.styleSheet.cssText = css;
} else {
// Crée un nœud dans la balise style
style.appendChild(this.doc.createTextNode(css));
}
// Ajoute la balise style comme balise enfant de la balise head
head.appendChild(style);
}
// Boucle pour créer le tableau
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(""));
},
// Ajoute du HTML au tableau
setTableInnerHTML: function (table, html) {
if (navigator && navigator.userAgent.match(/msie/i)) {
// Crée un div dans le document propriétaire du tableau
var temp = table.ownerDocument.createElement("div");
// Crée le contenu du tbody du tableau
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;
})();
Le code ci-dessus inclut du code pour la compatibilité avec les navigateurs IE, qui peut être ignoré.
Partie index.js
Dans le code d'appel dans index.js, nous devons lier l'événement des boutons de sélection de difficulté, puis appeler le JMS que nous avons défini ci-dessus pour commencer à dessiner la grille.
Ajoutez le code suivant à index.js et enregistrez-le :
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("Êtes-vous sûr de vouloir terminer le jeu actuel?"))
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);
}
Ensuite, ouvrez index.html dans le navigateur, et la grille devrait être affichée. L'effet est le suivant :
Cliquez sur la sélection de difficulté à droite pour voir le nombre de cellules changer.
Maintenant, commençons à initialiser le jeu, ce qui implique principalement trois étapes :
Initialiser toutes les cellules (représentées par un tableau dans le code) à 0.
Générer un nombre aléatoire de mines et les placer aléatoirement dans le tableau, en définissant la valeur de l'élément du tableau à 9.
Calculer les nombres dans les autres cellules et stocker les valeurs dans le tableau.
Ajoutez le code suivant à l'intérieur de JMS.prototype dans jms.js :
// Initialisation : définir la valeur par défaut du tableau à 0 et déterminer le nombre de mines
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;
},
// Définir la valeur des éléments du tableau qui contiennent des mines à 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;
}
},
// Calculer les nombres dans les autres cellules
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]++;
}
}
}
},
// Obtenir un nombre aléatoire
selectFrom: function (iFirstValue, iLastValue) {
var iChoices = iLastValue - iFirstValue + 1;
return Math.floor(Math.random() * iChoices + iFirstValue);
},
// Trouver les numéros de ligne et de colonne en fonction de la valeur
getRowCol: function (val) {
return {
row: parseInt(val / this.colCount),
col: val % this.colCount
};
},
Maintenant, il est nécessaire d'ajouter des événements de clic aux cellules. Lorsque l'on clique avec le bouton gauche, afficher le nombre dans la cellule (si c'est une mine, le jeu se terminera). Lorsque l'on clique avec le bouton droit, la marquer comme une mine.
En outre, lorsque la cellule est cliquée pour la première fois (généralement par hasard), si elle est entourée d'une zone vide, elle devrait être directement développée.
Ajoutez le code suivant au JMS.prototype dans jms.js :
// Obtenir l'élément
$: function (id) {
return this.doc.getElementById(id);
},
// Lier les événements de clic (gauche et droit) à chaque cellule
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);
}
}
},
// Développer la zone sans mines
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);
}
}
}
},
// Afficher
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();
}
},
// Afficher les mines
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";
}
}
}
},
// Afficher les informations de toutes les cellules
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";
}
}
}
},
// Effacer les informations des cellules affichées
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 = "";
}
}
},
// Supprimer les événements liés aux cellules
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;
}
}
},
Jusqu'à présent, la majeure partie du jeu a été terminée. L'étape suivante consiste à ajouter des fonctions de contrôle du jeu pour que le jeu fonctionne sans problème. Les principales étapes sont les suivantes :
Ajouter un écouteur d'événements de clic au bouton de démarrage pour réinitialiser les paramètres du jeu.
Commencer à compter le temps lorsque le jeu commence.
Arrêter de compter le temps et afficher une alerte lorsque le jeu se termine.
Section jms.js
Ajoutez l'entrée du jeu et la fonction de démarrage au JMS.prototype dans jms.js :
// Démarrage du jeu
begin: function() {
this.currentSetpCount = 0; // Remettre le compteur d'étapes à zéro
this.markLandMineCount = 0;
this.beginTime = new Date(); // Heure de début du jeu
this.hideAll();
this.bindCells();
},
// Fin du jeu
end: function() {
this.endTime = new Date(); // Heure de fin du jeu
if (this.endCallBack) { // Appeler la fonction de rappel s'il existe
this.endCallBack();
}
},
// Victoire au jeu
success: function() {
this.end();
this.showAll();
this.disableAll();
alert("Félicitations!");
},
// Défaite au jeu
failed: function() {
this.end();
this.showAll();
this.disableAll();
alert("GAME OVER!");
},
// Point d'entrée
play: function() {
this.init();
this.landMine();
this.calculateNoLandMineCount();
},
Section index.js
Dans index.js, ajoutez un écouteur d'événements au bouton de démarrage pour démarrer le jeu, et affichez le temps de jeu et le nombre restant de mines (après jms = JMS("landmine", rowCount, colCount, minLandMineCount, maxLandMineCount); dans la fonction init de index.js) :
jms.endCallBack = function () {
clearInterval(timeHandle);
};
jms.landMineCallBack = function (count) {
landMineCountElement.innerHTML = count;
};
// Lier l'événement au bouton "Démarrer le jeu"
beginButton.onclick = function () {
jms.play(); // Initialiser le jeu
// Afficher le nombre de mines
landMineCountElement.innerHTML = jms.landMineCount;
// Démarrer le jeu
jms.begin();
// Mettre à jour le temps écoulé
timeHandle = setInterval(function () {
timeShow.innerHTML = parseInt((new Date() - jms.beginTime) / 1000);
}, 1000);
};
Cette expérience utilise principalement JavaScript pour implémenter une version web du jeu classique de démineur. Il est supposé que grâce à cette expérience, les utilisateurs peuvent améliorer leur compréhension et leurs compétences d'application de JavaScript. En outre, les utilisateurs peuvent également apprendre à utiliser le langage JavaScript pour extraire et encapsuler des objets dans le jeu.
We use cookies for a number of reasons, such as keeping the website reliable and secure, to improve your experience on our website and to see how you interact with it. By accepting, you agree to our use of such cookies. Privacy Policy