Hi,
I have a memory game in javascript and I need help with 2 things. Although the first one I’m wondering if it’s possible.
-
I would like the cards to be shown when the game starts and then after X seconds, the card is flipped back.
-
Each card I would like to have a number to be labelled on it (e.g 1, 2, 3, 4, 5, 6 for Easy mode)
Here are the codes and here’s a demo : https://kuochye.github.io/memorygame/2
HTML
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Memory Game</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<script src="js/MemoryGame.js" type="text/javascript"></script>
<script src="js/Card.js" type="text/javascript"></script>
<link href="css/MemoryGame.css" type="text/css" rel="stylesheet" />
</head>
<body>
<section class="memory--menu-bar">
<div class="inner">
<div class="left"><h1 class="memory--app-name"><img src="rsnlogo.png"></h1></div>
<div class="right"><a href="#settings"><img id="memory--settings-icon" src="images/gear.png" /></a>
</div>
</div>
</section>
<section id="memory--settings-modal" class="valign-table modal show">
<div class="valign-cell">
<form>
<h2 class="memory--settings-label">Difficulty level:</h2>
<select id="memory--settings-grid">
<option value="2x3">Easy</option>
<option value="3x4">Medium</option>
<option value="4x5">Hard</option>
<option value="5x6">Insane</option>
</select>
<input id="memory--settings-reset" type="submit" value="Start New Game" />
</form>
</div>
</section>
<section id="memory--end-game-modal" class="valign-table modal">
<div class="valign-cell">
<div class="wrapper">
<h2 id="memory--end-game-message"></h2>
<h3 id="memory--end-game-score"></h3>
</div>
</div>
</section>
<section id="memory--app-container">
<ul id="memory--cards">
</ul>
</section>
<script src="js/BrowserInterface.js" type="text/javascript"></script>
</body>
</html>
BrowserInterface.js
(function($) {
// Handle clicking on settings icon
var settings = document.getElementById('memory--settings-icon');
var modal = document.getElementById('memory--settings-modal');
var handleOpenSettings = function (event) {
event.preventDefault();
modal.classList.toggle('show');
};
settings.addEventListener('click', handleOpenSettings);
// Handle settings form submission
var reset = document.getElementById('memory--settings-reset');
var handleSettingsSubmission = function (event) {
event.preventDefault();
var selectWidget = document.getElementById("memory--settings-grid").valueOf();
var grid = selectWidget.options[selectWidget.selectedIndex].value;
var gridValues = grid.split('x');
var cards = $.initialize(Number(gridValues[0]), Number(gridValues[1]));
if (cards) {
document.getElementById('memory--settings-modal').classList.remove('show');
document.getElementById('memory--end-game-modal').classList.remove('show');
document.getElementById('memory--end-game-message').innerText = "";
document.getElementById('memory--end-game-score').innerText = "";
buildLayout($.cards, $.settings.rows, $.settings.columns);
}
};
reset.addEventListener('click', handleSettingsSubmission);
// Handle clicking on card
var handleFlipCard = function (event) {
event.preventDefault();
var status = $.play(this.index);
console.log(status);
if (status.code != 0 ) {
this.classList.toggle('clicked');
}
if (status.code == 3 ) {
setTimeout(function () {
var childNodes = document.getElementById('memory--cards').childNodes;
childNodes[status.args[0]].classList.remove('clicked');
childNodes[status.args[1]].classList.remove('clicked');
}.bind(status), 500);
}
else if (status.code == 4) {
var score = parseInt((($.attempts - $.mistakes) / $.attempts) * 100, 10);
var message = getEndGameMessage(score);
document.getElementById('memory--end-game-message').textContent = message;
/*document.getElementById('memory--end-game-score').textContent =
'Score: ' + score + ' / 100';*/
document.getElementById("memory--end-game-modal").classList.toggle('show');
}
};
var getEndGameMessage = function(score) {
var message = "";
if (score == 100) {
message = "Amazing job!"
}
else if (score >= 70 ) {
message = "Great job!"
}
else if (score >= 50) {
message = "Great job!"
}
else {
message = "You can do better.";
}
return message;
}
// Build grid of cards
var buildLayout = function (cards, rows, columns) {
if (!cards.length) {
return;
}
var memoryCards = document.getElementById("memory--cards");
var index = 0;
var cardMaxWidth = document.getElementById('memory--app-container').offsetWidth / columns;
var cardHeightForMaxWidth = cardMaxWidth * (3 / 4);
var cardMaxHeight = document.getElementById('memory--app-container').offsetHeight / rows;
var cardWidthForMaxHeight = cardMaxHeight * (4 / 3);
// Clean up. Remove all child nodes and card clicking event listeners.
while (memoryCards.firstChild) {
memoryCards.firstChild.removeEventListener('click', handleFlipCard);
memoryCards.removeChild(memoryCards.firstChild);
}
for (var i = 0; i < rows; i++) {
for (var j = 0; j < columns; j++) {
// Use cloneNode(true) otherwise only one node is appended
memoryCards.appendChild(buildCardNode(
index, cards[index].value, cards[index].isRevealed,
(100 / columns) + "%", (100 / rows) + "%"));
index++;
}
}
// Resize cards to fit in viewport
if (cardMaxHeight > cardHeightForMaxWidth) {
// Update height
memoryCards.style.height = (cardHeightForMaxWidth * rows) + "px";
memoryCards.style.width = document.getElementById('memory--app-container').offsetWidth + "px";
memoryCards.style.top = ((cardMaxHeight * rows - (cardHeightForMaxWidth * rows)) / 2) + "px";
}
else {
// Update Width
memoryCards.style.width = (cardWidthForMaxHeight * columns) + "px";
memoryCards.style.height = document.getElementById('memory--app-container').offsetHeight + "px";
memoryCards.style.top = 0;
}
};
// Update on resize
window.addEventListener('resize', function() {
buildLayout($.cards, $.settings.rows, $.settings.columns);
}, true);
// Build single card
var buildCardNode = function (index, value, isRevealed, width, height) {
var flipContainer = document.createElement("li");
var flipper = document.createElement("div");
var front = document.createElement("a");
var back = document.createElement("a");
flipContainer.index = index;
flipContainer.style.width = width;
flipContainer.style.height = height;
flipContainer.classList.add("flip-container");
if (isRevealed) {
flipContainer.classList.add("clicked");
}
flipper.classList.add("flipper");
front.classList.add("front");
front.setAttribute("href", "#");
back.classList.add("back");
back.classList.add("card-" + value);
back.setAttribute("href", "#");
flipper.appendChild(front);
flipper.appendChild(back);
flipContainer.appendChild(flipper);
flipContainer.addEventListener('click', handleFlipCard);
return flipContainer;
};
})(MemoryGame);
Card.js
MemoryGame.Card = function(value) {
this.value = value;
this.isRevealed = false;
this.reveal = function() {
this.isRevealed = true;
}
this.conceal = function() {
this.isRevealed = false;
}
};
MemoryGame.js
var MemoryGame = {
settings: {
rows: 2,
columns: 3
},
// Properties that indicate state
cards: [], // Array of MemoryGame.Card objects
attempts: 0, // How many pairs of cards were flipped before completing game
mistakes: 0, // How many pairs of cards were flipped before completing game
isGameOver: false,
/**
* Modify default settings to start a new game.
* Both parameters need integers greater than one, and
* at least one them needs to be an even number.
*
* @param {number} columns
* @param {number} rows
* @return {array} shuffled cards
*/
initialize : function(rows, columns) {
var validOptions = true;
// Validate arguments
if (!(typeof columns === 'number' && (columns % 1) === 0 && columns > 1) ||
!(typeof rows === 'number' && (rows % 1) === 0) && rows > 1) {
validOptions = false;
throw {
name: "invalidInteger",
message: "Both rows and columns need to be integers greater than 1."
};
}
if ((columns * rows) % 2 !== 0) {
validOptions = false;
throw {
name: "oddNumber",
message: "Either rows or columns needs to be an even number."
};
}
if (validOptions) {
this.settings.rows = rows;
this.settings.columns = columns;
this.attempts = 0;
this.mistakes = 0;
this.isGameOver = false;
this.createCards().shuffleCards();
}
return this.cards;
},
/**
* Create an array of sorted cards
* @return Reference to self object
*/
createCards: function() {
var cards = [];
var count = 0;
var maxValue = (this.settings.columns * this.settings.rows) / 2;
while (count < maxValue) {
cards[2 * count] = new this.Card(count + 1);
cards[2 * count + 1] = new this.Card(count + 1);
count++;
}
this.cards = cards;
return this;
},
/**
* Rearrange elements in cards array
* @return Reference to self object
*/
shuffleCards: function() {
var cards = this.cards;
var shuffledCards = [];
var randomIndex = 0;
// Shuffle cards
while (shuffledCards.length < cards.length) {
// Random value between 0 and cards.length - 1
randomIndex = Math.floor(Math.random() * cards.length);
// If element isn't false, add element to shuffled deck
if(cards[randomIndex]) {
// Add new element to shuffle deck
shuffledCards.push(cards[randomIndex]);
// Set element to false to avoid being reused
cards[randomIndex] = false;
}
}
this.cards = shuffledCards;
return this;
},
/**
* A player gets to flip two cards. This function returns information
* about what happens when a card is selected
*
* @param {number} Index of card selected by player
* @return {object} {code: number, message: string, args: array or number}
*/
play: (function() {
var cardSelection = [];
var revealedCards = 0;
var revealedValues = [];
return function(index) {
var status = {};
var value = this.cards[index].value;
if (!this.cards[index].isRevealed) {
this.cards[index].reveal();
cardSelection.push(index);
if (cardSelection.length == 2) {
this.attempts++;
if (this.cards[cardSelection[0]].value !=
this.cards[cardSelection[1]].value) {
// No match
this.cards[cardSelection[0]].conceal();
this.cards[cardSelection[1]].conceal();
/**
* Algorithm to determine a mistake.
* Check if the pair of at least
* one card has been revealed before
*
* indexOf return -1 if value is not found
*/
var isMistake = false;
if (revealedValues.indexOf(this.cards[cardSelection[0]].value) === -1) {
revealedValues.push(this.cards[cardSelection[0]].value);
}
else {
isMistake = true;
}
if (revealedValues.indexOf(this.cards[cardSelection[1]].value) === -1) {
revealedValues.push(this.cards[cardSelection[1]].value);
}
if (isMistake) {
this.mistakes++;
}
revealedValues.push(this.cards[cardSelection[0]].value);
status.code = 3,
status.message = 'No Match. Conceal cards.';
status.args = cardSelection;
}
else {
revealedCards += 2;
if (revealedCards == this.cards.length) {
// Game over
this.isGameOver = true;
revealedCards = 0;
revealedValues = [];
status.code = 4,
status.message = 'GAME OVER! Attempts: ' + this.attempts +
', Mistakes: ' + this.mistakes;
}
else {
status.code = 2,
status.message = 'Match.';
}
}
cardSelection = [];
}
else {
status.code = 1,
status.message = 'Flip first card.';
}
}
else {
status.code = 0,
status.message = 'Card is already facing up.';
}
return status;
};
})()
};