Creating a Minesweeper game in Javascript tutorial

Anleitung für die Programmierung eines Minesweeper-Spiels in Javascript

How to create a fully functional minesweeper game in javascript

Wie man ein voll funktionierendes Minesweeper-Spiel in Javascript erstellt

Hello world!

Hallo Welt!

This tutorial is about creating one of the most popular windows game ever: MINESWEEPER ^.^

Dieses Tutorial handelt davon, wie man eines der populärsten Windows-Spiele herstellt,
das es je gab: MINESWEEPER ^.^

Before we start we have to make a plan regarding what are the components of our game. So, what are the components, the key functions of minesweeper?

Bevor wir loslegen, müssen wir uns einen Plan darüber zulegen, welche Bestandteile unser Spiel hat.
Was sind also die Bestandteile, die Schlüsselfunktionen von Minesweeper?

This is the end product that we are going to create: http://tak3r.com/tutorials/download/minesweeper.html

Hier ist schon mal das Endprodukt, das wir im Folgenden schaffen werden: minesweeper-game.html

Give it a 5 minute thought and see if you come up with the following list:

  • Grid generator function
  • Mine placing function
  • Field discover function
  • Flag placing function
  • Multiple flield flip function. ( I’ll explain later on)
  • Win/loose events
  • And a reset button for loosers :D

Denke fünf Minuten darüber nach und schau, ob Du auf folgende Liste kommst:

  • Funktion, die ein Raster erzeugt
  • Funktion, die die Minen verteilt
  • Funktion, die ein Feld aufdeckt
  • Funktion, die eine Flagge setzt
  • Funktion, die mehrere Felder nacheinander aufdeckt (werde ich später genauer erklären)
  • Gewinnen und Verlieren-Ereignisse
  • Und einen "Wiederherstellen"-Button für Verlierer :D

For the sake of logic we will do them in that order.

Um der Logik willen werden wir sie in dieser Reihenfolge abarbeiten.

We will do the same file both the javascript and the HTML+css. Well… we can finish the html right now. All the html we will ever need to write for this game is:

Wir werden beides in der gleichen Datei anlegen: den Javascript-Code sowie das HTML+CSS. Gut… das HTML werden wir mal sofort fertig machen. Das ganze HTML, das wir für dieses Spiel brauchen werden, ist:

<div id="m">&nbsp;</div>

This is just the place where we will transform into a minesweeper field. The rest is ALL javascript+css. Lets finish with the css also, so it won’t bother us later. I have created the game ahead and used just some minor CSS style to go around. If you want you can enhance it with background images of fields, flags and mines, but tipically I used just 3 colors: lightblue for a discovered field, red for a mine field and orange for a flag field. So, here’s the code:

Das ist die Stelle, die wir in ein Minesweeper-Spiel umwandeln werden. Der Rest ist alles Javascript+CSS. Lasst uns gleich auch mal das CSS fertig machen, damit es uns später nicht mehr beschäftigt. Ich habe das Spiel soweit fertig programmiert, aber nur ein paar CSS-Stile für das Nötigste festgelegt. Wenn Du willst, kannst Du das Spiel mit Hintergrundbildern für Felder, Flaggen und Minen erweitern; ich habe hier nur drei Farben verwendet: hellblau für aufgedeckte Felder, rot für ein vermintes Feld sowie orange für beflaggte Felder.
Hier erstmal das CSS:

<style type="text/css">
#m { display:inline-block; background:#55AAFF; border:3px outset #aaaaaa; padding:4px; } 
#m #status{ border:2px inset #eeeeee; width:100%; margin:1px 1px 3px 1px; color:#fff; font-size:12px; } 
#m #status input { text-align:center; } 
#m #mineField{ border:2px inset #eeeeee; padding:1px; } 
#m span{ display:inline-block; background:#dddddd; width:15px; height:15px; margin:1px 1px 0 0; font-family:georgia; font-size:8pt; text-align:center; cursor:pointer; } 
#m span span{ margin:0; background-color:lightblue; } 
#m #minesLeft{ font-weight:bold; } 
#m .tile{ } 
#m .mine{ background-color:red; } 
#m .clue{ background-color:lightblue; font-weight:bold; } 
#m .flag{ background-color:orange; } 
</style>

In case you don’t know, the CSS is inserted in the <head> part of the page, but it works fine in the <body> also. As for the css, it is pretty straight forward and I don’t think It must be explained any further. Now starts the fun part: the javascript. We will start off by adding the attribute: onload=”setupMinesweeper(‘m’);” to the <body> tag. What this says is that when the document has finished loading run the setupMinesweeper(‘m’) function. This is the function we are going to make now and the argument is the id tag of the field we want to place it in as string, in our case ‘m’. Having done that move to your <script> tag and ’till the end of the episode we will work only there. We start off by making the setupMinesweeper function that initially creates the fields:

Für den Fall, dass Du es nicht weißt: das CSS wird im <head>-Teil der Seite eingefügt, aber es tut seine Wirkung auch im <body>. (Anmerkung des Übersetzers: Am besten lagert man es allerdings in eine eigene Datei aus, genauso wie das Javascript, die man dann im head-Bereich einbindet).
Das CSS ist glaube ich ziemlich klar, so dass ich es wohl nicht weiter erklären muss.
Nun fängt der Teil an, der wirklich Spaß macht: das Javascript. Wir werden damit anfangen, dem Body-Tag das Attribut onload="setupMinesweeper('m');" hinzuzufügen. Dies bewirkt, dass die Funktion setupMinesweeper() ausgeführt wird, wenn das Dokument vollständig geladen wurde. Mit dieser Funktion werden wir uns nun beschäftigen; als Argument übergeben wir der Funktion die ID des Feldes, und zwar als Zeichenkette (string), in dem wir das Spiel platzieren wollen, in unserem Fall also 'm'.
Wenn wir dies gemacht haben, gehen wir zum Script-Tag und bis zum Schluss der Geschichte werden wir uns nur noch dort aufhalten.
Wir fangen damit an, die Funktion setupMinesweeper() zu erstellen, die das Spielfeld mit seinen Kästchen erzeugt:

var mWidth = 30;
var mHeight = 24;
var mNumber = 80;
var mStatus = 1; //0=lose 1=playing 2=win
var mFlags = mNumber;
var mFields = 0;
var mArr = [];
var mClues = [];
var mPinned = [];
var mColors = ['#000000', '#0000FF', '#008200', '#FF0000', '#000084', '#840000', '#008284', '#840084', '#FF7F00', '#FF00FF', '#FFFFFF'];
 
function setupMinesweeper(id) { 
 mStatus = 1; 
 mFlags = mNumber; 
 mFields = 0;
 mArr.length = 0; 
 mClues.length = 0; 
 var h = '<div
 id="mineField">'; 
 for(i=1;i<=mHeight;++i) {
  for(j=1;j<=mWidth;++j) { 
   h += '<span id="m'+i+'x'+j+'"
   class="tile" onclick="setFlag(this);"
   ondblclick="revealField(this)">&nbsp;</span>'; 
  } 
  h += '<br />'; 
 } 
 h += '</div>'; 
 h = '<table id="status"><td align="left"><input type="button"
 value="Reset" onclick="setupMinesweeper(\'m\');" /></td>'+
 '<td style="text-align:right">Flags left: <label
 id="minesLeft">'+mFlags+'</label>&nbsp;</td></tr></table>'
 + h; 
 document.getElementById(id).innerHTML = h;
}

I have also added some global variables that we will need later on:
mWidththe number of columns
mHeightthe number of rows
mNumberthe number of mines
mStatusthe current game status. Initially it is set to 1 for playing
mFlagsthe number of flags which we add the number of mines. Initially it is equal but it decreases as you place flags…DUUUH
mFields - the number of discovered fields.
mArr - in this array we store the position of the mines
mCluesand here we store the clues and their position
mPinnedan array where we will store the position of the flags placed by the player
mColorsan array of colors. We will attribute each of these colors to a specific number (blue for 1, green for 2, etc.)
I have reassigned these variables their default values again in the setupMinesweeper() function because we will need to reset these when we want to restart the game so it is best to do it from now.
In case you did not know, in javascript if you attribute a variable the value [] it is the same thing as giving it the value array(). For example: var a = array() is the same
thing as var a = []. I also left a space between mNumber and mStatus to split the variables any user can modify (number of mines, width and height of a field)
o the ones that must be global and that are modified only by the script itself

Ich habe hier auch ein paar globale Variablen eingefügt, die wir später brauchen werden:
mWidthdie Anzahl der Spalten des Spielfelds
mHeightdie Anzahl der Reihen
mNumberdie Anzahl der Minen
mStatusder momentane Spiel-Status. Zu Beginn ist er auf 1 gesetzt, d.h. das Spiel läuft
mFlagsdie Anzahl der zu setzenden Flaggen, die wir auf die Anzahl der Minen setzen. Anfänglich ist beides gleich, aber die Flaggenanzahl nimmt ab, wenn Du sie setzt…DUUUH
mFields - die Anzahl der aufgedeckten Kästchen.
mArr - in diesem Array speichern wir die Position der Minen
mCluesund hier speichern wir die Hinweise und ihre Position
mPinnedist ein Array, in dem wir die Position der Flaggen speichern, die der Spieler setzt
mColorsder Array für die Farben der Hinweiszahlen. Jede Farbe wird einer bestimmten Zahl zugeordnet: blau für 1, grün für 2, usw.)
Am Anfang der Funktion setupMinesweeper() habe ich diesen Variablen noch einmal ihre Ausgangswerte zugewiesen, weil wir diese Variablen zurücksetzen müssen, wenn wir das Spiel neu starten; es ist das beste, das hier schon zu tun.
Nur für den Fall, dass Du es nicht weißt: wenn Du in Javascript einer Variable die beiden eckigen Klammern [] zuweist, ist es dasselbe als würdes Du new Array() schreiben; z. B. var a = new Array() ist das gleiche wie var a = [].
Ich habe auch einen Abstand zwischen den Variablen mNumber und mStatus gelassen, damit ein Nutzer die Werte ändern kann (Anzahl der Minen, Breite und Höhe des Spielfelds)
Die anderen Variablen müssen ebenfalls global sein; diese werden aber nur vom Skript selbst verändert.

Now take a look at the function up there. This function is equivalent to the function of filling a matrix with values. So what our function does is for every row (mHeight)
it creates a mWidth number of fields resulting in a mHeight x mWidth number of fields. In the same time it turns the mArr[] and mClues arryas into matrices.
For each field we attribute an unique id that will allow us to find the position of that field with the help of its id. Basically the id of a field is composed out of:
the letter m + its position on the Y axis + the letter x + its position on the X axis.

Nun werden wir mal einen Blick auf die vollständige Funktion oben werfen. Diese Funktion ist gleichbedeutend mit einer Funktion, die eine Matrix mit Werten befüllt. Was unsere Funktion also tut, ist, für jede Reihe (mHeight) eine Anzahl von mWidth Kästchen zu erzeugen; daraus resultiert ein Feld mit einer Anzahl von mHeight x mWidth Kästchen. Gleichzeitig werden die Arrays mArr[] und mClues in Matrizen verwandelt.
Jedem Feld fügen wir eine eindeutige ID hinzu, mit dessen Hilfe wir die Position jedes Spielfeldkästchen eindeutig bestimmen können. Grundsätzlich ist jede ID eines Kästchens folgendermaßen aufgebaut:
der Buchstabe m + seine Position auf der Y-Achse + der Buchstabe x + seine Position auf der X-Achse.

I have also added to more attributes that are quite self explanatory. When a user CLICKS once on a field it will attempt to place a flag on it, when he DOUBLECLICKS the field will
reveal what is “underneath” it: a mine or a hint.

Ich habe weitere Attribute hinzugefügt, die aber im Grunde selbst-erklärend sind: wenn ein Spieler EINFACH auf ein Kästchen KLICKT, wird versucht, dort eine Flagge zu setzen (es wird orange), wenn er DOPPELKLICKT, wird angezeigt, was “darunter” ist: eine Mine oder ein Hinweis.

There is also a field added under the field matrix where we tell a user how many flags he has left to place and then we insert all this html code into the container we gave as
an argument for our function, in our case ‘m’.

Über dem Spielfeld habe ich einen Bereich hinzugefügt, wo wir dem Spieler zeigen, wieviele Minen er noch nicht markiert hat (wieviele Flaggen er noch nicht gesetzt hat).
Dann injizieren wir den gesamten HTML-Code in den Container, dessen ID wir der Funktion als Argument übergeben haben, in unserem Fall ‘m’.

If you test your code now you should see the field matrix.

Wenn Du das Spiel jetzt testest, solltest Du die Spielfeld-Matrix sehen.

Moving on, we need to place the mines on the field and their corresponding hints. I have done this also in the setupMinesweeper() function for ease of access, after all,
adding the mines is part of the setup process. So, here is the enhanced function:

Wenn wir weitergehen, müssen wir nun die Minen im Spielfeld verteilen sowie die davon abhängigen Hinweiszahlen. Ich habe das ebenfalls in der Funktion setupMinesweeper() untergebracht für einen leichteren Zugang; denn das Minenlegen ist Teil des Setup-Prozesses.
Hier ist also die erweiterte Funktion:

function setupMinesweeper(id) { 
 mStatus = 1; 
 mFlags = mNumber; 
 mFields = 0; 
 mArr.length = 0; 
 mClues.length = 0; 
 var h = '<div id="mineField">'; 
 for(i=1;i<=mHeight;++i) { 
  for(j=1;j<=mWidth;++j) { 
   h += '<span id="m'+i+'x'+j+'" class="tile" onclick="setFlag(this);" ondblclick="revealField(this)">&nbsp;</span>'; } h += '<br />'; 
  } 
  h += '</div>'; 
  h = '<table id="status"><td align="left"><input type="button"
  value="Reset" onclick="setupMinesweeper(\'m\');" /></td>'+ '<td style="text-align:right">Flags left: <label id="minesLeft">'+mFlags+'</label>&nbsp;</td></tr></table>' + h; 
  document.getElementById(id).innerHTML = h; 
  
  for(i=0;i<=mHeight+1;++i) { 
   mArr[i] = []; 
   mClues[i] = []; 
   mPinned[i] = []; 
    for(j=0;j<=mWidth+1;++j) { 
	 mArr[i][j] = 0; 
	 mClues[i][j] = 0; 
	 mPinned[i][j] = 0; 
    } 
  } 
  for(i=1;i<=mNumber;++i) { 
   var idR = Math.floor(Math.random() * mHeight)+1; 
   var idC = Math.floor(Math.random() * mWidth)+1; 
   while(mArr[idR][idC]== 1 || (idR==1 && idC==1) ) { 
    idR = Math.floor(Math.random() * mHeight)+1; 
	idC = Math.floor(Math.random() * mWidth)+1; 
   }
   mArr[idR][idC] = 1;
   // new method to count hints
   for (var ddx=-1; ddx<=1; ddx++) { 
    for (var ddy=-1; ddy<=1; ddy++) {
     if ((idR+ddx>=0)&&(idR+ddx<=mWidth) &&(idC+ddy>=0)&&(idC+ddy<=mHeight)){
      if (mArr[idR+ddx][idC+ddy]==0) 	
       mClues[idR+ddx][idC+ddy]++;
     }
    }
   }
 }
 /* 
  Original method to count the hints
  for(i=1;i<=mHeight;++i) { 
   for(j=1;j<=mWidth;++j)
   { 
	if(mArr[i][j]== 0) 
	 mClues[i][j] = mArr[i-1][j-1]+mArr[i-1][j]+mArr[i-1][j+1]+mArr[i][j-1]+mArr[i][j+1]+mArr[i+1][j-1]+mArr[i+1][j]+mArr[i+1][j+1];
   } 
  }
 */  
}

Ok, as you might have noticed i have added 2 more for-s. The first one adds mines on the field… in fact it adds mines on the mArr[][] matrix but it is the same thing. The process
is fairly simple. It selects a random number between 1 and mWidth and another random number between 1 and mHeight. These are the idR and idC (stands for: “the ID of the Row” and
“the ID of the Column”). After that it goes into a while that states the following: “while at the coordinates of the random set of numbers you have chosen randomly is a mine OR
you have chosen to place a mine on the top left corner field, choose another set of random numbers and try again until”. So basically it searches for a field that does not
have a mine and that is different from the top left corner field (that field is never a mine because the guys from microsoft considered that you MUST have 1 field from where
to start the game) and gives it the value 1 which means that it is a mine.

Ok, wie Du sicher bemerkt hast, habe ich zwei weitere for-Schleifen eingebaut. Die erste legt Minen ins Feld… tatsächlich fügt sie der mArr[][]-Matrix Minen hinzu, aber das ist das gleiche.
Dieser Vorgang ist wirklich einfach. Es werden zwei Zufallszahlen erzeugt, eine die zwischen 1 und mWidth sowie eine weitere die zwischen 1 und mHeight liegt. Dies sind die Variablen idR and idC (steht für: “die ID der Reihe” und “die ID der Spalte”).
Danach folgt eine while-Schleife, die folgendes tut: “solange an den Koordinaten (in den Kästchen), die durch die Zufallszahlen bestimmt sind, schon eine Mine liegt, ODER es das linke, obere Kästchen ist, wähle einen neuen Satz Zufallszahlen und versuche erneut bis”. Kurz gesagt sucht die Schleife also nach einem Kästchen, das noch keine Mine enthält und nicht das obere, linke Kästchen ist (dieses Kästchen soll niemals eine Mine enthalten, da die Leute von MICROSOFT wollten, dass man immer ein Feld haben MUSS, vom dem aus man das Spiel starten kann. Anmerkung des Übersetzers: das stimmt nicht ganz. Eine Mine, auf die man beim 1. Klick irgendwo im Feld stößt, wird in die linke, obere Ecke verschoben; insofern ist es richtig, dass dieses Kästchen anfangs keine Mine zugewiesen bekommen darf); wenn ein solches Kästchen gefunden ist, wird ihm der Wert 1 zugewiesen; das bedeutet, dass dort eine Mine liegt.

The last for adds the hints for the mines. It goes and checks every field of the matrix to see if it is empty (not a mine). If it is then it gives it the value of the number of
mines around it.

Die letzte for-Schleife fügt die Hinweise auf Minen hinzu. Sie testet jedes Feld der Matrix, ob es leer ist (keine Mine enthält). Falls es leer ist, gibt sie ihm als Wert die Anzahl an Minen, die um es herum liegen.
Anmerkung des Übersetzers: Ich habe die Methode insoweit geändert, indem ich in allen Kästchen, die um eine Mine herum liegen, die Hinweiszahl sofort um 1 erhöhe, nachdem eine Mine gelegt wurde; damit wird diese letzte for-Schleife überflüssig. Diese Methode habe ich übrigens dem Minesweeper-Spiel von Lutz Tautenhahn entnommen.

Moving on with our coding:

Weiter geht's im Code:

function setFlag(x) { 
 var tmp = x.id.slice(1).split('x'); 
 var i = Number(tmp[0]); 
 var j = Number(tmp[1]); 
 if(mStatus==1) { 
  if(!hasClass(x,'clue') && !hasClass(x,'mine')) { 
   if(!hasClass(x,'flag') && mFlags>0) { 
    x.className = 'flag'; 
    --mFlags; 
    mPinned[i][j] = 1; 
   } 
   else if(hasClass(x,'flag')) { 
    x.className = 'tile'; 
	++mFlags; 
	mPinned[i][j] = 0; 
   } 
   document.getElementById('minesLeft').innerHTML = mFlags; 
   if(mFlags==0) 
    checkWin(); 
   } 
   else 
    return; 
 } 
} 

function hasClass(obj, c) { 
 var tmp = obj.className.split(' '); 
 for (i in tmp) 
  if(tmp[i] == c) 
   return true; 
  return false; 
} 

function checkWin() { 
 if(mFlags==0) { 
  var w = true; 
  for(a=1;a<=mHeight;++a) 
   for(b=1;b<=mWidth;++b) 
    if(mArr[a][b]!=mPinned[a][b]) 
	 win = false; 
	if(w) 
	 youWin(); 
	else 
	 return; 
 } 
 else return; 
}

Here we have the flag function, a checkWin() function and a small helpful boolean function that searches to see if an item has a specific class. Why we need this? Because at the next step when we start revealing fields we will mark each field by giving it a class specific to the type of field it is. So, there are a couple of if-s there:
-the first one checks to see if we are still playing (if we lost or won it is just stupid to be able to place flags)
-the second one checks to see if the field is NOT discovered.
-the third if checks to see whether the field is a flag field or not and if we have any more flags to place. if it is not a flag and we still have flags then it adds it the “flag” class and decrements the flags left variable, otherwise if it is a flag it unflags it and increments the mFlags var.
-the forth and final one check to see if we still have flags to place. if we don’t (mFlags==0) then it summons the checkWin() function. What this does it compare the mine matrix with the flag matrix. if they overlap perfectly then we won.

Hier haben wir die Funktion, die die Flagge setzt, eine checkWin()-Funktion sowie eine kleine, hilfreiche Funktion, die einen Bool'schen Wert zurückgibt und untersucht, ob ein Kästchen eine bestimmte CSS-Klasse hat.
Wozu brauchen wir das?
Wegen des nächsten Schritts, wenn wir nämlich Kästchen aufdecken, markieren wir sie mit einer bestimmten CSS-Klasse je nachdem welcher Typ Kästchen es ist.
Es gibt hier also eine Reihe von if-Abfragen:

  • mit der 1. wird getestet, ob das Spiel noch läuft (wenn wir verloren oder gewonnen haben, ist es unsinnig noch Flaggen setzen zu können)
  • die 2. untersucht, ob das angeklickte Kästchen noch NICHT aufgedeckt ist
  • die 3. prüft, ob das Kästchen schon eine Flagge hat und ob es überhaupt noch Flaggen zu setzen gibt. Wenn das Kästchen nicht beflaggt ist und es noch Flaggen gibt, wird das Kästchen mit der CSS-Klasse “flag” ausgezeichnet und die Variable für die Anzahl der Flaggen (Minen) mFlags heruntergezählt; wenn es schon mit einer Flagge versehen ist, wird es “entflaggt” und die mFlags-Variable hochgezählt.
  • die 4. und letzte if-Abfrage dient dazu, festzustellen, ob es überhaupt noch Flaggen zum Setzen gibt. Wenn es keine mehr gibt (mFlags==0), dann wird die checkWin()-Function aufgerufen.
    Diese vergleicht die Minen-Matrix mit der Flaggen-Matrix; wenn diese genau übereinstimmen, haben wir gewonnen.

And in the end we update the “minesLeft” field we inserted when we called the setupMinesweeper() function

Und zum Schluss wird die Minen-Anzeige “minesLeft” auf den neuen Stand gebracht; diese wurde ja beim Aufruf der Funktion setupMinesweeper() eingebaut.

And now come the 3 big functions:

Und nun kommen die drei großen Funktionen:

function revealField(obj) { 
 if(mStatus==1) { 
  var tmp = obj.id.slice(1).split('x'); 
  var i = Number(tmp[0]); 
  var j = Number(tmp[1]); 
  if(mArr[i][j] == 1) 
   youLose(); 
  else { 
   for(a=i-1;a<=i+1;a++) 
    for(b=j-1;b<=j+1;b++) 
	 if(mClues[a][b]==0) 
	  rollback(a,b,1); 
  } 
  showField(i,j); 
 } 
} 

function rollback(i,j) { 
 if(i>0 && i<=mHeight && j>0 && j<=mWidth)  
  if(mClues[i][j] == 0 && mArr[i][j]==0) { 
   var c = 0; 
   for(a=i-1;a<=i+1;a++) 
    for(b=j-1;b<=j+1;b++) 
	 if(a!=i&&b!=j) 
	  c+=mArr[a][b]; 
	 mClues[i][j] = 10; 
	 showField(i,j); 
	 if (c== 0) { 
	  rollback(i-1,j-1); 
	  rollback(i-1,j); 
	  rollback(i-1,j+1);
      rollback(i,j-1); 
	  rollback(i,j+1); 
	  rollback(i+1,j-1); 
	  rollback(i+1,j);
      rollback(i+1,j+1); 
	 } 
	} 
	else if(mClues[i][j]!=10 && mArr[i][j]==0) 
	 showField(i,j); 
} 

function showField(i,j) { 
 var obj = document.getElementById('m'+i+'x'+j); 
 if(mArr[i][j] == 1) { 
  obj.className ='mine'; 
 } 
 else { 
  if(hasClass(obj,'clue')) 
   return; 
  if(hasClass(obj,'flag')) {
   ++mFlags; 
   mPinned[i][j] = 0; 
   document.getElementById('minesLeft').innerHTML = mFlags;
  } 
  ++mFields; 
  obj.className ='clue'; 
  obj.innerHTML = mClues[i][j]<9 ? '<span style="color:'+mColors[mClues[i][j]]+'">'+mClues[i][j]+'</span>' : '&nbsp;';
  checkWin(); 
 } 
}

Although they might seem scary, there is not that much to explain here.

The showField() function basically adds the proper attribute and value to the field being checked. At the end, if the total number of visible fields plus the total number of mines is equal to the total number of fields it calls a win() function.
It also adds the specific color to each specific number. I just took those colors from my minesweeper, they are pretty general so nothing unusual there.

The rollback() function is a recursive function. A recursive function is that which calls itself. Its arguments are the X and Y coordinates of the field to be checked.
So, first it checks if the arguments entered are in the game matrix. If it is, and if is not a mine and it is not discovered yet it counts the mines around it. If the number
of the mind around the field is 0 it shows it and checks all calls the rollback function again on each of the fields surrounding it(8 calls).

And the revealField() function. This is the one who tells the other functions what to do. It initially check to see if we are still playing. If we are it gets the coordinates
of the field that was doubleclicked. It gets its matrix coordinates by removing the first caracter of its ID which is always ‘m’ and spliting it by the value ‘x’. What is on the
left is its position on the Y axis and what is on the right is its position on the X axis.

Obwohl sie ein wenig furchteinfößend zu sein scheinen, gibt's hier nicht viel zu erklären.

Die showField()-Function belegt im Grunde nur das angeklickte Kästchen mit dem passende Attribut und dem entsprechenden Wert. Am Ende, wenn die Gesamtzahl aufgedeckter Kästchen plus der Gesamtnzahl an Minen mit der Gesamtzahl der Kästchen übereinstimmt, ruft sie die youWin()-Funktion auf.
Sie färbt ebenfalls jede Hinweiszahl mit ihrer spezifischen Farbe. Ich habe die Farben einfach von meinem Minesweeper-Original-Spiel übernommen; sie sind ziemlich allgemein, also nicht Ungewöhnliches hier.

Die rollback()-Function ist eine rekursive Funktion. Eine rekursive Funktion ist eine, die sich selbst wieder aufruft. Ihre Argumente sind die X- und Y-Koordinate des Kästchens, das geprüft wird.
Zuerst wird festgestellt, ob die erte, die sie übergeben bekommen hat, überhaupt in der Matrix vorhanden sind. Falls dieses Kästchen also existiert und keine Mine enthält und nicht aufgedeckt ist, werden die Minen gezählt, die es umgeben. Wenn keine Minen um dieses Kästchen herum zu finden sind, wird es aufgedeckt, und es werden alle Kästchen um es herum von der rollback()-Funktion untersucht, indem sie weitere acht mal aufgerufen wird.

Und dann bleibt noch die revealField()-Function. Diese weist die anderen Funktionen an, was sie zu tun haben. Zuerst stellt sie fest, ob wir überhaupt noch im Spiel sind. Wenn ja, holt sie die Koordinaten des Kästchens, das doppelgeklickt wurde. Es ermittelt die Matrix-Koordinaten, indem sie die ersten Buchstaben der ID entfernt, der immer ‘m’ ist, und spaltet den Rest beim Buchstaben ‘x’. Auf der linken Seite ist die Position auf der Y-Achse, auf der rechten die Position auf der X-Achse.

It then check to see if it is a mine. If it is, we have lost. If not, it checks the sum of all the mines around it. If it is 0 it calls the rollback() function I told you about
earlier, if not then it just reveals the field.

Finally we end this tutorial up by writing our final lines of code, the win() and lose() functions:

Dann wird noch überprüft, ob wir auf eine Mine gestoßen sind; wenn ja, haben wir verloren. Wenn nicht, wird die Summe der Minen um dieses Feld herum ermittelt. Wenn sie 0 ist, ruft sie die rollback()-Funktion auf, die ich Dir eben schon erklärt habe; wenn sie nicht 0 ist, wird nur dieses Kästchen aufgedeckt.

Zuguterletzt beenden wir dieses Tutorial mit dem Schreiben unserer letzten Zeilen Code, der youWin()- und der youLose()-Funktion:

function youWin() { 
 alert('you freakin\' won!'); 
 mStatus = 2; 
} 
function youLose() { 
 for(a=1;a<=mHeight;++a) 
  for(b=1;b<=mWidth;++b) 
   if(mArr[a][b]==1) 
    document.getElementById('m'+a+'x'+b).className='mine'; 
 mStatus = 0; 
}

Nothing much to say here… If we won we summon an alert box with a message, if not we show where all the mines were.

all together now:

Mehr gibt es nicht zu sagen… Wenn wir gewonnen haben, lassen wir eine Alert-Box mit einer Meldung erscheinen, wenn nicht, zeigen wir all die Stellen, an denen Minen lagen.

Nochmal alles zusammen:

<style type="text/css">
#m { display:inline-block; background:#55AAFF; border:3px outset #aaaaaa; padding:4px; } 
#m #status{ border:2px inset #eeeeee; width:100%; margin:1px 1px 3px 1px; color:#fff; font-size:12px; } 
#m #status input { text-align:center; } 
#m #mineField{ border:2px inset #eeeeee; padding:1px; } 
#m span{ display:inline-block; background:#dddddd; width:15px; height:15px; margin:1px 1px 0 0; font-family:georgia; font-size:8pt; text-align:center; cursor:pointer; } 
#m span span{ margin:0; background-color:lightblue; } 
#m #minesLeft{ font-weight:bold; } 
#m .tile{ } 
#m .mine{ background-color:red; } 
#m .clue{ background-color:lightblue; font-weight:bold; } 
#m .flag{ background-color:orange; } 
</style>
<script type="text/javascript"> 
var mWidth = 30; 
var mHeight = 24; 
var mNumber = 80; 
var mStatus = 1; //0=lose 1=playing 2=win 
var mFlags = mNumber; 
var mFields = 0; 
var mArr = []; 
var mClues = []; 
var mPinned = []; 
var mColors = ['#000000', '#0000FF', '#008200', '#FF0000', '#000084', '#840000', '#008284', '#840084', '#FF7F00', '#FF00FF', '#FFFFFF'];
 
function setupMinesweeper(id){ 
 mStatus = 1; 
 mFlags = mNumber; 
 mFields = 0;
 mArr.length = 0; 
 mClues.length = 0; 
 var h = '<div id="mineField">'; 
 for(i=1;i<=mHeight;++i) {
  for(j=1;j<=mWidth;++j) { 
   h += '<span id="m'+i+'x'+j+'" class="tile" onclick="setFlag(this);"
ondblclick="revealField(this)">&nbsp;</span>'; } 
  h += '<br />'; 
 } 
 h += '</div>'; 
 h = '<table id="status"><td align="left"><input type="button"
value="Reset" onclick="setupMinesweeper(\'m\');" />
		</td>'+'<td style="text-align:right">Flags left: <label
id="minesLeft">'+mFlags+'</label>&nbsp;</td></tr></table>'+ h; 
 document.getElementById(id).innerHTML = h;
 for(i=0;i<=mHeight+1;++i) { 
  mArr[i] = []; 
  mClues[i] = []; 
  mPinned[i] = []; 
  for(j=0;j<=mWidth+1;++j) { 
   mArr[i][j] = 0; 
   mClues[i][j] = 0;
   mPinned[i][j] = 0; 
  } 
 } 
 for(i=1;i<=mNumber;++i) { 
  var idR = Math.floor(Math.random() * mHeight)+1; 
  var idC = Math.floor(Math.random() * mWidth)+1; 
  while(mArr[idR][idC] == 1 || (idR==1 && idC==1) ) { 
   idR = Math.floor(Math.random() * mHeight)+1; 
   idC = Math.floor(Math.random() * mWidth)+1; 
  }
  mArr[idR][idC] = 1;
  for (var ddx=-1; ddx<=1; ddx++) { 
    for (var ddy=-1; ddy<=1; ddy++) {
     if ((idR+ddx>=0)&&(idR+ddx<=mWidth) &&(idC+ddy>=0)&&(idC+ddy<=mHeight)){
      if (mArr[idR+ddx][idC+ddy]==0) 	
       mClues[idR+ddx][idC+ddy]++;
     }
    }
   }  
 } 
} 

function revealField(obj) { 
 if(mStatus==1) { 
  var tmp = obj.id.slice(1).split('x'); 
  var i = Number(tmp[0]); 
  var j = Number(tmp[1]); 
  if(mArr[i][j] == 1) 
   lose(); 
  else {
   for(a=i-1;a<=i+1;a++) for(b=j-1;b<=j+1;b++) 
    if(mClues[a][b]==0)
     rollback(a,b,1); } 
   showField(i,j); 
 } 
} 

function rollback(i,j) { 
 if(i>0 && i<=mHeight && j>0 && j<=mWidth)  
  if(mClues[i][j] == 0 && mArr[i][j]==0) { 
   var c = 0;
   for(a=i-1;a<=i+1;a++) 
    for(b=j-1;b<=j+1;b++)
     if(a!=i&&b!=j) 
	  c+=mArr[a][b]; 
	 mClues[i][j] = 10;
     showField(i,j); 
	 if (c == 0) { 
	  rollback(i-1,j-1); 
	  rollback(i-1,j);
      rollback(i-1,j+1); 
	  rollback(i,j-1); 
	  rollback(i,j+1); 
	  rollback(i+1,j-1);
      rollback(i+1,j); 
	  rollback(i+1,j+1); 
	 } 
	} 
	else if(mClues[i][j]!=10 && mArr[i][j]==0) 
	 showField(i,j); 
} 

function showField(i,j) {
 var obj = document.getElementById('m'+i+'x'+j); 
 if(mArr[i][j] == 1) {
  obj.className ='mine'; 
 } 
 else { 
  if(hasClass(obj,'clue')) 
   return;
  if(hasClass(obj,'flag')) {
   ++mFlags; 
   mPinned[i][j] = 0;
   document.getElementById('minesLeft').innerHTML = mFlags;
  } 
  ++mFields;
  obj.className ='clue'; 
  obj.innerHTML = mClues[i][j] < 9 ? '<span style="color:'+mColors[mClues[i][j]]+'">'+mClues[i][j]+'</span>':'&nbsp;';
  checkWin(); 
 } 
} 

function setFlag(x) { 
 var tmp = x.id.slice(1).split('x'); 
 var i = Number(tmp[0]); 
 var j = Number(tmp[1]); 
 if(mStatus==1) { 
  if(!hasClass(x,'clue') && !hasClass(x,'mine')) { 
   if(!hasClass(x,'flag') && mFlags>0) {
    x.className = 'flag'; 
	--mFlags; 
	mPinned[i][j] = 1; 
   } 
   else if(hasClass(x,'flag')) { 
    x.className = 'tile'; 
	++mFlags; 
	mPinned[i][j] = 0; 
   } 
   document.getElementById('minesLeft').innerHTML = mFlags;
   if(mFlags==0) checkWin(); 
  } 
  else return; 
 } 
} 

function hasClass(obj, c) {
 var tmp = obj.className.split(' '); 
 for (i in tmp) 
  if(tmp[i] == c)
   return true; 
  return false; 
} 

function checkWin() { 
 if(mFlags==0) { 
  var w = true; 
  for(a=1;a<=mHeight;++a) 
   for(b=1;b<=mWidth;++b)
    if(mArr[a][b]!=mPinned[a][b]) 
	 win = false; 
	if(w) 
	 youWin(); 
	else return; 
   }
   else 
    return; 
} 

function youWin() { 
 alert('You freakin\' won!'); 
 mStatus = 2; 
} 

function youLose() { 
 for(a=1;a<=mHeight;++a)
  for(b=1;b<=mWidth;++b) 
   if(mArr[a][b]==1)
    document.getElementById('m'+a+'x'+b).className='mine'; 
   mStatus = 0; 
}
</script>

Not that hard, is it?

War nicht so schwer, oder?