javascriptfrontendwordsearch

Word search game. How to search the grid and highlight the result?


I'm new at programming and not experienced with JavaScript and I have an assignment to do for my school. I need to create a word search game, that has a form/button search that finds a word and highlights it in the grid. I have created the HTML/CSS layout, but I'm stuck linking it to JavaScript. I want to be able to search for the words "BANGKOK", "LONDON", "SINGAPORE", "HAVANA" AND "KYOTO", but I'm only able to search/highlight one letter/cell of the grid and I don't know how to make a string out of the letters from the grid so I could find those words. I'm stuck here. Really trying to understand. Can someone please help me/guide/explain to me how does it work? I have read a lot of things, but can't seem to find what I'm looking for. I would really appreciate it. I also need the highlighted word to stay highlighted, when it is found.

Here is what I have so far: https://jsfiddle.net/fwg8hequ/10/

function search() {
  var text = document.getElementById("query").value;
  var query = new RegExp("(\\b" + text + "\\b)", "gim");
  var e = document.getElementById("searchtext").innerHTML;
  var enew = e.replace(/(<span>|<\/span>)/igm, "");
  document.getElementById("searchtext").innerHTML = enew;
  var newe = enew.replace(query, "<span>$1</span>");
  document.getElementById("searchtext").innerHTML = newe;

}
@charset "UTF-8";

/* CSS Document */

@font-face {
  font-family: 'RobotoSlab';
  src: url('RobotoSlab-bold.ttf');
}

@font-face {
  font-family: 'RobotoMono';
  src: url('RobotoMono-Regular.ttf');
}

.container {
  position: relative;
  width: 1000px;
  height: 800px;
  background: #ffcc78;
}

.header {
  position: absolute;
  left: 24.7%;
  right: 26%;
  top: 5.25%;
  bottom: 86.75%;
  overflow: auto;
}

.header img {
  width: 58px;
  height: 58px;
  left: 247px;
  top: 46px;
  float: left;
}

.header h1 {
  left: 33.8%;
  right: 28.4%;
  width: 378px;
  height: 64px;
  font-family: RobotoSlab;
  font-style: normal;
  line-height: normal;
  font-size: 48px;
  letter-spacing: -1px;
  color: #E25C5C;
  line-height: 5.28%;
  float: right;
}

form {
  position: absolute;
  left: 24.7%;
  right: 26%;
  top: 18.75%;
  bottom: 75%;
}

input[type=text] {
  float: left;
  left: 24.7%;
  right: 35.8%;
  top: 18.75%;
  bottom: 75%;
  width: 410px;
  height: 50px;
  background: #FFFFFF;
  border: 1px solid #417505;
  box-sizing: border-box;
  border-radius: 5px;
}

button {
  position: absolute;
  left: 66.5%;
  right: 27.3%;
  top: 18.75%;
  bottom: 75%;
  background: linear-gradient(180deg, #76AD0C 0%, #417505 100%);
  border-radius: 5px;
  font-family: RobotoSlab;
  font-style: normal;
  line-height: normal;
  font-size: 15px;
  color: #FFFFFF;
  float: right;
}

.grid-container {
  display: grid;
  grid-template-columns: auto auto auto auto auto auto auto auto auto auto;
  background-color: #E25C5C;
  position: absolute;
  left: 24.7%;
  right: 26%;
  top: 30.5%;
  bottom: 7.88%;
  padding: 2px;
  border-radius: 5px;
}

.grid-item {
  background-color: #ffcc78;
  border: 2px solid #E25C5C;
  left: 26.2%;
  right: 27.2%;
  font-family: RobotoMono;
  line-height: 36px;
  font-size: 36px;
  letter-spacing: 2.9px;
  font-style: normal;
  font-weight: normal;
  text-align: center;
  padding: 2px;
}

#searchtext span {
  background-color: #F5A623;
}
<div class="container">

  <div class="header">
    <img src="icon.png" alt="Icon" height="58" width="58">

    <h1>WORD SEARCH</h1>

  </div>



  <form>
    <input name="query" id="query" type="text">
  </form>
  <button type="button" onClick="search();">SEARCH</button>



  <div class="grid-container" id="searchtext">

    <div class="grid-item">W</div>
    <div class="grid-item">S</div>
    <div class="grid-item">I</div>
    <div class="grid-item">A</div>
    <div class="grid-item">L</div>
    <div class="grid-item">C</div>
    <div class="grid-item">E</div>
    <div class="grid-item">O</div>
    <div class="grid-item">I</div>
    <div class="grid-item">V</div>

    <div class="grid-item">V</div>
    <div class="grid-item">A</div>
    <div class="grid-item">L</div>

    <div class="grid-item">B</div>
    <div class="grid-item">A</div>
    <div class="grid-item">N</div>
    <div class="grid-item">G</div>
    <div class="grid-item">K</div>
    <div class="grid-item">O</div>
    <div class="grid-item">K</div>


    <div class="grid-item">U</div>

    <div class="grid-item">T</div>
    <div class="grid-item">L</div>
    <div class="grid-item">O</div>
    <div class="grid-item">N</div>
    <div class="grid-item">D</div>
    <div class="grid-item">O</div>
    <div class="grid-item">N</div>
    <div class="grid-item">O</div>
    <div class="grid-item">I</div>


    <div class="grid-item">U</div>

    <div class="grid-item">S</div>
    <div class="grid-item">I</div>
    <div class="grid-item">N</div>
    <div class="grid-item">G</div>
    <div class="grid-item">A</div>
    <div class="grid-item">P</div>
    <div class="grid-item">O</div>
    <div class="grid-item">R</div>
    <div class="grid-item">E</div>


    <div class="grid-item">A</div>
    <div class="grid-item">L</div>
    <div class="grid-item">C</div>
    <div class="grid-item">O</div>
    <div class="grid-item">G</div>
    <div class="grid-item">E</div>
    <div class="grid-item">E</div>
    <div class="grid-item">U</div>
    <div class="grid-item">V</div>
    <div class="grid-item">R</div>

    <div class="grid-item">H</div>
    <div class="grid-item">A</div>
    <div class="grid-item">V</div>
    <div class="grid-item">A</div>
    <div class="grid-item">N</div>
    <div class="grid-item">A</div>
    <div class="grid-item">T</div>
    <div class="grid-item">L</div>
    <div class="grid-item">A</div>
    <div class="grid-item">A</div>

    <div class="grid-item">A</div>
    <div class="grid-item">B</div>
    <div class="grid-item">I</div>
    <div class="grid-item">S</div>
    <div class="grid-item">S</div>
    <div class="grid-item">N</div>
    <div class="grid-item">O</div>
    <div class="grid-item">R</div>
    <div class="grid-item">I</div>
    <div class="grid-item">S</div>

    <div class="grid-item">N</div>
    <div class="grid-item">K</div>
    <div class="grid-item">Y</div>
    <div class="grid-item">O</div>
    <div class="grid-item">T</div>
    <div class="grid-item">O</div>
    <div class="grid-item">A</div>
    <div class="grid-item">H</div>
    <div class="grid-item">B</div>
    <div class="grid-item">E</div>

    <div class="grid-item">Z</div>
    <div class="grid-item">M</div>
    <div class="grid-item">P</div>
    <div class="grid-item">T</div>
    <div class="grid-item">R</div>
    <div class="grid-item">E</div>
    <div class="grid-item">S</div>
    <div class="grid-item">J</div>
    <div class="grid-item">R</div>
    <div class="grid-item">L</div>

    <div class="grid-item">F</div>
    <div class="grid-item">P</div>
    <div class="grid-item">E</div>
    <div class="grid-item">K</div>
    <div class="grid-item">T</div>
    <div class="grid-item">A</div>
    <div class="grid-item">M</div>
    <div class="grid-item">L</div>
    <div class="grid-item">O</div>
    <div class="grid-item">J</div>


  </div>






</div>


Solution

  • Let's try and decompose what your search function does:

     function search() {
        // get the searched text OK
        var text = document.getElementById("query").value;
    
        // make a regexp out of the searched text OK
        var query = new RegExp("(\\b" + text + "\\b)", "gim");
    
        // retrieve the html content of the grid items's container OK
        var e = document.getElementById("searchtext").innerHTML;
    
        // remove all the spans tags from this html content (span tags in #searchtext are red)
        var enew = e.replace(/(<span>|<\/span>)/igm, "");
    
        // set the html stripped from the span tags as the content of #searchtext
        document.getElementById("searchtext").innerHTML = enew;
    
        // in the html stripped from span, wrap with spans all contents matching the search string
        var newe = enew.replace(query, "<span>$1</span>");
    
        // set the final html as the content of #searchtext
        document.getElementById("searchtext").innerHTML = newe;
    
    }
    

    So first you're extracting html code and trying to find text in this html code. But since you keep most of the tags (you only remove spans), you wont be able to find text only in the content of your divs (your search will be polluted with the div tags themselves).

    We could do complicated things with replace but there has to be another way.

    Let's now decompose the problem at hand: we would like to write a function that highlights the searched word in the grid according to the rules of this search word game (horizontaly, verticaly diagonaly).

    `function highlightSearchedWord() {....}`
    

    There is no builtin javascript function to do that so we have to split the problem.

    function highlightSearchedWord() {
        var text = getSearchedWord();
        highlightText(text);
    }
    

    we can solve getSearchedWord:

    function getSearchedWord() {
        var text = document.getElementById("query").value;
        return text;
    }
    

    Now in highlightText we need to find a word, that is being able to read letters given positions in the grid, compare them to the searched text, keep the list of the positions if the word is found and highlight these positions.

    A position in the grid can be seen as coordinates x (index of the column of the letter), and y (index of the row of the letter).

    In javascript we can define structured objects with braces {} so for instance the position 0,0 (first letter of the first row of the grid) will be { x: 0, y: 0}

    The first letter of your grid is in the first div (.grid-item) of your grid. Javascript gives you ways to retrieve elements based on their class name.

    `document.getElementsByClassName()`
    

    Documentation of getElementsByClassName

    So we are able to list all your grid elements by writing var items= document.getElementsByClassName('grid-item');

    Lets define the function getItems:

    function getItems() {
        var items= document.getElementsByClassName('grid-item');
        return items;
    }
    

    From this we can easily derive a new function:

    function getLetterAtPos(pos) {
        var items = getItems();
        // items is an array so we have to convert position {x, y} to index
        return items[posToIndex(pos)].innerHTML;
    }
    

    with posToIndex being:

    function posToIndex(pos) {
        // if the grid is 10x10 the first element of first row is index 0 (0 * 10 + 0)
        // !remember first indice is 0!
        // the first item of second row is index 10 (1 * 10 + 0)
        // the second item of the third row is index 21 (1 * 10 + 1)
        return pos.y * 10 + pos.x;
    }
    

    I'm going faster here to limit the size of the answer but the comments should help.

    A way to highlight a position could be helpful too:

    First define a css class doing the highlight (it easier to add or remove a class from an element than to wrap/unwrap its content in a span):

    css:

    .highlight {
        background-color:#F5A623;
    }
    

    Then javascript helper functions

    js:

    function addClass(elem, className) {
        // HTMLElement.className is a string with one or several class names separated by a space
        var classNames =  elem.className.split(" ");
        // we search the array classNames with indexOf to check if the class needs to be added
        if (classNames.indexOf(className) == -1) {
            // the class name is not found in the existing class names of this element so we just concatenate className to t elem.className
            elem.className += " " + className;
        }
    }
    
    function removeClass(elem, className) {
        // same as above we split elem.className into an array of classNames
        var classNames = elem.className.split(" ");
        // we search for index of the className we want to remove
        // index === -1 means not found, otherwise the index is the position of className in classNames
        var index = classNames.indexOf(className);
        if (index !== -1) {
            // javascript's version of remove at, splice(index, 1) means remove one item at index
            classNames.splice(index, 1);
            // join(' ') re concatenate classNames into a string of space separated class names
            elem.className = classNames.join(' ');
        }
    }
    
    function highlightPos(pos) {
        var item = getItems()[posToIndex(pos)];
        addClass(item, 'highlight');
    }
    
    // to reset highlights between searches
    function clearHighlights() {
        var items = getGridItems();
        for (var i = 0; i < items.length; i++) {
            removeClass(items[i], 'sel');
        }
    }
    

    References:

    split, indexOf and splice

    Now to read words in the grid we have to find at least the positions of first letter of the searched text and then try to match each letters of the searched text:

    function findLetterPositions(letter) {
        // we define a new array to receive our results
        var positions = [];
        // there are 10 columns x 10 rows of items
        var itemCount = 10 * 10;
        for (let i = 0; i < itemCount; i++) {
            var pos = indexToPos(i);
            // we compare letters lowercased
            if (getLetterAtPos(pos).toLowerCase() === letter.toLowerCase()) {
                // we have found letter at pos, so we add it to our array of positions (push)
                positions.push(pos);
            }
        }
        return positions;
    }
    

    with indexToPos being define as the reverse operation of posToIndex (takes an index, returns a pos):

    function indexToPos(index) {
        var y = Math.floor(index / columnCount);
        var x = index - y * columnCount;
        return { x: x, y: y };
    }
    

    For each found position we will need to try and match each letters of the searched text starting at this position and in a specified direction. For instance to the right (given the initial position of the first letter):

    function tryAndMatchRight(text,initialPos) {
    
        var x = initialPos.x;
        var y = initialPos.y;
        var columnCount = 10;
        // we need to check that we are far enough from the edge of the grid for the whole word to fit, otherwise give up by returning
        if (x + text.length > columnCount) {
            return;
        }
        // word found == true by default, the for loop below will try to prove otherwise
        var wholeWordFound = true;
        // we will keep track of the letter positions we're trying
        var wordPositions = [];
        // obviously
        wordPositions.push(initialPos);
        // we will try each letter of text starting from the second (index 1) to the end of text (index length-1)
        for (var x2 = 1; x2 < text.length; x2++) {
            // building the position object for the current letter
            var pos = { x: x + x2, y: y};
            // if the comparaison fails we can stop
            if (text[x2].toLowerCase() !== getLetterAtPos(pos).toLowerCase()) {
                wholeWordFound = false;
                break;
            }
            wordPositions.push(pos);
        }
        if (wholeWordFound) {
            highLightPositions(wordPositions);
        }
    }
    
    function hightlightPositions(positions) {
        for(var i = 0; i < positions.length; i++) {
            highlightPos(positions[i]);
        }
    }
    

    To sum it up the function to call when clicking the search button could be:

    function search() {
        clearHighlights();
        var text = getSearchedText();
        var firstLetterPositions = findLetterPositions(text[0]);
        for (var i = 0; i < firstLetterPositions.length; i++) {
            var initialPos = firstLetterPositions[i];
            tryAndMatchRight(text,initialPos);
            // we only did it rightward, but other directions need their own functions
            // tryAndMatchDown(text,initialPos); 
            // tryAndMatchDownRight(text,initialPos);
            // tryAndMatchUpRight(text,initialPos);
        }
    }
    

    Fully working solution as a fiddle here

    If you do want to understand programming I hope you'll appreciate this answer demonstrating how programing is mostly about splitting big problems into smaller problems until the problems are easily solvable (and understood) by the tools given by the language itself.

    It was fun for me anyway! Cheers

    (PS: as stated by Evochrome in the comments below the two helper functions addClass and removeClass are already solved by plain js this way element.classList.add("mystyle") and element.classList.remove("mystyle"))