Simple jeu de Memory réalisé en Javascript et CSS3 :
Score : 0
J'ai récemment décidé de me mettre au Javascript et d'approfondir un peu mes connaissances en HTML5 et CCS3. Ce petit jeu de Memory est le premier exercice que j'ai fait pour mettre en pratique ce que j'ai appris. L'idée m'est venue en lisant HTML5 Hacks par O'Reilly, plus particulièrement le Hack #25 qui expliquait comment réaliser un retournement de carte iOS-Style.
J'ai attaqué par la création d'une carte que je pouvais tourner et retourner en cliquant dessus. Jusque là rien de bien compliqué, j'utilisais simplement classList.toggle
pour ajouter/retirer la class flipped
à laquelle était liée mon animation.
.card {
transition: transform 0.6s;
transform-style: preserve-3d;
transform-origin: center;
}
.card.flipped {
transform: rotateY(-180deg);
}
.front, .back {
position: absolute;
width: 100%;
height: 100%;
backface-visibility: hidden;
}
.front {
transform: rotateY(-180deg);
}
L'étape suivante consistait à augmenter le nombre de cartes tout en pouvant les retourner indépendamment les unes des autres. Pour ceux qui s'y connaissent ça peut paraitre facile mais c'est certainement ce qui m'a donné le plus de fil à retordre. Le principal problème étant d'arriver à reconnaître sur quelle carte le clic avait été effectué. J'ai au final opté pour l'attribution d'un id unique à chacune des cartes et l'appel de ma fonction flip()
depuis la carte avec onclick
:
<div id="A1" class="card" onclick="flip(this.id)">
<div class="front">A</div>
<div class="back"></div>
</div>
Le moment était venu de créer le comportement normal du Memory, c'est à dire d'avoir à retourner deux cartes et si elles ne sont pas identiques, les retourner à nouveau.
var memory = document.getElementById('memory');
var cards = memory.getElementsByClassName('card');
var flipped = memory.getElementsByClassName('flipped');
var elem, pair, pairFound;
function isEven(x) {
return (x % 2) == 0;
} // Renvoie true si x est pair, false si impair
function flip(x) {
elem = document.getElementById(x); // elem = id de la carte cliquee
if (elem.classList.contains('flipped') === false) {
// verifie que la carte n'est pas deja retournee
elem.classList.add('flipped'); // retourne la carte
pair = elem.classList.item(1); // paire a laquelle la carte appartient
pairFound = memory.getElementsByClassName(pair + ' ' + 'flipped');
// Liste des cartes retournees appartenant a la meme paire
if (isEven(flipped.length)) { // si nb de cartes retournees est pair
if (pairFound.length == 2) { // si les deux cartes sont identiques
for (i = 0; i < 2; i++) {
pairFound[i].classList.add('found'); // ajoute la classe found
}
} else {
setTimeout (unflip, 700); // appele la fonction unflip apres 0.7sec
}
}
}
}
function unflip() {
var fl = flipped.length; //nb de cartes retournees
for (i = fl-1; i >= 0; i--) {
if (flipped[i].classList.contains('found')) { continue; }
// saute cette etape si la carte appartient a une paire trouvee
flipped[i].classList.remove('flipped'); // retourne la carte
}
}
J'ai ensuite ajouté un simple système de score, qui augmente de 1 à chaque fois que l'on retourne une carte; et un bouton Reset pour retourner toutes les cartes et les mélanger. J'ai également ajouté la possibilité de choisir le nombre de paires de cartes.
function reset() {
draw(); // remplace l'ensemble du Memory par le nb de paires choisi
score = 0;
document.getElementById('score').innerHTML = score; // MaJ du score
randomize(); // Melange les cartes
}
function randomize() {
var cardsArray = []; // Cree un Array vide
for (i = 0; i < cards.length; i++) {
cardsArray.push(cards[i].outerHTML); // Ajoute les cartes a l'Array
}
cardsArray.sort(function(a, b){return 0.5 - Math.random()}); // Melange
var memCards = '';
for (i = 0; i < cardsArray.length; i++) {
memCards += cardsArray[i]; // Distribue les cartes
}
memory.innerHTML = memCards;
}
function incrLetter(x) {
return String.fromCharCode(x.charCodeAt(0) + 1); // Incremente une lettre
}
function draw() {
var pairs = document.getElementById('pairs').value; // Recupere le nb de
var letter = 'A'; // paires voulu
var memCards = '';
for (i = 0; i < pairs; i++) {
memCards += '<div id="' + letter + '1" class="card mem' + letter +
'" onclick="flip(this.id)">' + '<div class="front">' + letter +
'</div><div class="back"></div></div>' +
'<div id="' + letter + '2" class="card mem' + letter +
'" onclick="flip(this.id)">' + '<div class="front">' + letter +
'</div><div class="back"></div></div>';
letter = incrLetter(letter);
} // Cree les cartes pour chaque paire
memory.innerHTML = memCards;
}
Code final
MàJ 21/04/17 : Remplacement de la méthode pour réarranger aléatoirement l'array contenant les cartes par la méthode Fisher-Yates-Durstenfeld Shuffle.
var memory = document.getElementById('memory');
var cards = memory.getElementsByClassName('card');
var flipped = memory.getElementsByClassName('flipped');
var score = 0;
var elem, pair, pairFound;
draw();
randomize();
function isEven(x) {
return (x % 2) == 0;
}
function incrLetter(x) {
return String.fromCharCode(x.charCodeAt(0) + 1);
}
function randomNumber(n) {
return Math.floor(Math.random() * n);
}
function shuffle(array) { // Fisher-Yates-Durstenfeld Shuffle
var shufArray = array;
var lcv = array.length - 1;
var n, nElem, lastElem;
for (; lcv >= 0; lcv--) {
n = randomNumber(lcv + 1);
nElem = shufArray[n];
lastElem = shufArray[lcv];
shufArray.splice(n, 1, lastElem);
shufArray.splice(lcv, 1, nElem);
}
return shufArray;
}
function flip(x) {
elem = document.getElementById(x);
if (elem.classList.contains('flipped') === false) {
elem.classList.add('flipped');
score += 1;
document.getElementById('score').innerHTML = score;
pair = elem.classList.item(1);
pairFound = memory.getElementsByClassName(pair + ' ' + 'flipped');
if (isEven(flipped.length)) {
if (pairFound.length == 2) {
for (i = 0; i < 2; i++) {
pairFound[i].classList.add('found');
}
} else {
setTimeout (unflip, 700);
}
}
}
}
function unflip() {
var fl = flipped.length;
for (i = fl-1; i >= 0; i--) {
if (flipped[i].classList.contains('found')) { continue; }
flipped[i].classList.remove('flipped');
}
}
function reset() {
draw();
score = 0;
document.getElementById('score').innerHTML = score;
randomize();
}
function randomize() {
var cardsArray = [];
for (i = 0; i < cards.length; i++) {
cardsArray.push(cards[i].outerHTML);
}
cardsArray = shuffle(cardsArray);
var memCards = '';
for (i = 0; i < cardsArray.length; i++) {
memCards += cardsArray[i];
}
memory.innerHTML = memCards;
}
function draw() {
var pairs = document.getElementById('pairs').value;
var letter = 'A';
var memCards = '';
for (i = 0; i < pairs; i++) {
memCards += '<div id="' + letter + '1" class="card mem' + letter +
'" onclick="flip(this.id)">' + '<div class="front">' + letter +
'</div><div class="back"></div></div>' +
'<div id="' + letter + '2" class="card mem' + letter +
'" onclick="flip(this.id)">' + '<div class="front">' + letter +
'</div><div class="back"></div></div>';
letter = incrLetter(letter);
}
memory.innerHTML = memCards;
}
Evidemment, puisqu'il s'agit de mon tout premier exercice de Javascript c'est certainement loin d'être parfait. Mais cela m'a permis de mettre en pratique une partie des choses que j'avais apprises et d'en découvrir d'autres (comme les node-lists par exemple).
La prochaine étape sera certainement de créer des images en SVG (Scalable Vector Graphics) pour chacune des paires et de proposer un choix entre lettres et images, ainsi qu'une option pour afficher ou non le fond coloré.