Memory the Game V2

Circular icon with two abstract cats

Click here to play Version 2 of Memory the Game || Click here to view the repo

To view blogpost of Version 1, click here

Screenshot of my Memory the Game
 
Earlier this year I created Memory in HTML, CSS and JavaScript. It was my first project after doing JavaScript tutorials. The project was pretty hard and it took me 4 weeks to finish. I learnt about objects, for loops in for loops, but most importantly: naming your functions and variables well.

 

One thing I always noticed with tutorials were the names. You would have a function that would be something like this:

 

function foo()
{
	console.log("I do something, but what?");
}

 
You probably know the 3 notorious ones: foo, bar and baz and they drive me crazy. If a function, even for testing purposes, is named like that, I can't read it.
I cannot determine what the function does in 1 viewing. What does "foo" do? Does it return something? Does it set or get something?
Since I cannot read what it does, I have to actually read the function to see what it does. But what I want is to tell the reader what the function does by only reading the function name.
Something like this:
 

var arrayOfNumbers= [1, 2];

function countSizeOfArray(array)
{
	console.log(array.length);
}

countSizeOfArray(arrayOfNumbers);

 
Without knowing what's inside countSizeOfArray(), you can pretty much guess that it will tell me the size of an array I pass on. I think it's important that anyone that would read your code can see what a function is or should do, solely based on names of variables and functions. If the code works, but no one can read it or understand what is happening, is it good code? In my opinion : no. Sure, the compiler can read it, but at the end of the day it is a human that has to write it, therefor also read it.
 
So writing readable code was one goal of mine. The other was to write better and smarter code. In my first version, I did a lot (in hindsight) bad things, like checking the innerHTML of a tile to see if they match. Or looping through an array while I actually don't have to do that. All kinds of small things.
Here are some examples:
 


function userInput()
{
	window["tileContainer"].length = 0;
	window["bottomValue"].length = 0;
	document.getElementById("content-container").innerHTML = "";

	var userNumber = parseInt(document.getElementById("userInputField").value);

	for (var i = 0; i < userNumber; i++)
	{
		window["bottomValue"].push(i);
		window["bottomValue"].push(i);
	}

	do
	{
		var index = Math.floor(Math.random() * window["bottomValue"].length);
		var bottomRandomValue = window["bottomValue"][index];
		window["bottomValue"].splice(index, 1);

		var divTileContainer = document.createElement("div");
		divTileContainer.setAttribute("class", "tile-container");

		window["contentContainer"].appendChild(divTileContainer);

		var innerTileContainerFront = document.createElement("div");
		innerTileContainerFront.class = "front";
		innerTileContainerFront.setAttribute("class", "front");

		var innerTileContainerBack = document.createElement("div");
		innerTileContainerBack.class = "back";
		innerTileContainerBack.setAttribute("class", "back");

		divTileContainer.appendChild(innerTileContainerFront);
		divTileContainer.appendChild(innerTileContainerBack);

		var NewTile = new Tile(bottomRandomValue, false, innerTileContainerBack, innerTileContainerFront, divTileContainer);

		window["tileContainer"].push(NewTile);

	}
	while(window["bottomValue"].length > 0);

  scaleIn();
}

 

So this code was used to create the tiles and append them to the div with class "tile-container" and I do this while the array is bigger than 0, because each time I do this I take one out of this array with splicing. Of course I was very happy that it worked at the time. But now, knowing what I know now…it is a disaster! UserInput as name for this function? It doesn't do anything with user input, other than parsing the input field to a number. Besides that it does :
 
1) Create the hidden numbers for the tiles
2) Push these numbers to an array
3) Create the HTML element ("tile")
4) Instantiate Tile
5) Push Tile to an array
 
So UserInput() obviously is not right here. This code does way too much and I need to split it up. The first thing I did was to create a Class for Tile:
 

class Tile
  {
    constructor(hiddenNumber, id)
    {
      this.hiddenNumber = hiddenNumber;
      this.tileState = TileState.FACE_DOWN;

      this.divTile = document.createElement("div");
      this.divTile.className = "tile-container";
      this.divTile.id = "tile-" + id;

      contentContainer.appendChild(this.divTile);

      this.innerTileContainerFront = document.createElement("div");
      this.innerTileContainerFront.className = "front";

      this.innerTileContainerBack = document.createElement("div");
      this.innerTileContainerBack.className = "back";

      this.divTile.appendChild(this.innerTileContainerFront);
      this.divTile.appendChild(this.innerTileContainerBack);

      this.divTile.addEventListener('click', userHandleClickEvent, false);
    }

    turnTileFaceUp()
    {
      this.divTile.classList.add("flipped");
      this.innerTileContainerBack.innerHTML = this.hiddenNumber;
      this.tileState = TileState.FACE_UP;
      clickedTilesContainer.push(this);
    }

    turnTileFaceDown()
    {
      this.divTile.classList.remove("flipped");
      this.innerTileContainerBack.innerHTML = "";
      this.tileState = TileState.FACE_DOWN;
      clickedTilesContainer.splice(this);
    }

    scaleInTile()
    {
      this.divTile.classList.add("scaled");
    }

  }

 

So the creation of tiles is now a seperate entity. If I instantiate this Tile, I get:
1) A tile in HTML element
2) A tile object with 3 functions, a hidden number and a enum for its state.

 

The upside to this is that everything related to a tile, such as its number, turning it face up or down is now a property of the Tile object itself, therefor it is properly encapsulated from the rest of the code.

 

How about this function?

  function initializeGame()
  {
    tileNumberContainer = [];
    tileObjectsContainer = [];
    contentContainer.innerHTML = "";
    let userNumber = parseInt(document.getElementById("userInputField").value);

		for (let i = 0; i < userNumber; i++)
		{
			// push twice, because 2 tiles have the same value
			tileNumberContainer.push(i);
			tileNumberContainer.push(i);
		}
    assignNumberToTiles();
    scaleInSpawnedTiles();
  }

 

So the name of the function, initializeGame(), tells us this runs to start the game. So what would need to happen at the start? Well, I would first have to create the tiles. I would have to assign a number to the tiles. And for visual effects I also created a scale effect when the tiles are created in HTML, so they kinda pop in to the screen.
I see this function has 2 other functions in it as well: assignNumberToTiles() and scaleInSpawnedTiles(). Without looking at their code, I think you know what they do.
 

 function assignNumberToTiles()
  {
    let loopCount = 0;

    do
    {
      let index = Math.floor(Math.random() * tileNumberContainer.length);
      let bottomRandomValue = tileNumberContainer[index];

      let newTile = new Tile(bottomRandomValue, loopCount);

      tileObjectsContainer.push(newTile);
      tileNumberContainer.splice(index, 1);

      loopCount++;
    }
    while (tileNumberContainer.length > 0);
  }

  function scaleInSpawnedTiles()
  {
    for (let i = 0; i < tileObjectsContainer.length; i++)
    {
      (function (i)
      {
        setTimeout(function ()
        {
          tileObjectsContainer[i].scaleInTile();
        }, randomIntFromInterval(10, 50) * i );
      })(i);
    };
  }

 

So the assignNumberToTiles() function does actually 2 things: It instantiates a tile object and assigns a number to it. You might argue that I gotta split those two up, but I don't think it's necessary here. I could have perhaps made a function for instantiating the objects. But it feels like that would be a bit too much; not every separate task needs its own function. A better way I think would be to name the function assignNumberToTiles perhaps spawnTilesAndAssignNumberToTiles(). Something to think about next time.
Here is a list of all my functions, without their code:
 

  • InitializeGame()
  • AssignNumberToTiles()
  • scaleInSpawnedTiles()
  • userHandleClickEvent(event)
  • gameLogic(clickedTileId)
  • compareTwoTilesAndDoAnimation()
  • removeTilesFromContainer(tileToBeRemoved)
  • removeTileFromGame(tileToBeRemoved)
  • amountOfTimesClickedOnTile()
  • userHasWon()

 
By reading this list, you can get an idea of how I setup the code and what each function does, without even seeing one line of code. Granted the names aren't perfect, but it's a good start to clean and readable code and I am very happy with the results.
 
To view the code, check the Github repo link at the top of this post.