Word Scramble game in HTML and JS

Do not miss this exclusive book on Binary Tree Problems. Get it now for free.

In this article at OpenGenus, we are going to develop the word Scramble game in HTML, CSS and JavaScript.

Table of contents:

  1. Introduction
  2. Rules
  3. Approach
  4. Implementation
  5. Output
  6. Conclusion

Introduction

Word Scramble is a classic word game that challenges players to form words using letter tiles and score points based on the letters' values. In this article, we will explore how to build a Word Scramble game using HTML and JavaScript. By following these step-by-step instructions, you will be able to create an interactive and engaging word game that can be played in any modern web browser.

Rules

  • A random word is generated from a list of words.
  • The word is scrambled and displayed to the player.
  • The player has a certain number of guesses to try to unscramble the word.
  • If the player guesses the word correctly, they win the game.
  • If the player runs out of guesses, they lose the game.

Approach

This was a complex game that had to be broken down into its component parts step by step. The instruction screen was simple enough to create, and finishing that screen along with the HTML layout and CSS styling was the first thing we'll complete.

For the game logic of JS, we first initialized the players, created the game board (as a grid), and created the bag of randomly placed tiles. From there, we will create functions that allow each player to load their shelves, select and play a tile, and submit a word to end their turn. Next, we made all the buttons functional using event listeners - from the letter key to refreshing the shelf and returning all the tokens to the shelf.

The next section was about creating the logic for valid placement of the tokens. This included writing functions that checked neighborhood rules and word alignment. The conditionals piled up, and we admit that it was difficult to keep track of them. Later, we will be able to clean up the code to reduce some redundancies and split actions into different functions.

Implementation

We have simple file structure for this project.
We required three files "index.html" "style.css' and "script.js" in the same folder.

Now let's create structure for our project using html

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>Scramble</title>
    <link rel="stylesheet" href="./style.css">
    <script src="https://code.jquery.com/jquery-3.2.1.min.js" integrity="sha256-hwg4gsxgFZhOsEEamdOYGBf13FyQuiTwlAQgxVSNgt4=" crossorigin="anonymous"></script>
    <script type="text/javascript" src="./script.js"></script>
</head>

<body>
    <!-- INSTRUCTIONS -->
    <div class="instructions">
        <h1>INSTRUCTIONS</h1>
        <ul class="instructionsList">
            <li>Place tiles on the board to spell out words.  Click on the letter on your rack and then click again on the location you'd like to place it.</li>
            <li>Except for the first turn, at least one of your letters must touch a letter already in play, and your new letters must all be in the same row or column, and be touching another tile.</li>
            <li>The letter values of each tile can be accessed by clicking the tab on the right side of the screen.</li>
            <li>At the beginning of your turn, click "SHOW TILES" to reveal the letters on your rack.</li>
            <li>After you click "Submit Word" your score is tallied and your rack refreshed from the tile bag.</li>
            <li>Clicking "Back to Rack" will return the tiles from your current turn back to your rack.</li>
            <li>Clicking "Refresh Tiles" will give you a brand new rack and shuffle your current tiles back into the bag.  Doing this forfeits your current turn.</li>
            <li>When one player clears their rack and the bag is empty, the game ends.  The other player's score will decrease by the total value of the letters still in his/her rack.</li>
            <li>Enter both player's names and click "START" to begin!</li>
        </ul>
        <div class="playerNames">
            <input type="text" placeholder="Player 1 Name" class="playerOneName">
            <input type="text" placeholder="Player 2 Name" class="playerTwoName">
        </div>
        <button class="start">START</button>
    </div>

    <!-- LETTER VALUES KEY -->
    <div class="letterValuesBox">
        <div class="tab">
            <hr>
            <hr>
            <hr>
        </div>
        <div class="letterValues">
            <ul class="letterValueList"></ul>
        </div>
    </div>

    <!-- MAIN GAME BOARD -->
    <div class="container">
        <div class="title">Scramble</div>
        <div class="gameBoard">
        </div>
        <div class="playerOneScoreBox textBox">
            <h3 class="playerOneDisplayName">Player 1 Score</h3>
            <div class="playerOneScore"></div>
        </div>
        <div class="playerTwoScoreBox textBox">
            <h3 class="playerTwoDisplayName">Player 2 Score</h3>
            <div class="playerTwoScore"></div>
        </div>
        <div class="remainingTilesBox textBox">
            <h3>Remaining Tiles</h3>
            <div class="remainingTiles"></div>
        </div>
        <h2 class="playerOneTitle">Player 1 Tiles</h2>
        <h2 class="playerTwoTitle">Player 2 Tiles</h2>
        <div class="playerOneTiles">
            <div class="playerOneTilesRow">
            </div>
        </div>
        <div class="playerTwoTiles">
            <div class="playerTwoTilesRow">
            </div>
        </div>
        <button class="showTiles">SHOW TILES</button>
        <button class="refreshTiles">Refresh Tiles</button>
        <button class="backToRack">Back to Rack</button>
        <button class="submitWord">Submit Word</button>
    </div>

    <!-- PLAY AGAIN BUTTON -->
    <button class="playAgain">PLAY AGAIN</button>
</body>

</html>

Let's understand above code

The html element is the root element of an HTML page. It encapsulates all the content on the page.

The head element contains meta-information about the HTML document and external resources used by the page.

Body Section:
The body element represents the content of the web page that is visible to the user.

Instructions:
The div class="instructions" section contains the instructions for playing the game. It includes an h1 heading and an unordered list (ul) with several list items (li) explaining the game rules and mechanics.

Player Names:
The div class="playerNames" section contains two input elements (input) for entering the names of Player 1 and Player 2. The placeholder attribute provides initial text that disappears when the user starts typing.

Start Button:
The START button element represents a button that initiates the game when clicked.

Letter Values Key:
The div class="letterValuesBox" section represents a box that displays the letter values used in the game.

Tab:
The div class="tab" element provides a visual tab-like design using horizontal lines (hr) stacked vertically.

Letter Values:
The div class="letterValues" section contains an unordered list (ul) with the class name letterValueList. This list will hold the letter values for the game.

Main Game Board:
The div class="container" section represents the main game board.

Game Board:
The div class="gameBoard" element is an empty container where the game board will be displayed.

Player Scores:
The div elements with classes playerOneScoreBox and playerTwoScoreBox represent the score boxes for Player 1 and Player 2, respectively. They contain a heading (h3) and a score display container (div).

Remaining Tiles:
The div class="remainingTilesBox" section displays the number of tiles remaining in the game. It contains a heading (h3) and a container (div) to show the remaining tiles.

Player Tiles:
The div elements with classes playerOneTiles and playerTwoTiles represent the tile racks for Player 1 and Player 2, respectively. They contain headings (h2) to display the player names and empty containers (div) where the tiles will be displayed.

Action Buttons:
The button elements with classes showTiles, refreshTiles, backToRack, and submitWord represent various actions that can be performed during the game.

Now,Lets add some styling for our game, which makes our game more attractive

body {
    margin: 0;
}

The code begins with a CSS reset for the body element, setting the margin to 0.


/*--------INSTRUCTIONS SCREEN-----------*/
.instructions {
    text-align: center;
    border: 1px solid black;
    position: absolute;
    background-color: rgba(0, 0, 0, 0.9);
    width: 100vw;
    height: 100vh;
    color: white;
    z-index: 5;
    overflow-x: hidden ;
}

.instructionsList {
    text-align: left;
    width: 60%;
    margin: 30px auto;
    font-size: 20px;
}
  • The .instructions class defines the styling for an instructions screen. It sets the text alignment to center, applies a black border, and positions the element absolutely. It has a semi-transparent black background and covers the entire viewport (100vw and 100vh). The text color is white, and it has a z-index of 5 to ensure it appears above other elements. The overflow-x: hidden property is set to prevent horizontal scrolling.
  • The .instructionsList class styles a container for the instructions text. It aligns the text to the left, sets the width to 60%, centers it horizontally using auto margins, and sets the font size to 20px.
input {
    height: 50px;
    width: 200px;
    font-size: 30px;
    margin: 0 50px;
}

.start {
    height: 54px;
    width: 150px;
    font-size: 33px;
    margin-top: 20px;
    border-radius: 5%;
}

The input selector styles all input elements. It sets the height to 50px, width to 200px, font size to 30px, and adds a margin of 0 50px.

The .start class styles a start button. It sets the height to 54px, width to 150px, font size to 33px, adds a top margin of 20px, and applies a border-radius of 5%.

.letterValuesBox {
    display: flex;
    align-items: center;
    position: absolute;
    right: -150px;
    width: 200px;
    height: 500px;
    background-color: beige;
    color: brown;
    top: 50%;
    margin-top: -250px;
}

The .letterValuesBox class styles a box that displays letter values. It uses flexbox to align items vertically, positions it absolutely to the right (-150px), sets the width to 200px, height to 500px, background color to beige, and text color to brown. It is positioned in the center of the viewport using top: 50% and margin-top: -250px.

.tab {
    width: 50px;
    height: 65px;
    background-color: grey;
    border-radius: 10% 0 0 10%;
    display: flex;
    flex-direction: column;
    padding-top: 10px;
    border: 1px solid black;
    z-index: 3;
}

The .tab class styles a tab within the letter values box. It sets the width to 50px, height to 65px, background color to grey, and adds a border-radius of 10% on the left side. It uses flexbox to vertically align the content and adds a small top padding. It also has a black border and a z-index of 3.

hr {
    border: 1px solid black;
    width: 70%;
}

The hr selector styles a horizontal rule. It adds a black border and sets the width to 70%.

.letterValues {
    height: 500px;
    width: 150px;
    background-color: beige;
    border: 1px solid black;
    z-index: 3;
}

The .letterValues class styles the container for letter values. It sets the height to 500px, width to 150px, background color to beige, and adds a black border. It has a z-index of 3.

.slideLeft {
    animation: moveLeft 2s;
    animation-fill-mode: forwards
}
.slideRight {
    animation: moveRight 2s;
    animation-fill-mode: forwards
}

The .slideLeft and .slideRight classes define animations to move the letter values box left or right. They use the moveLeft and moveRight keyframes and specify a duration of 2 seconds with animation-fill-mode: forwards to retain the final state of the animation.

.container {
    background-color: beige;
    height: 100vh;
}

.title {
    text-align: center;
    font-size: 60px
}

.textBox {
    position: absolute;
    border: 1px solid black;
    padding: 5px;
    margin-top: 5px;
}

The .container class styles the main game screen container. It sets the background color to beige and height to 100vh (viewport height).

The .title class styles the game title. It centers the text and sets the font size to 60px.

The .textBox class styles a text box element. It adds a black border, padding of 5px, and a margin-top of 5px.

.playerOneScoreBox {
    left: 20px;
    top: 0;
}

.playerTwoScoreBox {
    right: 20px;
    top: 0;
}

The .playerOneScoreBox and .playerTwoScoreBox classes position the score boxes for player one and player two. They are positioned at the top-left and top-right corners respectively.

.remainingTilesBox {
    left: 20px;
    top: 50%;
}

h3 {
    font-size: 15px;
    margin: 0;
}

The .remainingTilesBox class positions the box that displays the remaining tiles. It is positioned at the top-left corner, below the score boxes.

The h3 selector styles level 3 headings. It sets the font size to 15px and removes any margin.

.row {
    position: relative;
    width: 480px;
    margin: 0 auto;
}

.box {
    border: 1px solid black;
    display: inline-block;
    width: 30px;
    height: 30px;
    text-align: center;
    float: left;
    font-size: 25px;
}

The .row class styles a row within the game board. It is positioned relatively and has a width of 480px with auto margins.

The .box class styles individual cells within the game board. They have a black border, inline-block display, width and height of 30px, centered text, and a font size of 25px.

.playerOneScore,
.playerTwoScore,
.remainingTiles {
    font-size: 35px;
    text-align: center;
    margin: 5px;
}

.playerOneTitle,
.playerTwoTitle {
    clear: both;
    text-align: center;
    position: relative;
    top: 20px;
}

.playerOneTiles,
.playerOneTitle {
    display: none;
}

.playerOneTile,
.playerTwoTile,
.permInPlay,
.tempInPlay {
    background-image: url("./tile.jpeg");
    background-size: cover;
    background-repeat: no-repeat;
}

.playerTwoTiles,
.playerTwoTitle {
    display: show;
}

.playerOneTilesRow,
.playerTwoTilesRow {
    clear: both;
    position: relative;
    top: 5px;
    width: 224px;
    margin: 20px auto;
}

.tileBox {
    border: 1px solid black;
    display: inline-block;
    width: 30px;
    height: 30px;
    text-align: center;
    float: left;
    font-size: 25px;
}

.playerOneTilesRow,
.playerTwoTilesRow {
    display: none;
}

.selected {
    border-color: red;
}

The .playerOneTitle and .playerTwoTitle classes style the titles for the tile racks of player one and player two respectively. They are centered and have a relative position with a top margin.

The .playerOneTiles, .playerTwoTiles, .playerOneTitle, and .playerTwoTitle classes control the display of the tile racks and titles. .playerOneTilesRow and .playerTwoTilesRow style the rows of tiles for player one and player two respectively. By default, .playerOneTilesRow is hidden, and .playerTwoTilesRow is visible.

The .playerOneTile, .playerTwoTile, .permInPlay, and .tempInPlay classes style the individual tiles. They set a background image using the URL "./tile.jpeg", adjust the background size to cover, and disable background repetition.


/*-----------BUTTONS-----------*/
.refreshTiles,
.backToRack,
.showTiles,
.playAgain {
    position: relative;
    top: 3px;
    left: 10px;
    clear: both;
    height: 36px;
    width: 100px;
    font-size: 12px;
    border-radius: 5%;
}

.playAgain {
    display: none;
    top: 50vh;
    left: 50%;
    margin: -18px 0 0 -50px;
}

.submitWord {
    clear: both;
    position: relative;
    height: 36px;
    width: 100px;
    font-size: 12px;
    border-radius: 5%;
    display: block;
    margin: 30px auto;
}

The .refreshTiles, .backToRack, .showTiles, and .submitWord classes style various buttons in the game interface. They have a relative position, different heights and widths, font sizes, and border-radius values. The .playAgain class positions the play again button at the center of the viewport and is initially hidden.

@keyframes moveLeft {
    from {
        right: -150px;
    }
    to {
        right: 0;
    }
}

@keyframes moveRight {
    from {
        right: 0;
    }
    to {
        right: -150px;
    }
}

The @keyframes section defines the moveLeft and moveRight animations used earlier. They animate the right property to move the letter values box left or right.

@media (max-width: 800px) {
    .instructionsList {
        text-align: left;
        width: 40%;
        margin: 30px auto;
        font-size: 12px;
    }

    input {
        height: 25px;
        width: 100px;
        font-size: 15px;
        margin: 0 50px;
    }
    .row {
        width: 330px;
    }
    .playerOneTilesRow,
    .playerTwoTilesRow {
        width: 224px;
    }
    .refreshTiles,
    .backToRack,
    .showTiles,
    .submitWord {
        height: 18px;
        width: 70px;
        font-size: 8px;
        top: 5px;
    }
    .box,
    .tileBox {
        width: 20px;
        height: 20px;
        font-size: 18px;
    }
    .title {
        font-size: 40px;
    }
    h3 {
        font-size: 10px;
    }
    .playerOneScoreBox {
        left: 0;
        top: 0;
    }
    .playerTwoScoreBox {
        right: 0;
        top: 0;
    }
    .remainingTilesBox {
        top: 70%;
    }
    .playerOneScore,
    .playerTwoScore,
    .remainingTiles {
        font-size: 25px;
        text-align: center;
        margin: 0;
    }
}

The @media query section applies responsive styles for screens with a maximum width of 800px. It adjusts the font sizes, dimensions, and positions of various elements to fit smaller screens.

Now, Let's see our main and complex logic of the game.Here we are using JQuery for DOM manipulation and all even listner.

$(function() {

    var shuffledBag = [];
    var tempBag = [];
    var selected = false;
    var tabCounter = 0;
    var firstTurn = true;
    var sameColumn = true;
    var sameRow = true;
    var players = []
  • The code is wrapped inside a jQuery $(function() {...}) function, which ensures that the code executes when the document is ready.
  • The code defines several variables used throughout the game, such as shuffledBag, tempBag, selected, tabCounter, firstTurn, sameColumn, sameRow, and players. These variables store game-related information and will be used later.
 //-------BEGIN FUNCTION DECLARATIONS------
    //resets everything
    var resetGame = function() {
            //global variables
            shuffledBag = [];
            tempBag = [];
            selected = false;
            tabCounter = 0;
            firstTurn = true;
            sameColumn = true;
            sameRow = true;

            //player objects
            players =[{
                name: "",
                score: 0,
                rack: []
            },
            {
                name: "",
                score: 0,
                rack: []
            }]
        }
  • The resetGame function resets all the game-related variables and initializes the players array.
    var createBag = function() {
        tileBag = [
            { letter: "E", score: 1, count: 12 },
            { letter: "A", score: 1, count: 9 },
            { letter: "I", score: 1, count: 9 },
            { letter: "O", score: 1, count: 8 },
            { letter: "N", score: 1, count: 6 },
            { letter: "R", score: 1, count: 6 },
            { letter: "T", score: 1, count: 6 },
            { letter: "L", score: 1, count: 4 },
            { letter: "S", score: 1, count: 4 },
            { letter: "U", score: 1, count: 4 },
            { letter: "D", score: 2, count: 4 },
            { letter: "G", score: 2, count: 3 },
            { letter: "B", score: 3, count: 2 },
            { letter: "C", score: 3, count: 2 },
            { letter: "M", score: 3, count: 2 },
            { letter: "P", score: 3, count: 2 },
            { letter: "F", score: 4, count: 2 },
            { letter: "H", score: 4, count: 2 },
            { letter: "V", score: 4, count: 2 },
            { letter: "W", score: 4, count: 2 },
            { letter: "Y", score: 4, count: 2 },
            { letter: "K", score: 5, count: 1 },
            { letter: "J", score: 8, count: 1 },
            { letter: "X", score: 8, count: 1 },
            { letter: "Q", score: 10, count: 1 },
            { letter: "Z", score: 10, count: 1 }
        ]
    }
  • The createBag function initializes the tileBag array, which represents the available tiles in the game with their respective letters, scores, and counts.
    //create physical game board
    var createBoard = function() {
        for (i = 0; i < 15; i++) {
            var newRow = $('<div class="row"></div>');
            for (j = 0; j < 15; j++) {
                var newBox = $('<div class="box"></div>');
                newBox.attr("data-row", i);
                newBox.attr("data-column", j);
                newRow.append(newBox);
            }
            $('.gameBoard').append(newRow);
        }
    }
  • The createBoard function creates the physical game board by dynamically generating a 15x15 grid of div elements with the appropriate data attributes for rows and columns.
   //creates the letter values key
   var createLetterKey = function() {
       tileBag.forEach(function(tile) {
           var newListing = $('<li>');
           newListing.text(tile.letter + ": " + tile.score + " points");
           $('.letterValueList').append(newListing);
       });
   }

   //creates a temporary bag giving each tile its own array item
   var createTileBag = function() {
       while (tileBag.length > 0) {
           tempBag.push(tileBag[0]);
           tileBag[0].count--;
           if (tileBag[0].count === 0) {
               tileBag.shift();
           }
       }
   }
  • The createLetterKey function creates a list of the available letters and their corresponding scores, appending them to a specific DOM element.
  • The createTileBag function creates a temporary bag (tempBag) by iterating over the tileBag array and adding each tile to tempBag based on its count. The count is decreased for each tile added, and if the count reaches zero, the tile is removed from the tileBag array.
    //shuffles the bag
    var shuffleBag = function() {
        while (tempBag.length > 0) {
            var rndm = Math.floor(tempBag.length * Math.random());
            shuffledBag.push(tempBag[rndm]);
            tempBag.splice(rndm, 1);
        }
    }
  • The shuffleBag function shuffles the tiles in the tempBag array by randomly selecting and removing tiles one by one and adding them to the shuffledBag array.
    //tallies up score and declares a winner
    var endGame = function() {
        //deducts remaining tile values from each player
        players.forEach(function(player) {
            player.rack.forEach(function(tile) {
                player.score -= tile.score;
            })
        })

        //re-initializes the game board
        $('.tempInPlay').text("");
        $('.permInPlay').text("");
        $('.permInPlay').removeClass('permInPlay');
        $('.playerOneTile').remove();
        $('.playerTwoTile').remove();
        $('.playerOneTiles').hide();
        $('.playerOneTitle').hide();
        $('.playerTwoTiles').show();
        $('.playerTwoTitle').show();
        $('.playerOneTilesRow').hide();
        $('.playerTwoTilesRow').hide();

        //winning conditions
        if (players[0].score > players[1].score) {
            alert(players[0].name + " is the winner!\nFinal score:\n" + players[0].name + ": " + players[0].score + " points\n" + players[1].name + ": " + players[1].score + " points");
        } else if (players[0].score < players[1].score) {
            alert(players[1].name + " is the winner!\nFinal score:\n" + players[0].name + ": " + players[0].score + " points\n" + players[1].name + ": " + players[1].score + " points");
        } else {
            alert("It's a tie game!");
        }

        //sets up for another game
        createBag();
        resetGame();
        $('.container').hide();
        $('.letterValuesBox').hide();
        $('.playAgain').show();
    };
  • The endGame function is called when the game ends. It deducts remaining tile values from each player's score, resets the game board and player racks, displays the winner or a tie, and prepares for another game.
    //determines whose turn it is
    var isItPlayerOnesTurn = function() {
        if ($('.playerOneTiles').css("display") !== 'none') {
            return true;
        } else {
            return false;
        }
    }
  • The isItPlayerOnesTurn function checks if it is player one's turn by checking the visibility of the player one tiles.
    //given a player, will fill their rack with up to 7 tiles
    var loadRack = function(player) {
        //adds tiles to the player's rack array until it's either at 7 or until the bag is empty
        for (i = player.rack.length; i < 7; i++) {
            if (shuffledBag.length > 0) {
                player.rack.push(shuffledBag[0]);
                shuffledBag.shift();
                if (player === players[0]) {
                    var newTileBox = $('<div class="playerOneTile tileBox"></div>');
                    $('.playerOneTilesRow').append(newTileBox);
                } else {
                    var newTileBox = $('<div class="playerTwoTile tileBox"></div>');
                    $('.playerTwoTilesRow').append(newTileBox);
                }
            }
        }
        //updates the DOM to display the correct text on the player's rack
        if (player === players[0]) {
            for (j = 0; j < $('.playerOneTile').length; j++) {
                $('.playerOneTile').eq(j).text(player.rack[j].letter);
            }
        } else {
            for (j = 0; j < $('.playerTwoTile').length; j++) {
                $('.playerTwoTile').eq(j).text(player.rack[j].letter);
            }
        }
    }
  • The loadRack function fills a player's rack with up to 7 tiles from the shuffledBag array. It adds the tiles to the player's rack array and updates the DOM to display the tiles on the player's rack.
    //returns the score value of a given tile
    var letterValue = function(tile) {
        var selectedLetter = tile.text();
        createBag();
        var selectedTile = tileBag.find(function(tile1) {
            return tile1.letter === selectedLetter;
        });
        return selectedTile.score;
    }
  • The letterValue function takes a tile and returns its score value by matching the tile's letter with the tileBag array.
    //given 2 tiles, returns the direction the 'adjacent' one is to the 'original' one
    var direction = function(original, adjacent) {
        if (original.attr('data-row') < adjacent.attr('data-row')) {
            return "bottom";
        } else if (original.attr('data-row') > adjacent.attr('data-row')) {
            return "top";
        } else if (original.attr('data-column') < adjacent.attr('data-column')) {
            return "right";
        } else {
            return "left";
        }
    }
  • The direction function determines the direction of an adjacent tile relative to the original tile based on their row and column positions.
    //tallies the score from a given turn and adds it to the player's total
    var tallyScore = function(player) {
        var counter = 0;
        $(".counted").removeClass('counted');

        //checks each new tile added
        for (var i = 0; i < $('.tempInPlay').length; i++) {
            player.score += letterValue($('.tempInPlay').eq(i));
            var adjacentToMe = adjacentTiles(parseInt($('.tempInPlay').eq(i).attr('data-row')), parseInt($('.tempInPlay').eq(i).attr('data-column')), true, true, true, true);

            //iterates along each adjacent tile for each new tile added to the board
            for (j = 0; j < adjacentToMe.length; j++) {
                if (adjacentToMe[j].hasClass('permInPlay') && !(adjacentToMe[j].hasClass('counted'))) {
                    player.score += letterValue(adjacentToMe[j]);
                    adjacentToMe[j].addClass('counted');
                    if ((sameRow && (adjacentToMe[j].attr('data-column') === $('.tempInPlay').eq(i).attr('data-column'))) || (sameColumn && (adjacentToMe[j].attr('data-row') === $('.tempInPlay').eq(i).attr('data-row')))) {
                        player.score += letterValue($('.tempInPlay').eq(i));
                    }
                    var stop = false;

                    //checks each direction of the current tile and keeps adding score values until edge of word or edge of board is reached
                    if (direction($('.tempInPlay').eq(i), adjacentToMe[j]) === "left") {
                        var dirCounter = 0;
                        while (stop === false) {
                            var nextOne = adjacentTiles(parseInt(adjacentToMe[j].attr('data-row')), parseInt(adjacentToMe[j].attr('data-column')) - dirCounter, false, false, true, false);
                            if (nextOne.length > 0 && nextOne[0].hasClass("permInPlay") && !(nextOne[0].hasClass('counted'))) {
                                player.score += letterValue(nextOne[0]);
                                nextOne[0].addClass("counted");
                                dirCounter++;
                            } else {
                                stop = true;
                            }
                        }
                    }
                    if (direction($('.tempInPlay').eq(i), adjacentToMe[j]) === "right") {
                        var dirCounter = 0;
                        while (stop === false) {
                            var nextOne = adjacentTiles(parseInt(adjacentToMe[j].attr('data-row')), parseInt(adjacentToMe[j].attr('data-column')) + dirCounter, false, false, false, true);
                            if (nextOne.length > 0 && nextOne[0].hasClass("permInPlay") && !(nextOne[0].hasClass('counted'))) {
                                player.score += letterValue(nextOne[0]);
                                nextOne[0].addClass("counted");
                                dirCounter++;
                            } else {
                                stop = true;
                            }
                        }
                    }
                    if (direction($('.tempInPlay').eq(i), adjacentToMe[j]) === "top") {
                        var dirCounter = 0;
                        while (stop === false) {
                            var nextOne = adjacentTiles(parseInt(adjacentToMe[j].attr('data-row')) - dirCounter, parseInt(adjacentToMe[j].attr('data-column')), true, false, false, false);
                            if (nextOne.length > 0 && nextOne[0].hasClass("permInPlay") && !(nextOne[0].hasClass('counted'))) {
                                player.score += letterValue(nextOne[0]);
                                nextOne[0].addClass("counted");
                                dirCounter++;
                            } else {
                                stop = true;
                            }
                        }
                    }
                    if (direction($('.tempInPlay').eq(i), adjacentToMe[j]) === "bottom") {
                        var dirCounter = 0;
                        while (stop === false) {
                            var nextOne = adjacentTiles(parseInt(adjacentToMe[j].attr('data-row')) + dirCounter, parseInt(adjacentToMe[j].attr('data-column')), false, true, false, false);
                            if (nextOne.length > 0 && nextOne[0].hasClass("permInPlay") && !(nextOne[0].hasClass('counted'))) {
                                player.score += letterValue(nextOne[0]);
                                nextOne[0].addClass("counted");
                                dirCounter++;
                            } else {
                                stop = true;
                            }
                        }
                    }
                }
            }
        }

        //permanently removes tiles in play from player's rack
        if (isItPlayerOnesTurn()) {
            while (counter < player.rack.length) {
                if ($('.playerOneTile').eq(counter).css("display") === 'none') {
                    player.rack.splice(counter, 1);
                    $('.playerOneTile').eq(counter).remove();
                } else {
                    counter++;
                }
            }
        } else {
            while (counter < player.rack.length) {
                if ($('.playerTwoTile').eq(counter).css("display") === 'none') {
                    player.rack.splice(counter, 1);
                    $('.playerTwoTile').eq(counter).remove();
                } else {
                    counter++;
                }
            }
        }
    }
  • The tallyScore function calculates the score for a given turn and updates the player's total score. It iterates over the tiles added in the current turn, calculates the score for each tile, considers adjacent tiles, and checks for word extensions in different directions.
    //ends a turn and starts a new one
    var turn = function() {
        $('.playerOneTilesRow').hide();
        $('.playerTwoTilesRow').hide();
        $('.showTiles').show();
        if (shuffledBag.length > 0) {
            loadRack(players[0]);
            loadRack(players[1]);
        }

        //conditions for going to the "end game" scenario
        if (players[0].rack.length === 0 || players[1].rack.length === 0) {
            endGame();
        } else {
            $('.playerOneTiles').toggle();
            $('.playerOneTitle').toggle();
            $('.playerTwoTiles').toggle();
            $('.playerTwoTitle').toggle();

            $('.playerOneScore').text(players[0].score);
            $('.playerTwoScore').text(players[1].score);
            $('.remainingTiles').text(shuffledBag.length);
        }
    }

    var returnToRack = function() {
        if (isItPlayerOnesTurn()) {
            $('.playerOneTile').css("display", "inline-block");
        } else {
            $('.playerTwoTile').css("display", "inline-block");
        }
        $('.tempInPlay').text("");
        $('.tempInPlay').removeClass('tempInPlay');
    }
    

Above function will takes all tiles placed on the board from the current turn and returns them to that player's rack.

    var adjacentTiles = function(row, column, justAbove, justBelow, justLeft, justRight) {
        var allAdjacents = [];
        if (justAbove && row > 0) {
            var above = $("[data-row=\'" + (row - 1) + "\'][data-column=\'" + column + "\']");
            allAdjacents.push(above);
        }
        if (justBelow && row < 14) {
            var below = $("[data-row=\'" + (row + 1) + "\'][data-column=\'" + column + "\']");
            allAdjacents.push(below);
        }
        if (justLeft && column > 0) {
            var left = $("[data-row=\'" + row + "\'][data-column=\'" + (column - 1) + "\']");
            allAdjacents.push(left);
        }
        if (justRight && column < 14) {
            var right = $("[data-row=\'" + row + "\'][data-column=\'" + (column + 1) + "\']");
            allAdjacents.push(right);
        }
        return allAdjacents;
    }

adjacentTiles function returns the DOM elements of all adjacent tiles; parameters determine which directions are checked

    //returns whether or not all newly placed tiles are all in a line (either vertically or horizontally)
    var correctOrientation = function() {
        sameColumn = true;
        sameRow = true;
        var row = $('.tempInPlay').eq(0).attr('data-row');
        var column = $('.tempInPlay').eq(0).attr('data-column');
        for (i = 0; i < $('.tempInPlay').length; i++) {
            if ($('.tempInPlay').eq(i).attr('data-column') !== column) {
                sameColumn = false;
            }
            if ($('.tempInPlay').eq(i).attr('data-row') !== row) {
                sameRow = false;
            }
        }
        return (sameRow || sameColumn);
    }

    var permanentAdjacent = function() {
        for (i = 0; i < $('.tempInPlay').length; i++) {
            var adjacentToMe = adjacentTiles(parseInt($('.tempInPlay').eq(i).attr('data-row')), parseInt($('.tempInPlay').eq(i).attr('data-column')), true, true, true, true);
            for (j = 0; j < adjacentToMe.length; j++) {
                if (adjacentToMe[j].hasClass('permInPlay')) {
                    return true;
                }
            }
        }
        return false;
    }

permanentAdjacent function returns whether at least one newly placed tile is touching a tile already on the board

    var allTouching = function() {
        var counter = 0;
        if (sameRow) {
            for (l = 0; l < $('.tempInPlay').length; l++) {
                var adjacentToMe = adjacentTiles(parseInt($('.tempInPlay').eq(l).attr('data-row')), parseInt($('.tempInPlay').eq(l).attr('data-column')), false, false, true, true);
                for (x = 0; x < adjacentToMe.length; x++) {
                    if (!(adjacentToMe[x].hasClass('permInPlay') || adjacentToMe[x].hasClass('tempInPlay'))) {
                        counter++;
                    }
                }
                if (parseInt($('.tempInPlay').eq(l).attr('data-column')) === 0 || parseInt($('.tempInPlay').eq(l).attr('data-column')) === 14) {
                    counter++;
                }
            }

        } else if (sameColumn) {
            for (l = 0; l < $('.tempInPlay').length; l++) {
                var adjacentToMe = adjacentTiles(parseInt($('.tempInPlay').eq(l).attr('data-row')), parseInt($('.tempInPlay').eq(l).attr('data-column')), true, true, false, false);
                for (x = 0; x < adjacentToMe.length; x++) {
                    if (!(adjacentToMe[x].hasClass('permInPlay') || adjacentToMe[x].hasClass('tempInPlay'))) {
                        counter++;
                    }
                }
                if (parseInt($('.tempInPlay').eq(l).attr('data-row')) === 0 || parseInt($('.tempInPlay').eq(l).attr('data-row')) === 14) {
                    counter++;
                }
            }
        }
        if (counter > 2) {
            return false;
        }
        return true;
    }

Above function returns whether or not all newly placed tiles are adjacent to each other.

    var submitWord = function() {
        correctOrientation();
        if ($('.tempInPlay').length > 0 && (firstTurn || permanentAdjacent()) && ($('.tempInPlay').length === 1 || correctOrientation()) && allTouching()) {
            if (isItPlayerOnesTurn()) {
                tallyScore(players[0]);
            } else {
                tallyScore(players[1]);
            }
            $('.tempInPlay').addClass('permInPlay');
            $('.tempInPlay').removeClass('tempInPlay');
            turn();
            firstTurn = false;
            sameColumn = true;
            sameRow = true;
        }
    }

Above code will checks to make sure all conditions for playing a word have been met, then tallies the score, and resets for the next turn

    var refreshTiles = function() {
        returnToRack();
        if (isItPlayerOnesTurn()) {
            players[0].rack.forEach(function(tile) {
                shuffledBag.splice(Math.floor(Math.random() * shuffledBag.length), 0, tile);
            });
            while (players[0].rack.length > 0) {
                players[0].rack.pop();
            }
            $('.playerOneTile').remove();
        } else {
            players[1].rack.forEach(function(tile) {
                shuffledBag.splice(Math.floor(Math.random() * shuffledBag.length), 0, tile);
            });
            while (players[1].rack.length > 0) {
                players[1].rack.pop();
            }
            $('.playerTwoTile').remove();
        }
        turn();
    }

Above code will takes all tiles in the player's rack and returns them to the bag.

    //reveals the current player's rack onscreen
    var showTiles = function() {
        if (isItPlayerOnesTurn()) {
            $('.playerOneTilesRow').show();
        } else {
            $('.playerTwoTilesRow').show();
        }
        $('.showTiles').hide();
    }

    //opens or closes the letter key on right side of the screen
    var tabClick = function() {
        if (tabCounter % 2 === 0) {
            $('.letterValuesBox').removeClass('slideRight');
            $('.letterValuesBox').addClass('slideLeft');
        } else {
            $('.letterValuesBox').removeClass('slideLeft');
            $('.letterValuesBox').addClass('slideRight');
        }
        tabCounter++;
    }

    var startGame = function() {
        if (($('.playerOneName').val() !== "") && ($('.playerTwoName').val() !== "")) {
            $('.instructions').fadeOut(1000);
            players[0].name = $('.playerOneName').val();
            players[1].name = $('.playerTwoName').val();
            $('.playerOneDisplayName').text(players[0].name + "\'s Score");
            $('.playerTwoDisplayName').text(players[1].name + "\'s Score");
            turn();
        } else {
            alert("Please make sure both players have entered their names!");
        }
    }

startGame function checks that each player entered a name and removes the instruction screen and initializes the board screen.

    var startingProcedure = function() {
        $('.playAgain').hide();
        $('.container').show();
        $('.letterValuesBox').show();
        $('.instructions').fadeIn();
        createTileBag();
        shuffleBag();
    }

    //--------BEGIN FUNCTION DEPLOYMENT---------

    //run these the first time only
    resetGame();
    createBag();
    createBoard();
    createLetterKey();
    startingProcedure();

Above is the function call for our game.

    //---------BEGIN EVENT LISTENERS---------------

    //visually marks a tile as 'selected' when clicked
    $(document.body).on('click', '.tileBox', function() {
        $('.tileBox').removeClass('selected');
        $(this).addClass('selected');
        selected = true;
    });
    
    //adds a tile to the board if nothing occupies that space already
    $(document.body).on('click', '.box', function() {
        if (selected) {
            if (!($(this).hasClass('permInPlay')) && (!$(this).hasClass('tempInPlay'))) {
                $(this).text($('.selected').text());
                $(this).addClass('tempInPlay')
                $('.selected').hide();
                $('.selected').removeClass('selected');
                selected = false;
            }
        }
    });

    //starts the game when the start button is clicked
    $('.start').click(startGame);

    //runs the submitWord function when the submit button is clicked
    $('.submitWord').click(submitWord);

    //refreshes the rack when the refresh tiles buttons is clicked
    $('.refreshTiles').click(refreshTiles);

    //returns all tiles to the rack when the return to rack button is clicked
    $('.backToRack').click(returnToRack);

    //reveals the tiles of the current player when the show tiles button is clicked
    $('.showTiles').click(showTiles);

    //opens or closes the letter key when the tab is clicked
    $('.tab').click(tabClick);

    //starts a new game when the play again button is clicked
    $('.playAgain').click(startingProcedure);

});

  • Above code contains several event listeners for our game.
  • For startGame, submitWord, refreshTiles, backToRock and playAgain buttons' event listen at above code.

Output

Below is the snpashot of our output

Conclusion

The development of a Word Scramble game using HTML and JavaScript offers a fun and engaging experience for players. The combination of these two technologies allows for the creation of an interactive and dynamic game that tests players' vocabulary and strategic skills.By leveraging HTML, the game provides a visually appealing user interface, making it easy for players to navigate and interact with the game elements. The use of JavaScript adds interactivity to the game, enabling features such as drag-and-drop functionality, and scoring.Furthermore, the game can be customized and expanded upon, allowing to add new features and functionalities. This flexibility ensures that the Word Scramble game can continue to evolve and provide an enjoyable experience for players.

Sign up for FREE 3 months of Amazon Music. YOU MUST NOT MISS.