Computing: Website and Database Programming

Simple 2048 puzzle game.


1. The 2048 puzzle game.
 
2048 is a single-player sliding tile puzzle video game written by the Italian web developer Gabriele Cirulli and published on GitHub. The objective of the game is to slide numbered tiles on a grid to combine them to create a tile with the number 2048. It was originally written in JavaScript and CSS over a weekend, and released on 9 March 2014 as free and open-source software subject to the MIT License. Versions for iOS and Android followed in May 2014. If you are searching for a PC version of the game, click the following link to freely download a feature-rich 2048 game Lazarus/Free Pascal project.
Original game rules (Wikipedia): 2048 is played on a plain 4×4 grid, with numbered tiles that slide when a player moves them using the four arrow keys. Every turn, a new tile randomly appears in an empty spot on the board with a value of either 2 or 4. Tiles slide as far as possible in the chosen direction until they are stopped by either another tile or the edge of the grid. If two tiles of the same number collide while moving, they will merge into a tile with the total value of the two tiles that collided. The resulting tile cannot merge with another tile again in the same move.
The 2048 game has a mathematical nature. Strategies to win the game include keeping the highest tiles in a specific corner and to keep that tile in that corner and to fill the specified row with the highest numbers.
2. My Simple 2048 puzzle game online application.
 
I wrote my Simple 2048 game web application from scratch, without having a look at the source of the original game or any other 2048 game related code. With a theoretical Javascript knowledge of more or less zero and the rudimentary experience of only 2 JavaScript applications written in my whole life, I guess that my code is far away from what it would be, if a professional JavaScript developer would have written it. On the other side, I think that the code, as it is, is rather simple to understand by people, who are new to this language, as I am, and, most important, the application works all correctly...
My Simple 2048 game web application differs from the original game in the following:
  • The player can choose among 4 board sizes: 3x3, 4x4, 5x5 and 6x6.
  • The game is played with the mouse (and not with the arrow keys).
  • The randomly positioned new tiles have always a value of 2 (instead of 2 or 4).
  • A merged tile can (and always will) merge a further time when colliding with a tile with the same value.
  • The game ends when any tile has a value of 2048 (in the original version, the game can be continued even if the game aim has been reached).
Use the following link to load the online application
3. Simple 2048 game Javascript code.
  element of this table has a style attribute including a visibility property, that by this function is set to visible for the table row including the table corresponding to the board actually selected, and to collapse for the other table rows. This way, one single row of the table, i.e. one single of the 4 boards, will be displayed.
Click the following link to download the Simple 2048 game Javascript code and all other files needed to run this application on your web server. Have a look at the ReadMe.txt file, included in the download archive, for details about the different files, and where to place them on the server. As the application is pure Javascript, it is possible to run it in any web browser without the need of a webserver. Use the following link to download the offline version of Simple 2048 game.
Globally, the application works this way:
  • When the player pushes one of the four "board" buttons, the function boardSelect({board-size}) is executed. It sets the actual board size to the function argument, initializes the two dimensional array, that contains the values of the board grid elements (sets them to 0) and displays an empty board of the size selected.
  • The game is started when the player pushes the "Start" button. This calls the function gameStart(). It generates the first random position "2" tile and displays the board (with the new tile).
  • The tiles are moved when the player pushed one of the four "direction" buttons. This calls the function tilesMove({direction}). It moves the tiles according to the direction passed as argument (updates the two dimensional array), if the game isn't yet over, generates the next random position "2" tile and displays the board (with the tiles moved and the new tile). If 2048 has been reached on any tile, or if the grid is completely filled up, the game ends with a "win" or "lose" message.
Lets have a look at the code, step by step.
Global variables and constants.
First there are some global variable declarations, in particular the two dimensional array that is used to store the actual board grid values. There are also some global constants arrays: one with all possible tile values and a corresponding one with these tiles' color; a third one contains the font size that has to be used with a given board. In fact, I use a different tile size for each of the 4 boards and this makes necessary the usage of diferent font sizes. The array with these font sizes becomes necessary as the font size is part of the value of the style attribute of the corresponding HTML table field. As this style is dynamically applied by JavaScript, the font size value has to be altered, and the values used for the different board sizes must be known by the script. Here the global variable and constant declarations:
   let boardGrid = []; let boardSize = -1;
   let newX = -1; let newY = -1;
   let gameStarted = false; let gameEnded = false;
   let gameWin = false; let gameLose = false;
   // Create the two dimensional array
   for (i = 0; i < 6; i++) {
      boardGrid[i] = [];
      for (j = 0; j < 6; j++) {
         boardGrid[i][j] = -1;
      }
   }
   let tileValues = new Array(0, 2, 4, 8, 16, 32, 64, 128, 256, 1024, 2048);
   let tileColors = new Array("White", "DodgerBlue", "DeepSkyBlue", "Cyan", "Aquamarine", "Lime", "GreenYellow", "Yellow", "Orange", "DarkOrange", "Red", "DarkRed");
   let tileFontSizes = new Array(30, 25, 20, 15);
Game board selection.
The function boardSelect({board-size}) is called, when the player pushes one of the "board" buttons on the webpage. It sets the global variable boardSize to the actual board size, initializes the board by calling the function boardInit(), then displays the (empty) board by calling the function boardDisplay(). The HTML code of the game webpage includes all 4 boards (as 4 tables), included in the rows of a table. the
Code of the boardSelect({board-size}) function:
    function boardSelect (bsize) {
        boardSize = bsize;
        boardInit();
        boardDisplay();
        gameStarted = false; gameEnded = false;
        // Make board with actual size (table row) visible
        if (document.getElementById) {
            let board = 'b' + bsize;
            document.getElementById('b3').style.visibility = "collapse";
            document.getElementById('b4').style.visibility = "collapse";
            document.getElementById('b5').style.visibility = "collapse";
            document.getElementById('b6').style.visibility = "collapse";
            document.getElementById(board).style.visibility = "visible";
        }
    }
Code of the boardInit() function:
    function boardInit() {
        for (i = 0; i < 6; i++) {
            for (j = 0; j < 6; j++) {
                if (i < boardSize && j < boardSize) {
                    boardGrid[i][j] = 0;
                }
                else {
                    boardGrid[i][j] = -1;
                }
            }
        }
    }
Game start.
The function gameStart() is called when the player pushes the "Start" button on the webpage (this button becomes visible together with the board grid). The function generates a new value "2" tile at a random position on the board (a 2 is inserted into the corresponding cell of the boardGrid array) by calling the function tilesNew(). The new board layout is then displayed by calling boardDisplay(). Note the usage of the global variable gameStarted: Set when the game actually is started, further pushes on the "Start" button will have no effect (disabling the "Start" button until the player wants to play a new game by pushing one of the "board" buttons). The reason for using newX and newY as global variables (instead of defining them locally within the tilesNew() function, is to save the x- and y-position of the new tile, that I want to highlight this tile using a color different from the standard color for value "2" tiles (cf. boardDisplay() function).
Code of the gameStart() function:
    function gameStart() {
        if (!gameStarted && !gameEnded) {
            // Only do if the game has not yet been started (and has not ended)
            gameStarted = true;
            tilesNew();
            boardDisplay();
        }
    }
Code of the tilesNew() function:
    function tilesNew() {
        let found = false;
        do {
            newX = Math.floor(Math.random() * boardSize);
            newY = Math.floor(Math.random() * boardSize);
            if (boardGrid[newX][newY] == 0) {
                // Check if generated position is actually empty
                found = true;
                boardGrid[newX][newY] = 2;
            }
       } while (found == false);
    }
Movement of the tiles.
The tiles are moved each time, the player pushes one of the "direction" buttons on the webpage. The function called in this case is tilesMove({direction}), the direction of the movement being passed as argument. The function first moves the tiles, merging those with equal values, that collide during this movement (updating the cells of the two dimensional board grid array), then calls checkGameEnd() to check if the winning value of 2048 has been reached resp. if there aren't any free places left on the board. If the game may continue, a new value "2" tile is generated by calling the function tilesNew(). The new board layout (resuling from the tiles movement with possible tile merging and the new value "2" tile, if any) is displayed calling boardDisplay(). I'm aware that it would have been possible to largely shorten the code concerning the tile movement; just modify the code at your ease, if you consider this important...
Code of the tilesMove() function:
    function tilesMove(direction) {
        if (gameStarted && !gameEnded) {
            if (direction == 'up') {
                for (k = 1; k < boardSize; k++) {
                    for (i = 1; i < boardSize; i++) {
                        for (j = 0; j < boardSize; j++) {
                            if (boardGrid[i - 1][j] == 0) {
                                boardGrid[i - 1][j] = boardGrid[i][j];
                                boardGrid[i][j] = 0;
                            }
                            else if (boardGrid[i - 1][j] == boardGrid[i][j]) {
                                boardGrid[i - 1][j] = 2 * boardGrid[i][j];
                                boardGrid[i][j] = 0;
                            }
                        }
                    }
                }
            }
            else if (direction == 'down') {
                for (k = 1; k < boardSize; k++) {
                    for (i = boardSize - 2; i >= 0 ; i--) {
                        for (j = 0; j < boardSize; j++) {
                            if (boardGrid[i + 1][j] == 0) {
                                boardGrid[i + 1][j] = boardGrid[i][j];
                                boardGrid[i][j] = 0;
                            }
                            else if (boardGrid[i + 1][j] == boardGrid[i][j]) {
                                boardGrid[i + 1][j] = 2 * boardGrid[i][j];
                                boardGrid[i][j] = 0;
                            }
                        }
                    }
                }
            }
            else if (direction == 'left') {
                for (k = 1; k < boardSize; k++) {
                    for (j = 1; j < boardSize; j++) {
                        for (i = 0; i < boardSize; i++) {
                            if (boardGrid[i][j - 1] == 0) {
                                boardGrid[i][j - 1] = boardGrid[i][j];
                                boardGrid[i][j] = 0;
                            }
                            else if (boardGrid[i][j - 1] == boardGrid[i][j]) {
                                boardGrid[i][j - 1] = 2 * boardGrid[i][j];
                                boardGrid[i][j] = 0;
                            }
                        }
                    }
                }
            }
            else if (direction == 'right') {
                for (k = 1; k < boardSize; k++) {
                    for (j = boardSize - 2; j >= 0 ; j--) {
                        for (i = 0; i < boardSize; i++) {
                            if (boardGrid[i][j + 1] == 0) {
                                boardGrid[i][j + 1] = boardGrid[i][j];
                                boardGrid[i][j] = 0;
                            }
                            else if (boardGrid[i][j + 1] == boardGrid[i][j]) {
                                boardGrid[i][j + 1] = 2 * boardGrid[i][j];
                                boardGrid[i][j] = 0;
                            }
                        }
                    }
                }
            }
            checkGameEnd();
            if (!gameEnded) {
                tilesNew();
            }
            boardDisplay();
            if (gameEnded) {
                // Game over
                if (gameWin == true) {
                    alert("Great! You did it!");
                }
                else {
                    alert("Game over! Maybe, if you try another one...");
                }
            }
        }
    }
Code of the checkGameEnd() function:
    function checkGameEnd() {
        gameWin = false; gameLose = true;
        for (i = 0; i < boardSize; i++) {
            for (j = 0; j < boardSize; j++) {
                if (boardGrid[i][j] == 2048) {
                    // If there is any tile with a value of 2048, the game is over (with player wins)
                    gameWin = true;
                }
                else if (boardGrid[i][j] == 0) {
                    // If there remains any empty tile, the game isn't yet over (with player loses)
                    gameLose = false;
                }
            }
        }
        if (gameWin || gameLose) {
            gameEnded = true;
            newX = -1; newY = -1;
        }
    }
Display of the board.
The display of the board is done by calling the function boardDisplay(). All cells of the HTML tables corresponding to the boards grid cells have an id attribute of the following form: b{board-size}_{cell-row}{cell-column}. This makes it easily possible to change the cells' properties and value, using document.getElementById({id}). So, for each cell of the board grid, the value of the <td> element is set to blank for an empty cell, or to the tile value, if there actually is a tile. Then the <td> background-color is set to the color corresponding to the tile value (color retrieved from the tileColors array), and, as a given board has a given tile value font size, the font size (retrieved from the tileFontSizes array) is also set. These two settings are done by changing the value of the style attribute of the <td> element.
Code of the boardDisplay() function:
    function boardDisplay() {
        for (i = 0; i < boardSize; i++) {
            for (j = 0; j < boardSize; j++) {
                let tileId = 'b' + boardSize + '_' + i + j;   // id attribute for actual table cell
                if (document.getElementById) {
                    if (boardGrid[i][j] == 0) {
                        // Empty place
                        document.getElementById(tileId).firstChild.data = "";
                    }
                    else {
                        // Tile with given value
                        document.getElementById(tileId).firstChild.data = boardGrid[i][j];
                    }
                    // Determine tile color (depending on its value)
                    let tileColor = "White";
                    for (k = 0; k < 12; k++) {
                        // Find index of actual value (that is the same as the index of the colors array)
                        if (boardGrid[i][j] == tileValues[k]) {
                            tileColor = tileColors[k];
                            if (boardGrid[i][j] == 2 && i == newX && j == newY) {
                                tileColor = "Blue";   // extra color for the new tile
                            }
                        }
                    }
                    // Construct the style attribute of the <td> corresponding to this tile
                    // Take the color from the colors array (using the index determined before)
                    // Take the font size from the font sizes array (with index derived from board size)

                    let tileStyle = "background-color:" + tileColor + "; font-size:" + tileFontSizes[boardSize - 3];
                    // Apply the style to the <td> element
                    document.getElementById(tileId).style = tileStyle;
                }
            }
        }
    }
4. Simple 2048 game HTML.
 
I show only those lines of the HTML code that make a call to JavaScript, or are otherwise important to understand the JavaScript code.
HTML defining the board selection buttons and calling the function boardSelect({board-size}):
    <table border="0">
        <tr>
            <td><input type="button" onclick="boardSelect('3')" value="3x3 board"/></td>
            <td width="25px">&nbsp;</td>
            <td><input type="button" onclick="boardSelect('4')" value="4x4 board"/></td>
            <td width="25px">&nbsp;</td>
            <td><input type="button" onclick="boardSelect('5')" value="5x5 board"/></td>
            <td width="25px">&nbsp;</td>
            <td><input type="button" onclick="boardSelect('6')" value="6x6 board"/></td>
        </tr>
    </table>
HTML defining the direction buttons and calling the function tilesMove({direction}):
    <tr>
        <td>&nbsp;</td>
        <td><img src="up.png" alt="Up" title="Move upwards" border="0" onclick="tilesMove('up')"></td>
        <td>&nbsp;</td>
    </tr>
    <tr>
        <td><img src="left.png" alt="Left" title="Move leftwards" border="0" onclick="tilesMove('left')"></td>
        <td>&nbsp;</td>
        <td><img src="right.png" alt="Right" title="Move rightwards" border="0" onclick="tilesMove('right')"></td>
    </tr>
    <tr>
        <td>&nbsp;</td>
        <td><img src="down.png" alt="Down" title="Move downwards" border="0" onclick="tilesMove('down')"></td>
        <td>&nbsp;</td>
    </tr>
As I mentionned above, the four boards are included in the HTML code as tables that are part of the <tr> element of an outer table, and displaying a given board is done by setting the visibility property of the <style> attribute of the corresponding <tr> element to visible. There is a Start button, calling the function gameStart() included with each of the four board tables. Here the code for the 3x3 board (note the id attributes of the table cells, used within the Javascript code to set the properties and value for a given tile):
    <tr id="b3" style="visibility:collapse">
        <td><table border="1">
            <tr>
                <td id="b3_00" width="80" height="80" align="center" valign="middle" style="background-color: white; font-size:30">&nbsp;</td>
                <td id="b3_01" width="80" height="80" align="center" valign="middle" style="background-color: white; font-size:30">&nbsp;</td>
                <td id="b3_02" width="80" height="80" align="center" valign="middle" style="background-color: white; font-size:30">&nbsp;</td>
            </tr>
            <tr>
                <td id="b3_10" width="80" height="80" align="center" valign="middle" style="background-color: white; font-size:30">&nbsp;</td>
                <td id="b3_11" width="80" height="80" align="center" valign="middle" style="background-color: white; font-size:30">&nbsp;</td>
                <td id="b3_12" width="80" height="80" align="center" valign="middle" style="background-color: white; font-size:30">&nbsp;</td>
            </tr>
            <tr>
                <td id="b3_20" width="80" height="80" align="center" valign="middle" style="background-color: white; font-size:30">&nbsp;</td>
                <td id="b3_21" width="80" height="80" align="center" valign="middle" style="background-color: white; font-size:30">&nbsp;</td>
                <td id="b3_22" width="80" height="80" align="center" valign="middle" style="background-color: white; font-size:30">&nbsp;</td>
            </tr>
            <tr>
                <td colspan="3" align="center" valign="middle" style="padding-top:10px; padding-bottom:10px"><input type="button" onclick="gameStart()" value="Start"/></td>
            </tr>
        </table></td>
    </tr>

If you find this page helpful or if you like the Simple 2048 game web application, please, support me and this website by signing my guestbook.