Saturday 14 November 2020

Web Tutorial: Word Search Game (Part 3/4)

In the last part, we went through a whole lot of logic to place the words in the grid, make them intersect, and ensure the placements did not overrun the set boundaries.

What we need to do now, is click functionality. Remember in the first part, we set a click function on each element of the squares array within the populateGrid() method? Now, let's set this thing to run the method upon a click, passing in the x and y coordinates as arguments.
var square = $("<div><div>");
square.attr("data-x", x);
square.attr("data-y", y);
square.addClass("letter");
square.click
(
    (e) =>
    {
        this.displaySelected(e.currentTarget.dataset.x, e.currentTarget.dataset.y);
    }
);


This is the displaySelected() method. We generally want clicking to work only if the game is still in progress, so set an If block to check if seconds is more than 0.
displayTheme: function ()
{
    $(".theme .title").html(themes[this.themeIndex].title);
    $(".theme .description").html(themes[this.themeIndex].description);
    $(".theme .picture").attr("style", "background-image:url(" + themes[this.themeIndex].picture + ".png)");
},
displaySelected: function (x, y)
{
    if (this.seconds > 0)
    {
                    
    }
   


And also ensure that the current square clicked on, has not already been selected. Check if the selected property is false.
displaySelected: function (x, y)
{
    if (this.seconds > 0)
    {
        if (!this.squares[x][y].selected)
        {
                        
        }  
                     
    }
}    


Then we perform actions based on whether or not the correct property is true. Right now, all visible letters have correct set to true, so we don't have a way to test for false.
displaySelected: function (x, y)
{
    if (this.seconds > 0)
    {
        if (!this.squares[x][y].selected)
        {
            if (this.squares[x][y].correct)
            {

            }
            else
            {

            }  
                     
        }                        
    }
}    


If the selected square is correct, we add correct to the CSS class using the addClass() method. Of course, we will also set selected to true, so no further action can be taken on this square.
displaySelected: function (x, y)
{
    if (this.seconds > 0)
    {
        if (!this.squares[x][y].selected)
        {
            if (this.squares[x][y].correct)
            {
                this.squares[x][y].selected = true;
                $(".letter[data-x=" + x + "][data-y=" + y + "]").addClass("correct");

            }
            else
            {

            }                        
        }                        
    }
}    


If the selected square is wrong, then we add the class wrong. About half a second later, we remove the class. It's meant to be a warning, nothing more.
displaySelected: function (x, y)
{
    if (this.seconds > 0)
    {
        if (!this.squares[x][y].selected)
        {
            if (this.squares[x][y].correct)
            {
                this.squares[x][y].selected = true;
                $(".letter[data-x=" + x + "][data-y=" + y + "]").addClass("correct");
            }
            else
            {
                $(".letter[data-x=" + x + "][data-y=" + y + "]").addClass("wrong");

                setTimeout
                (
                    () =>
                    {
                        $(".letter[data-x=" + x + "][data-y=" + y + "]").removeClass("wrong");    
                    }
                    ,500
                )

            }                        
        }                        
    }
}    


Now for some styling! Here, we add a transition of one second to the letter CSS class. And then we use a hover pseudoselector to change the color to red when moused over.
.letter
{
    width: 4%;
    height: 4%;
    margin: 1% 1% 0 0;
    float: left;
    color: rgba(255, 200, 0, 1);
    cursor: pointer;
    text-align: center;
    font-size: 1.2em;
    font-weight: bold;
    text-transform: uppercase;
    border-radius: 50%;
    -webkit-transition: all 1s;
    transition: all 1s;

}

.letter:hover
{
    color: rgba(255, 0, 0, 1);
}


Nice, eh?



Now, for the classes correct and wrong we set the letter color to white. If correct, the background turns orange. And if wrong, the background turns red.
.letter:hover
{
    color: rgba(255, 0, 0, 1);
}

.correct
{
    background-color: rgba(255, 200, 0, 1);
    color: rgba(255, 255, 255, 1);
}

.wrong
{
    background-color: rgba(255, 0, 0, 1);
    color: rgba(255, 255, 255, 1);
}


Let's test by clicking on the correct (visible) letters! After clicking on them, you should not be able to click again.



Clicking on any blank square gives you a red circle for half a second!



Now that you're able to click on letters, this should mean you're able to influence the score, and win the game. We do this by creating the detectScore() method. In the div that's being styled by fill, we first remove the classes score_bad, score_ok, score_good and score_excellent. These classes have not been created yet, but don't worry about them for now.
decrementCounter: function ()
{
    if (this.timer == undefined)
    {
        this.timer = setInterval
        (
            () =>
            {
                if (this.seconds > 0)
                {
                    this.seconds = this.seconds - 1;
                }
                else
                {
                    clearInterval(this.timer);
                    this.timer = undefined;    
                    $(".dashboard .message").html("Time's up! Better luck next time!");
                    $("#btnGo").html("&#9654;");
                }

                $(".dashboard .timer .seconds").html(this.seconds);                            
            }
            ,1000
        )
    }
},
detectScore: function ()
{
    $(".score .meter .fill")
    .removeClass("score_bad")
    .removeClass("score_ok")
    .removeClass("score_good")
    .removeClass("score_excellent");
},
                
populateGrid: function ()
{


Declare variables selectedCorrectly and correct, and initialize them to 0.
detectScore: function ()
{
    $(".score .meter .fill")
    .removeClass("score_bad")
    .removeClass("score_ok")
    .removeClass("score_good")
    .removeClass("score_excellent");

    var selectedCorrectly = 0;
    var correct = 0;

},     


Here, we have another nested For loop where we increment selectedCorrectly and correct based on the properties in the squares array. correct is the number of squares that are letters in the words that we put in the grid. selectedCorrectly is the number of those squares that have been selected by clicking.
detectScore: function ()
{
    $(".score .meter .fill")
    .removeClass("score_bad")
    .removeClass("score_ok")
    .removeClass("score_good")
    .removeClass("score_excellent");

    var selectedCorrectly = 0;
    var correct = 0;

    for (var x = 0; x < this.maxSize; x++)
    {
        for (var y = 0; y < this.maxSize; y++)
        {
            if (this.squares[x][y].correct) correct++;
            if (this.squares[x][y].correct && this.squares[x][y].selected) selectedCorrectly++;
        }    
    }

},     


Then we derive percentage by dividing selectedCorrectly by correct, and multiplying by 100. We add an If block to cater for percentage going below 0, which really shouldn't happen. We probably should cater for a divide by zero case, but correct is never going to be 0 (and if it is, we have bigger problems than a divide by zero scenario). After all that, return percentage.
detectScore: function ()
{
    $(".score .meter .fill")
    .removeClass("score_bad")
    .removeClass("score_ok")
    .removeClass("score_good")
    .removeClass("score_excellent");

    var selectedCorrectly = 0;
    var correct = 0;

    for (var x = 0; x < this.maxSize; x++)
    {
        for (var y = 0; y < this.maxSize; y++)
        {
            if (this.squares[x][y].correct) correct++;
            if (this.squares[x][y].correct && this.squares[x][y].selected) selectedCorrectly++;
        }    
    }

    var percentage = (selectedCorrectly / correct) * 100;
    if (percentage < 0) percentage = 0;

    return percentage;  
 
},     


But we're not done yet, young buck! We have to do the visuals. So set the margin-left property of the div that's being styled by fill, to reflect the completion status.
detectScore: function ()
{
    $(".score .meter .fill")
    .removeClass("score_bad")
    .removeClass("score_ok")
    .removeClass("score_good")
    .removeClass("score_excellent");

    var selectedCorrectly = 0;
    var correct = 0;

    for (var x = 0; x < this.maxSize; x++)
    {
        for (var y = 0; y < this.maxSize; y++)
        {
            if (this.squares[x][y].correct) correct++;
            if (this.squares[x][y].correct && this.squares[x][y].selected) selectedCorrectly++;
        }    
    }

    var percentage = (selectedCorrectly / correct) * 100;
    if (percentage < 0) percentage = 0;

    $(".score .meter .fill").attr("style", "margin-left:-" + (100 - percentage) + "%");

    return percentage;
},     


Then use a series of If blocks to add CSS classes based on what percentage is. We'll also want to set the text if percentage is more than 10.
detectScore: function ()
{
    $(".score .meter .fill")
    .removeClass("score_bad")
    .removeClass("score_ok")
    .removeClass("score_good")
    .removeClass("score_excellent");

    var selectedCorrectly = 0;
    var correct = 0;

    for (var x = 0; x < this.maxSize; x++)
    {
        for (var y = 0; y < this.maxSize; y++)
        {
            if (this.squares[x][y].correct) correct++;
            if (this.squares[x][y].correct && this.squares[x][y].selected) selectedCorrectly++;
        }    
    }

    var percentage = (selectedCorrectly / correct) * 100;
    if (percentage < 0) percentage = 0;

    $(".score .meter .fill").attr("style", "margin-left:-" + (100 - percentage) + "%");

    if (percentage > 0) $(".score .meter .fill").addClass("score_bad");
    if (percentage > 10) $(".score .meter .fill").html(percentage.toFixed(0) + "%");
    if (percentage > 40) $(".score .meter .fill").removeClass("score_bad").addClass("score_ok");
    if (percentage > 60) $(".score .meter .fill").removeClass("score_ok").addClass("score_good");
    if (percentage > 80) $(".score .meter .fill").removeClass("score_good").addClass("score_excellent");


    return percentage;
},


In the decrementCounter() method, we want to add a condition. If detectScore() nets you a 100 percent score, run the clearInterval() function, set timer to undefined, set a victory message and button text.
decrementCounter: function ()
{
    if (this.timer == undefined)
    {
        this.timer = setInterval
        (
            () =>
            {
                if (this.seconds > 0)
                {
                    this.seconds = this.seconds - 1;
                }
                else
                {
                    clearInterval(this.timer);
                    this.timer = undefined;    
                    $(".dashboard .message").html("Time's up! Better luck next time!");
                    $("#btnGo").html("&#9654;");
                }

                $(".dashboard .timer .seconds").html(this.seconds);

                if (this.detectScore() == 100)
                {
                    clearInterval(this.timer);
                    this.timer = undefined;
                    $(".dashboard .message").html("Congrats! You've won!");
                    $("#btnGo").html("&#9654;");
                }  
                             
            }
            ,1000
        )
    }
},


Also, we want to run detectScore() in begin() so as to reset the progress bar.
begin: function ()
{
    this.themeIndex = this.generateRandomNo(themes.length);
    this.assignWords();
    this.displayTheme();
    this.populateGrid();
    this.detectScore();
    this.seconds = 300;
    this.decrementCounter();
    $(".dashboard .message").html("Search for words both horizontal and vertical. Each word is five letters and longer.");
    $("#btnGo").html("&#8634;");
},


Finally, the CSS classes! I've used brown, red, orange and yellow to reflect the scores. Don't feel shy about changing the color scheme, yo. This is just personal preference.
.score .meter .fill
{
    width: 100%;
    height: 100%;
    color: rgba(255, 255, 255, 1);
    text-align: right;
    font-weight: bold;
}

.score_bad
{
    background-color: rgba(100, 0, 0, 1);
}

.score_ok
{
    background-color: rgba(255, 0, 0, 1);
}

.score_good
{
    background-color: rgba(255, 100, 0, 1);
}

.score_excellent
{
    background-color: rgba(255, 255, 0, 1);
}

.letter
{
    width: 4%;


Here you see that the meter is brown when I only select a couple of correct letters.



Once I click on more correct letters, the meter starts showing a number because I got over 10 percent.



It's changing to red now...



And as I progress, it gets to orange...



...and above 80 percent, it's yellow!



See that? I hit 100 percent and now it has a victory message. The timer stops moving.



Next

We're making good progress! In the next part, let's add some finishing touches.

No comments:

Post a Comment