Memory Game

Quick JS test of the classic matching memory game.

by Jonathan Lam

HTML

<div id="settings">
<input type="number" id="numRows" value="4" placeholder="4" max="10" min="2" /> &#215; <span id="boardSize"></span> BOARD
</div>
<div id="gameboard"></div>
<div id="cite">A memory game by Jonathan Lam for <a href="http://www.thehomeworklife.co.nf">The Homework Life</a>.<br />See the code at <a href="https://jsfiddle.net/jlam55555/2x0ufnpz/50/embedded/result/">JSFiddle.net</a>.</div>

CSS

@import url(https://fonts.googleapis.com/css?family=Lora:700);
@import url(https://fonts.googleapis.com/css?family=Merriweather);
* {
	border-radius: 5px;
	box-sizing: border-box;
}
html, body {
	height: 100%;
	width: 100%;
	min-height: 500px;
	min-width: 500px;
	margin: 0;
	padding: 0;
	font-size: 16px;
}
div#settings {
	text-align: center;
	margin-top: 20px;
	font-family: merriweather;
}
span#boardSize {
	padding: 0 10px;
}
input#numRows {
	background-color: transparent;
	border: none;
	display: inline-block;
	width: 50px;
	outline: none;
	text-align: right;
	font-family: inherit;
	font-size: inherit;
}
div#cite {
	font-family: merriweather;
	text-align: center;
}
div#cite > a {
	color: black;
	border-bottom: 1px dotted black;
	text-decoration: none;
	border-radius: 0;
}
div#gameboard {
	background-color: #2c5986;
	height: 500px;
	width: 500px;
	margin: 20px auto;
}
div.card:hover {
	background-color: rgba(200, 200, 255, 0.75);
}
div.card:active {
	background-color: rgba(200, 255, 200, 0.75);
}
div.card {
	background-color: rgba(255, 255, 255, 0.75);
	display: inline-flex;
	align-items: center;
	cursor: pointer;
	float: left;
	color: transparent;
	transition: all .2s;
}
@keyframes spinIn {
	0% { transform: rotateY(0deg); }
	50% { color: transparent; }
	51% { color: black; }
	100% { transform: rotateY(180deg); color: black; }
}
@keyframes spinOut {
	0% { transform: rotateY(180deg); }
	50% { color: black; }
	51% { color: transparent; }
	100% { transform: rotateY(0deg); color: transparent; }
}
@keyframes shake {
	0% { transform: rotate(0deg); }
	20% { transform: rotate(10deg); }
	50% { transform: rotate(0deg); background-color: rgba(255, 200, 200, 0.75); }
	80% { transform: rotate(-10deg); }
	100% { transform: rotate(0deg); backgound-color: rgba(255, 255, 255, 0.75) }
}
@keyframes disappear {
	from { opacity: 1; }
	to { opacity: 0; }
}
div.card span {
	flex-grow: 1;
	text-align: center;
	font-family: Lora;
	font-weight: bold;
	font-size: 30px;
	color:...

JavaScript

var numOpen = 0;
var numMoves = 0;
var draw = function() {
	numMoves = 0;
	numOpen = 0;
	var gameboard = $("div#gameboard");
	var numRows = parseInt($("input#numRows").val());
	$("span#boardSize").text(numRows);
	if(numRows % 2 != 0) {
		$("input#numRows").val(4);
		$("span#boardSize").text(4);
		draw();
		return;
	}
	var allNums = [];
	for(var i = 1; i <= numRows*numRows/2; i++) {
		allNums.push(i, i);
	}
	// The following shuffle uses the "Fisher-Yates" shuffle algorithm
	// See http://bost.ocks.org/mike/shuffle/
	var index, left = allNums.length;
	while(left) {
		index = Math.floor(Math.random() * left--);
		var tmp = allNums[left];
		allNums[left] = allNums[index];
		allNums[index] = tmp;
	}
	var margin = -(5/2)*numRows + 30;
	gameboard.css({
		"padding": margin/2 + "px"
	});
	gameboard.empty();
	for(var i = 0; i < numRows*numRows; i++) {
		var newSquare = $("<div>");
		newSquare.append("<span>" + allNums[i] + "</span>");
		newSquare.addClass("card");
		newSquare.css({
			"width": "calc(100% / " + numRows + " - " + margin + "px)",
			"height": "calc(100% / " + numRows + " - " + margin + "px)",
			"margin": margin/2 + "px"
		});
		gameboard.append(newSquare);
	}
	$("div.card").click(function() {
		if(!$(this).hasClass("active")) {
			if(numOpen == 2) {
				$(this).addClass("nope");
				setTimeout(function() {
					$("div.card.nope").removeClass("nope");
				}, 200);
				return;
			}
			numOpen++;
			$(this).removeClass("inactive").addClass("active");
			if(numOpen == 2) {
				setTimeout(function() {
					numOpen = 0;
					numMoves++;
					if($("div.card.active:first > span").text() == $("div.card.active:last > span").text()) {
						$("div.card.active").removeClass().addClass("card").addClass("gone");
						if($("div.gone").length == numRows*numRows) {
							if(confirm("You finished in " + numMoves + " moves!\nTry again?"))
								draw();
						}
					} else {
						$("div.card.active").removeClass("active").addClass("inactive");
					}
				}, 500);
			}
		}...